Tip 35. How to make oftypeonly<tentity> () such a notation

Source: Internet
Author: User

Tip 35. How to make oftypeonly<tentity> () such a notation

If you write such a LINQ to Entities query:

1 var results = from C in CTX. Vehicles.oftype<car> () 2               select C;

This will return cars including those derived from car types, such as Sportcar or SUV types.

If you just want cars that don't want to be a derivative like sportcar or SUV, you'll write in LINQ to objects:

1 var results = from C in vehiclescollection 2               where c.gettype () = = typeof (Car) 3               select C;

But unfortunately LINQ to Entities doesn't know how to translate it.

Attention:

This is fairly easy to implement in Entity SQL. If you use OFTYPE (collection, [Only]type) and include only this optional keyword, the object of the derived type will be excluded.

For example, this entity SQL:

1 SELECT VALUE (c) 2 from Container.vehicles as C 3 WHERE C was of (only Model.car)

will only return car, and those derived from car, such as SUVs, will be excluded.

About six months ago, in tip 5, I showed a workaround. You simply need to do this as follows:

1 var results = from C in CTX. Vehicles.oftype<car> () 2               where! ( C is SUV) &&! (c is sportscar) 3               select C;

But the solution was cumbersome and error-prone, so I decided to find a better solution.

You will be able to write code such as the following:

1 var results = from C in CTX. Vehicles.oftypeonly<car> () 2               select C;

Behind this approach is the need to do the following:

    1. On the source ObjectQuery the Oftype<car> () method is used to get a ojbectquery<car> ()
    2. Identify which entity types derive from car
    3. Building a lambda expression has the result of excluding all of these derived types.
    4. Invoke where (expression<func<car,bool>>) on ojbectquery<car> (), where the lambda expression passed in the previous step

Let's take a look at what the code looks like.

The following method binds all the code together:

 1 public static iqueryable<tentity> oftypeonly<tentity> (2 this objectquery query) 3 {4 query.  Checkargumentnotnull ("Query");  5//Get the C-space EntityType 6 var queryable = query as IQueryable; 7 var wkspace = query.  Context.metadataworkspace;  8 var ElementType = typeof (TEntity); 9//filter to limit to the derivedtype of interest iqueryable<tentity> Filter = query. Oftype<tentity> (); One//See if there is 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 20. Select (st = Wkspace. Getclrtypename (ST)) 21. Select (TN =&GT ElementType.Assembly.GetType (TN)) 22. ToArray (); Need to build the! (A is type1) &&! (A are type2) predicate and call it/via the provider-var lambda = getisnotoftypepredicate (ElementType, CLR Types); Return filter. Where (lambda as expression<func<tentity, bool>> 29); 30}

As you can see, we used an extension method called Getcspaceentitytype () in MetadataWorkspace, which takes a CLR type and returns the corresponding EntityType.

The function is as follows:

1 public static EntityType Getcspaceentitytype (  2 This      metadataworkspace workspace,  3      type type)  4 {  5     workspace. Checkargumentnotnull ("Workspace");  6     //Make sure the metadata for this assembly is loaded.  7     Workspace. loadfromassembly (type. Assembly);  8     //Try to get the Ospace type and if are found  9//Look for the     cspace type too.     EntityType Ospaceentitytype = null; One     structuraltype cspaceentitytype = null;     if (workspace. Trygetitem<entitytype> (         type. FullName,         dataspace.ospace,         ospaceentitytype))         (workspace. Trygetedmspacetype (             ospaceentitytype,             cspaceentitytype))             Cspaceentitytype as EntityType; (+}     ) and     return null; 25}

Does this approach look familiar? Yes, I introduced it in tip 13. In fact, this function is a handy tool in your EF toolkit.

Once we get EntityType, we can find the derived EntityType, when the getimmediatedescendants () method comes up. The method is as follows:

1 public static ienumerable<entitytype> getimmediatedescendants (  2 This        metadataworkspace workspace,  3        EntityType EntityType)  4 {  5     foreach (var dtype in Workspace  6                  . Getitemcollection (dataspace.cspace)  7                  . Getitems<entitytype> ()  8                  . Where (E =  9                       e.basetype! = null  &&                       e.basetype.fullname = = entitytype.fullname)) 11< c19/>{         yield return dtype;     14}

Note : I am only interested in direct derived classes, because when direct derived classes are filtered out, their derived classes are also filtered.

Next we need to get the CLR type corresponding to each EntityType. To do this, use an EF metadata to find the CLR type name function corresponding to each entity type, as follows:

1 public static string Getclrtypename (  2 This      metadataworkspace workspace,  3      EntityType Cspaceentitytype)  4 {  5     structuraltype ospaceentitytype = null;  6     if (workspace. Trygetobjectspacetype (  7             Cspaceentitytype, out Ospaceentitytype))  8         return Ospaceentitytype.fullname;  9     Else,         throw new Exception ("couldn ' t find CLR type"); 11}

You can combine its methods with some code for CLR types that correspond to specific types of names.

Writing some anti-error methods complicates the situation, but in my case I only assume that all types are in the same assembly as Tentity. This makes things simple:

1//Get the clrtypes. 2 type[] Clrtypes = subtypes 3       . Select (st = Wkspace. Getclrtypename (ST)) 4       . Select (tn = ElementType.Assembly.GetType (TN)) 5       . ToArray ();

... I'm pretty sure if you need this feature, you can point out how to make this method stronger:)

At this point, we temporarily put the EF metadata API behind and turn to the expression API.

gulp!

In fact, I thought it was simple.

We only need a lambda expression to filter out all the derived CLR types. is equivalent to this 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 all the types to be excluded are passed in:

1 public static LambdaExpression getisnotoftypepredicate (  2        Type parametertype,   3        params type[] Clrtypes)  4 {  5     parameterexpression predicateparam =  6                expression.parameter (ParameterType, " Parameter ");  7  8     return Expression.lambda (  9                predicateparam.isnot (clrtypes),                11 Predicateparam     ); 12}

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

1 public static Expression IsNot (  2 This     parameterexpression parameter,   3     params type[] types)  4 { C5/>5     types. Checkargumentnotnull ("types");  6     types. Checkarraynotempty ("types");  7     Expression merged = parameter. IsNot (Types[0]);  8 for     (int i = 1; i < types. Length; i++)  9     {         merged = expression.andalso (merged,             parameter. IsNot (Types[i]));     merged return,}15 public static Expression IsNot (     parameterexpression parameter, 17
   type type)     . Checkargumentnotnull ("type");     var parameteris = expression.typeis (parameter, type);     var parameterisnot = Expression.not (Parameteris) ;     return parameterisnot; 23}

As you can see, the first method iterates through all types and creates a IsNot expression (by calling the second method) and then merges with the previously created expression by creating a andalso expression.

Note : You may have noticed that this code may produce a very deep andalso call to the hierarchical image. I think it might be okay, but if you have a very broad type of hierarchy, you might want to consider how to rewrite this query to balance the call tree.

So far we have a way to create a lambdaexpression to do the filtering we need, we just need to convert it to expression<func<tentity, bool>> and pass it in The Where (...) extension method is as follows:

1 var lambda = getisnotoftypepredicate (ElementType, clrtypes); 2 return filter. Where (3      Lambda as expression<func<tentity, bool>> 4);

This is done!

First I admit that it's not all "hourly," but I'm happy to develop such a solution that motivates me to learn more about expression and the EF metadata API.

I hope you think it's interesting, too.

Tip 36. How to construct by query

During the writing tips series, while writing the EF Controller for MVC, I found that I regularly wanted to create and attach a stub entity.

Unfortunately this is not very easy, you need to first make sure that the entity is not already loaded, otherwise you will see some annoying exceptions.

To avoid these anomalies, I often find myself having to write some code like this:

1 person assignedto = Findpersoninstatemanager (CTX, p = = P.id = = 5); 2 if (AssignedTo = = null) 3 {4      AssignedTo = new person{id = 5}; 5      ctx. Attachto ("People", assignedto);  6} 7 Bug. AssignedTo = AssignedTo;

But the code is cumbersome, and a whole bunch of EF-capable parts pollute my business logic, making it harder to read and write.

I wish I could write this code instead:

1 bugs. AssignedTo = ctx. People.getorcreateandattach (p = p.id = = 5);

Now there are mechanisms to make this possible, but the core issue will be as follows:

1 (person p) = = P.id = 5;

Such assertions or queries are converted to the following:

1 () = new Person {ID = 5};

This is a lambda expression that contains the member initialization expression (memberinitexpression) body.

Search by example (query by Example)

People familiar with ORM history may recall that a large "orm" in "Good old days" uses a pattern called query by example:

1 person result = ORM. Querybyexample (new person {ID = 5});

With query By example you can create an instance of the database class that you want to populate with some fields, and ORM uses the sample object to create a query based on the value set in it.

Through query construction?

I mentioned this because the process of getting an instance from a query looks exactly the opposite of how one instance generates a query, such as query by Example.

So the title of this blog is: "Construct by Query (Construct by Example)"

For me this analogy/contrast makes this idea more beautiful.

But, ha, that's me!

Realize

Anyway, ... How can we really do this:

First step, we need a way to find an entity in Objectstatemanager:

1 public static ienumerable<tentity> where<tentity> (  2 This     Objectstatemanager Manager,  3     func<tentity, bool> predicate  4) where Tentity:class  5 {  6     return manager. Getobjectstateentries (  7         entitystate.added |  8         entitystate.deleted |      9         entitystate.modified |         entitystate.unchanged     )    . Where (entry =!entry. isrelationship)    . Select (Entry = entry. Entity)    . Oftype<tentity> ()    . Where (predicate); 16}

Then we actually write getorcreateandattachstub (...) This extension method:

1 public static TEntity getorcreateandattachstub<tentity> (  2 This     objectquery<tentity> query,  3     Expression<func<tentity, bool>> expression  4) where Tentity:class  5 {  6     var context = Quer Y.context;  7     var OSM = context. Objectstatemanager;  8     TEntity entity = OSM. Where (Expression.compile ())  9                         . Singleordefault ();     if (entity = = null),     {entity         = expression. Create (); The         context. Attachtodefaultset (entity);     17}

In this step, find a match in Objectstatemanager.

If an object is not found based on a compiled assertion expression conversion with the memberinitexpression body lambdaexpression, call the Create method of the lambda expression to make an instance of tentity and attach it.

I'm not going to go into the Attachtodefaultset method, because I've shared the code in the previous tip 13.

So we skip it and start right now ...

The nature of the problem

The Create extension method looks like this:

1 public static T-create<t> (2 this     expression<func<t, bool>> predicateexpr) 3 {4     var Initializ Erexpression = Predicatetoconstructorvisitor 5                                     . Convert<t> (predicateexpr); 6     var initializerfunction = Initializerexpression.compile (); 7     return Initializerfunction (); 8}

Predicatetoconstructorvisitor is a specific expressionvisitor that converts only one assertion expression to a memberinitexpression.

  1 public class Predicatetoconstructorvisitor 2 {3 public static expression<func<t>> Convert<t&gt   ;(    4 expression<func<t, bool>> predicate) 5 {6 Predicatetoconstructorvisitor visitor =   7 new Predicatetoconstructorvisitor (); 8 Return visitor.   visit<t> (predicate); 9} protected Expression<func<t>> visit<t> (expression<func<t, Bool>&gt ; predicate) {VISITLAMBDA (predicate as lambdaexpression) as EXPRESSION&LT;FUNC&L T  t>>; Protected virtual Expression Visitlambda (lambdaexpression Lambda) ( Lambda.  Body is binaryexpression) {+//Create A new instance expression i.e. NewExpression newexpr = expression.new (lambda. Parameters.single ().  Type); Binaryexpression binary = 26 Lambda.  Body as Binaryexpression; Expression.lambda Return (expression.memberinit wexpr, getmemberassignments (binary).  ToArray () 32) 33); The new InvalidOperationException (string. Format (PNS "Onlybinary Expressions is supported.\n\n{0}", "A. Lambda."  Body.tostring () 39) 40); Protected ienumerable<memberassignment> getmemberassignments (Binaryexpression bin ary) {if (binary).  NodeType = = expressiontype.equal), {yield return getmemberassignment (binary); (binary}. NodeType = = expressiontype.andalso) (var assignment in Getmembera Ssignments (binary. Left as Binaryexpression). Concat (getmemberassignments (binary).  Right as Binaryexpression))------yield return assignment; The "NotSupportedException" (binary}).  ToString ());         Protected memberassignment getmemberassignment (binaryexpression binary) 64 {65 if (binary. NodeType! = expressiontype.equal) The new InvalidOperationException (in.  ToString () 68); Memberexpression member = binary.  Left as Memberexpression; CONSTANTEXPRESSION constant = getconstantexpression (binary).  right); if (constant. Value = = null) constant = Expression.constant (null, member.  Type); Expression.bind return (member.  Member, constant);   Protected ConstantExpression Getconstantexpression (83)  {if (expr).  NodeType = = expressiontype.constant) (constantexpression) The other is the type = expr.  Type; if (type.  Isvaluetype) 94 expr = Expression.convert (expr, typeof (object)); expression<func<object>> Lambda 98 = expression.lambda&lt ;  Func<object>> (expr); func<object> fn = Lambda.compile (); 101 102 Return Expression.constant (FN (), type); 103} 104} 105}

The real work is done in the VISITLAMBDA.

Basically, if:

    1. This lambda expression is not a binaryexpression.
    2. A lambda expression has more than one parameter. We can only construct one!

This function throws an exception.

Then we begin to traverse binaryexpression until we get a node that is equal, such as (p.id = = 5), and we convert it to a member assignment statement (ID = 5) so that we can construct a memberinitexpression.

When creating a member assignment statement, we also convert all expressions on the right side of the equals sign to a constant. For example, if the lambda expression looks like this:

(person p) = = P.id = = GetID ();

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

Summary

Once again, I've demonstrated that mixing EF metadata with CLR expressions makes it possible to write help functions that are really useful, and that you have a lot less pain in writing your app.

Tip 35. How to make oftypeonly<tentity> () such a notation

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.