Entity Framework skill Series 9-tip 35-36

Source: Internet
Author: User

 

Tip 35. How to Write oftypeonly <tentity> ()

If you write the following LINQ to entities query:

VaR Results = from C in CTX. Vehicles. oftype <car> ()
Select C;

This will return. Cars include cars derived from the car type, such as the sportcar or SUV type.

If you only want cars, that is, do not want a derived type car, such as sportcar or SUV, you will write in LINQ to objects as follows:

VaR Results = from C in vehiclescollection
Where C. GetType () = typeof (CAR)
Select C;

But unfortunately, LINQ to entities does not know how to translate it.

Note:

It is quite easy to implement this in Entity SQL. If you use oftype (collection, [only] type) and contain the optional keyword "only", the object of the derived type will be excluded.

For example, the Entity SQL:

Select value (c)
From container. vehicles as C
Where C is of (only model. Car)

Only the car objects derived from the car, such as the SUV, will be excluded.

About six months ago, in Tip 5, I showed a work und. You only need to simply follow the steps below:

VaR Results = from C in CTX. Vehicles. oftype <car> ()
Where! (C is SUV )&&! (C is sportscar)
Select C;

However, this solution is cumbersome and error-prone, so I decided to find a better solution.

You can write the following code:

VaR Results = from C in CTX. Vehicles. oftypeonly <car> ()
Select C;

Behind this method, you need to do the following:

1. Call the oftype <car> () method on the source objectquery to obtain an ojbectquery <car> ()

2. Identify which object types are derived from car

3. Construct a Lambda expression and exclude all these derived types from the result.

4. Call where (expression <func <car, bool>) on ojbectquery <car> (), and pass in the lambda expression in the previous step.

Let's take a look at the code.

The following method combines all the code:

Public static iqueryable <tentity> oftypeonly <tentity> (
This objectquery query)
{
Query. checkargumentnotnull ("query ");

// Get the C-space entitytype
VaR queryable = query as iqueryable;
VaR wkspace = query. Context. metadataworkspace;
VaR elementtype = typeof (tentity );

// Filter to limit to the derivedtype of interest
Iqueryable <tentity> filter = query. oftype <tentity> ();

// See if there are any derived types of tentity
Entitytype cspaceentitytype =
Wkspace. getcspaceentitytype (elementtype );

If (cspaceentitytype = NULL)
Throw new notsupportedexception ("unable to find C-space type ");

Entitytype [] subtypes = wkspace. getimmediatedescendants (cspaceentitytype). toarray ();

If (subtypes. Length = 0) return filter;

// Get the clrtypes.
Type [] clrtypes = subtypes
. Select (ST => wkspace. getclrtypename (ST ))
. Select (Tn => elementtype. Assembly. GetType (TN ))
. Toarray ();
// Need to build! (A is type1 )&&! (A is type2) predicate and call it
// Via the provider
VaR Lambda = getisnotoftypepredicate (elementtype, clrtypes );
Return filter. Where (
Lambda as expression <func <tentity, bool>
);
}

As you can see, we used an extension method called getcspaceentitytype () in metadataworkspace. It accepts a CLR type and returns the corresponding entitytype.

This function is as follows:

Public static entitytype getcspaceentitytype (
This metadataworkspace,
Type type)
{
Workspace. checkargumentnotnull ("workspace ");
// Make sure the metadata for this Assembly is loaded.
Workspace. loadfromassembly (type. Assembly );
// Try to get the ospace type and if that is found
// Look for the cspace type too.
Entitytype ospaceentitytype = NULL;
Structuraltype cspaceentitytype = NULL;
If (workspace. trygetitem <entitytype> (
Type. fullname,
DataSpace. ospace,
Out ospaceentitytype ))
{
If (workspace. trygetedmspacetype (
Ospaceentitytype,
Out cspaceentitytype ))
{
Return cspaceentitytype as entitytype;
}
}
Return NULL;
}

Does this method seem familiar? Yes. I introduced it in tip 13. In fact, this function is a convenient tool in your EF toolbox.

Once we get the entitytype, we can find the derived entitytype. Then, the getimmediatedescendants () method is available. The method is as follows:

Public static ienumerable <entitytype> getimmediatedescendants (
This metadataworkspace,
Entitytype)
{
Foreach (VAR dtype in Workspace
. Getitemcollection (DataSpace. cspace)
. Getitems <entitytype> ()
. Where (E =>
E. basetype! = NULL &&
E. basetype. fullname = entitytype. fullname ))
{
Yield return dtype;
}
}

Note:: I am only interested in direct Derived classes, because when the direct Derived classes are filtered out, Their Derived classes will also be filtered out.

Next, we need to obtain the CLR type corresponding to each entitytype. To do this, use the EF metadata to find the CLR type name function corresponding to each entity type, which is as follows:

Public static string getclrtypename (
This metadataworkspace,
Entitytype cspaceentitytype)
{
Structuraltype ospaceentitytype = NULL;

If (workspace. trygetobjectspacetype (
Cspaceentitytype, out ospaceentitytype ))
Return ospaceentitytype. fullname;
Else
Throw new exception ("couldn't find CLR type ");
}

You can combine the methods with codes of the CLR type corresponding to a specific type name.

Writing some error-proof methods can complicate the situation, but in my example, I only assume that all types are in the same assembly as tentity. This makes things easy:

// Get the clrtypes.
Type [] clrtypes = subtypes
. Select (ST => wkspace. getclrtypename (ST ))
. Select (Tn => elementtype. Assembly. GetType (TN ))
. Toarray ();

... I am very confident that if you need this function, you can point out how to make this method stronger :)

At this time, we temporarily put the EF Metadata API behind it and turn to the expression API.

Gulp!

In fact, I thought it was very simple.

We only need a Lambda expression to filter out all derived CLR types. It is equivalent to the following form:

(Tentity entity) =>! (Entity is tsubtype1 )&&! (Entity is tsubtype2)

So I added the following method. The first parameter is the type of the lambda parameter, and then passed in all types to be excluded:

Public static lambdaexpression getisnotoftypepredicate (
Type parametertype,
Params type [] clrtypes)
{
Parameterexpression predicateparam =
Expression. parameter (parametertype, "parameter ");
Return expression. Lambda (
Predicateparam. isnot (clrtypes ),
Predicateparam
);
}

As you can see, this method creates a parameter and then calls another extension method to create the required andalso expression:

Public static expression isnot (
This parameterexpression parameter,
Params type [] types)
{
Types. checkargumentnotnull ("types ");
Types. checkarraynotempty ("types ");

Expression merged = parameter. isnot (types [0]);
For (INT I = 1; I <types. length; I ++)
{
Merged = expression. andalso (merged,
Parameter. isnot (types [I]);
}
Return merged;
}

Public static expression isnot (
This parameterexpression parameter,
Type type)
{
Type. checkargumentnotnull ("type ");

VaR parameteris = expression. typeis (parameter, type );
VaR parameterisnot = expression. Not (parameteris );
Return parameterisnot;
}

As you can see, the first method traverses all types and creates an isnot expression (by calling the second method), and then creates an andalso expression to merge with the previously created expression.

Note:: You may have noticed that this Code may produce a very deep andalso calling Hierarchical image. I think this may be okay, but if you have a very broad type, you may want to consider how to rewrite this query to balance the call tree.

So far, we have a method to create a lambdaexpression for filtering. We only need to convert it to expression <func <tentity, bool> and pass it into where (...) The extension method is as follows:

VaR Lambda = getisnotoftypepredicate (elementtype, clrtypes );
Return filter. Where (
Lambda as expression <func <tentity, bool>
);

This is done!

First of all, I admit that this is not just an hour, but I am happy to develop such a solution, which encourages me to learn more about expression and EF metadata APIs.

I hope you will also find this interesting.

Tip 36. How to construct a structure by querying

While writing a series of articles at the same time writing the EF controller for MVC, I found that I regularly want to create and attach a stub entity.

Unfortunately, this is not very easy. You need to first ensure that the object has not been loaded, otherwise you will see some annoying exceptions.

To avoid these exceptions, I often find that I have to write the following code:

Person assignedto = findpersoninstatemanager (CTX, P => P. ID = 5 );
If (assignedto = NULL)
{
Assignedto = new person {id = 5 };
CTX. attachto ("people", assignedto );
}
Bug. assignedto = assignedto;

However, these code is very cumbersome, and a lot of EF functions pollute my business logic, making it hard to read and write.

I hope I can write such code to replace it:

Bug. assignedto = CTX. People. getorcreateandattach (P => P. ID = 5 );

There are some mechanisms to make this possible, but the core issue is as follows:

(Person P) => P. ID = 5;

Such assertion or query is converted to the following:

() => New person {id = 5 };

Such a Lambda expression that contains the member initialization expression (memberinitexpression) body.

Query by example)

People familiar with the history of ORM may remember that in the "Good Times of the past", a large number of "ORM" Use a pattern called query by example:

Person result = Orm. querybyexample (new person {id = 5 });

You can use query by example to create an instance of the Database Class and fill in some fields. The ORM uses this sample object to create a query based on the value set in it.

Query to construct?

I mentioned this because the process of getting an instance from a query seems to be the opposite of the method of generating a query from an instance (for example, query by example.

Therefore, the title of this blog is "construct by example )"

My analogy/comparison makes this idea more brilliant.

But, ha, that's me!

Implementation

Whatever you say... How can we achieve this:

In the first step of work, we need a method to find an object in objectstatemanager:

Public static ienumerable <tentity> where <tentity> (
This objectstatemanager manager,
Func <tentity, bool> Predicate
) Where tentity: Class
{
Return manager. getobjectstateentries (
Entitystate. Added |
Entitystate. Deleted |
Entitystate. Modified |
Entitystate. unchanged
)
. Where (Entry =>! Entry. isrelationship)
. Select (Entry => entry. entity)
. Oftype <tentity> ()
. Where (predicate );
}

Then we compile getorcreateandattachstub (...) This extension method:

Public static tentity getorcreateandattachstub <tentity> (
This objectquery <tentity> query,
Expression <func <tentity, bool> Expression
) Where tentity: Class
{
VaR context = query. context;
VaR OSM = context. objectstatemanager;
Tentity entity = OSM. Where (expression. Compile ())
. Singleordefault ();
If (entity = NULL)
{
Entity = expression. Create ();
Context. attachtodefaultset (entity );
}
Return entity;
}

In this step, find a match in objectstatemanager.

If the lambdaexpression with memberinitexpression body converted based on the compiled asserted expression cannot find the object, call the create method of this Lambda expression to create a tentity instance and attach it.

I am not going to expand the attachtodefaultset method in depth, because I have shared the specific code in the previous tip 13.

So let's skip it and start...

Nature of the problem

The create extension method looks like this:

Public static t create <t> (
This expression <func <t, bool> predicateexpr)
{
VaR initializerexpression = predicatetoconstructorvisitor
. Convert <t> (predicateexpr );
VaR initializerfunction = initializerexpression. Compile ();
Return initializerfunction ();
}

Predicatetoconstructorvisitor is a specific expressionvisitor, which converts only one asserted expression to one memberinitexpression.

Public class predicatetoconstructorvisitor
{
Public static expression <func <t> convert <t> (
Expression <func <t, bool> predicate)
{
Predicatetoconstructorvisitor visitor =
New predicatetoconstructorvisitor ();
Return visitor. visit <t> (predicate );
}
Protected expression <func <t> visit <t> (
Expression <func <t, bool> predicate)
{
Return visitlambda (predicate as lambdaexpression)
As expression <func <t>;
}
Protected virtual expression visitlambda (
Lambdaexpression lambda)
{
If (lambda. Body is binaryexpression)
{
// Create a new instance expression I. e.
Newexpression newexpr =
Expression. New (lambda. Parameters. Single (). type );
Binaryexpression binary =
Lambda. Body as binaryexpression;
Return expression. Lambda (
Expression. memberinit (
Newexpr,
Getmemberassignments (Binary). toarray ()
)
);
}
Throw new invalidoperationexception (
String. Format (
"Onlybinary expressions are supported. \ n {0 }",
Lambda. Body. tostring ()
)
);
}
Protected ienumerable <memberassignment> getmemberassignments (
Binaryexpression binary)
{
If (Binary. nodetype = expressiontype. Equal)
{
Yield return getmemberassignment (Binary );
}
Else if (Binary. nodetype = expressiontype. andalso)
{
Foreach (VAR assignment in
Getmemberassignments (Binary. Left as binaryexpression). Concat (getmemberassignments (Binary. Right as binaryexpression )))
{
Yield return assignment;
}
}
Else
Throw new notsupportedexception (Binary. tostring ());
}
Protected memberassignment getmemberassignment (
Binaryexpression binary)
{
If (Binary. nodetype! = Expressiontype. Equal)
Throw new invalidoperationexception (
Binary. tostring ()
);
Memberexpression member = binary. Left as memberexpression;
Constantexpression constant
= Getconstantexpression (Binary. Right );
If (constant. value = NULL)
Constant = expression. Constant (null, member. type );
Return expression. BIND (member. Member, constant );
}
Protected constantexpression getconstantexpression (
Expression expr)
{
If (expr. nodetype = expressiontype. Constant)
{
Return expr as constantexpression;
}
Else
{
Type type = expr. type;
If (type. isvaluetype)
{
Expr = expression. Convert (expr, typeof (object ));
}
Expression <func <Object> Lambda
= Expression. Lambda <func <Object> (expr );
Func <Object> fn = lambda. Compile ();
Return expression. Constant (FN (), type );
}
}
}

Real work is done in visitlambda.

Basically, if:

1. This Lambda expression is not a binaryexpression.

2. Lambda expressions have more than one parameter. We can only construct one!

This function will throw an exception.

Then we start to traverse the binaryexpression until we get the same node, such as (P. id = 5), we convert it to the member Assignment Statement (ID = 5), so that we can construct a memberinitexpression.

When creating a member assignment statement, we also need to convert the expression on the right of all equal signs into a constant. For example, if the lambda expression is as follows:

(Person P) => P. ID = GETID ();

We need to calculate GETID () so that we can use this result in the member assignment statement.

Summary

Once again, I demonstrated how to mix EF metadata and CLR expressions to make it possible to write really useful help functions, and the process of writing applications is much less painful.

Enjoy...

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.