EF POCO Adapter updated (v1.03)


I’ve just posted another round of updates to EF POCO Adapter based on feedback from users. This set of changes is focused on N-tier scenarios and detached entities (it adds missing wrappers for EF APIs and fixes behavior of existing ones).

Change log:

  • Added wrapper for ObjectContext.ApplyPropertyChanges() to IEntitySet<T>
  • Added wrapper for ObjectContext.Detach() to IEntitySet<T>
  • Fixed handling of detached/out-of-scope entities w.r.t lazy loading and attaching to other contexts. Lazy loading outside of scope will do nothing if the context had DeferredLoadingEnabled=false, otherwise will throw.
  • Added partial OnZZZCreated() methods on generated contexts, adapters and proxies to enable integration with 3rd-party extensions
  • Fixed code generation from EDMX.
  • EFPocoContext.EnableLazyLoading has been renamed to EFPocoContext.DeferredLoadingEnabled to better align with Linq to SQL and our plans for EF v2
  • Fixed adapter-POCO synchronization for complex types

The new release is here: http://code.msdn.microsoft.com/EFPocoAdapter/Release/ProjectReleases.aspx?ReleaseId=1580

Big thanks to everyone who sent me comments, bug reports and improvement suggestions for this release. This is very valuable feedback as it allows us to better understand user scenarios around POCOs for EF v2.

Comments (12)

  1. crazydoctoral says:

    Was having trouble with the Generator, until I realised the filenames in the arguments are case sensitive. So classes.Dll failed to work but classes.dll did.

  2. CraigCav says:

    I’m having a little bit of trouble when trying to extend this sample with (what I would consider) a reasonable usage. The problem I encounter occurs after extracting an interface from a class in the model (say, Product : IProduct), and causes the TestMethod GenerateAndUsePocoAdapter() to fail.

    The error message given is:

    Test method NorthwindEF.Tests.AdapterGeneratorTests.GenerateAndUsePocoAdapter threw exception:  System.InvalidOperationException: Generated code failed to compile

    Errors (32):

    (2660,13): CS0506 ‘NorthwindEF.PocoProxies.ProductProxy.ProductID.get’: cannot override inherited member ‘NorthwindEF.Product.ProductID.get’ because it is not marked virtual, abstract, or override

    It also appears that the generated code will fail to compile if one of the model classes implement an interface that exists within another assembly – for example Customer : IAggregateRoot (where IAggregateRoot is an interface that exists in an assembly referenced from the NorthwindEF project).

  3. EntityClient Entity Framework != O/RM Sappiamo tutti che il buon vecchio pattern in un ADO.NET Data Provider

  4. jlddodger says:

    I am having trouble performing a certain type of query.  The goal of this query is to create an instance of a custom class that contains a member of the query result type (Customer) for each query result.  

    The most obvious query that worked before we began using the EF Poco Adapters is as follows:

    var b = (from r in db.CustomerSet

            where r.Name.Contains("AUTOMATED")

            orderby r.Name

            select new Result()

            {

               Customer = r

            });

    However, when enumerating the result of this query, I receive "Argument types do not match".

    Okay, so what is the actual object represented by "r"?

    {Database.PocoProxies.CustomerProxy}

    Okay, so what if I try to receive that object type directly?

    var d = (from r in db.CustomerSet

            where r.Name.Contains("AUTOMATED")

            orderby r.Name

            select new Result()

            {

               CustomerProxy = (CustomerProxy)r

            });

    This generates "No coercion operator is defined between types ‘Database.PocoAdapters.ResellerAdapter’ and ‘Database.PocoProxies.ResellerProxy’."

    Hmm… This is having trouble converting from a ResellerAdaptor, so what if I try to receive this type?… no, that can’t work…

    var f = (from r in db.ResellerSet

            where r.Name.Contains("AUTOMATED")

            orderby r.Name

            select new Result()

            {

               ResellerObject = (ResellerAdapter)(IPocoAdapter)r

            });

    Nope…  ‘Database.PocoAdapters.ResellerAdapter’ to type ‘EFPocoAdapter.DataClasses.IPocoAdapter’. LINQ to Entities only supports casting Entity Data Model primitive types.

    But that, at least, makes sense…

    Okay so here’s what makes me go from hmm… to grr…

    var a = (from r in db.CustomerSet

            where r.Name.Contains("AUTOMATED")

            orderby r.Name

            select r);

            Result res = new Result()

            {

               Customer = a.First(),

            };

    This works!  By performing the construction outside of the query expression, the types magically work… but then we lose nice features like Deferred Loading, etc… (is this correct?)

    But better yet…

    var a2 = (from r in db.CustomerSet

             where r.Name.Contains("AUTOMATED")

             orderby r.Name

             select new

             {

                Customer = r

             });

    The only difference between test (b)–the preferred method–and (a3) is that (a3) constructs an anonymous class instead of a defined one.  But, how then can I add functionality to that class if it is anonymous.  That is the whole point of constructing an object instead of meddling with the result directly.

    Just to take a stab in the dark.  I have witnesses ‘Argument types do not match’ coming from places where ValidateSameArgTypes fails…

    The stack at the exception is (I hope this formats well):

    System.Core.dll!System.Linq.Expressions.Expression.Bind(System.Reflection.MemberInfo member, System.Linq.Expressions.Expression expression) + 0xaa bytes

    EFPocoAdapter.dll!EFPocoAdapter.Internal.QueryTranslator.VisitMemberAssignment(System.Linq.Expressions.MemberAssignment assignment = {Reseller = r}) + 0x90 bytes

    EFPocoAdapter.dll!EFPocoAdapter.Internal.ExpressionVisitor.VisitBinding(System.Linq.Expressions.MemberBinding binding = {Reseller = r}) + 0x97 bytes

    EFPocoAdapter.dll!EFPocoAdapter.Internal.ExpressionVisitor.VisitBindingList(System.Collections.ObjectModel.ReadOnlyCollection<System.Linq.Expressions.MemberBinding> original = Count = 1) + 0x9d bytes

    EFPocoAdapter.dll!EFPocoAdapter.Internal.ExpressionVisitor.VisitMemberInit(System.Linq.Expressions.MemberInitExpression init = {new Result() {Reseller = r}}) + 0x91 bytes

    EFPocoAdapter.dll!EFPocoAdapter.Internal.ExpressionVisitor.Visit(System.Linq.Expressions.Expression exp = {new Result() {Reseller = r}}) + 0x3fc bytes

    EFPocoAdapter.dll!EFPocoAdapter.Internal.QueryTranslator.VisitLambda(System.Linq.Expressions.LambdaExpression lambda = {r => new Result() {Reseller = r}}) + 0x77 bytes

    EFPocoAdapter.dll!EFPocoAdapter.Internal.ExpressionVisitor.Visit(System.Linq.Expressions.Expression exp = {r => new Result() {Reseller = r}}) + 0x2e0 bytes

    EFPocoAdapter.dll!EFPocoAdapter.Internal.ExpressionVisitor.VisitUnary(System.Linq.Expressions.UnaryExpression u = {r => new Result() {Reseller = r}}) + 0x62 bytes

    EFPocoAdapter.dll!EFPocoAdapter.Internal.QueryTranslator.VisitUnary(System.Linq.Expressions.UnaryExpression u = {r => new Result() {Reseller = r}}) + 0x29c bytes

    EFPocoAdapter.dll!EFPocoAdapter.Internal.ExpressionVisitor.Visit(System.Linq.Expressions.Expression exp = {r => new Result() {Reseller = r}}) + 0xc0 bytes

    EFPocoAdapter.dll!EFPocoAdapter.Internal.QueryTranslator.VisitMethodCall.AnonymousMethod(System.Linq.Expressions.Expression c = {r => new Result() {Reseller = r}}) + 0x42 bytes

    System.Core.dll!System.Linq.Enumerable.WhereSelectEnumerableIterator<System.__Canon,System.__Canon>.MoveNext() + 0xb9 bytes

    System.Core.dll!System.Linq.Buffer<System.Linq.Expressions.Expression>.Buffer(System.Collections.Generic.IEnumerable<System.Linq.Expressions.Expression> source) + 0x1b3 bytes

    System.Core.dll!System.Linq.Enumerable.ToArray<System.Linq.Expressions.Expression>(System.Collections.Generic.IEnumerable<System.Linq.Expressions.Expression> source) + 0x52 bytes

    EFPocoAdapter.dll!EFPocoAdapter.Internal.QueryTranslator.VisitMethodCall(System.Linq.Expressions.MethodCallExpression m = {ResellerSet.Where(r => r.Name.Contains("AUTOMATED")).OrderBy(r => r.Name).Select(r => new Result() {Reseller = r})}) + 0x135 bytes

    EFPocoAdapter.dll!EFPocoAdapter.Internal.ExpressionVisitor.Visit(System.Linq.Expressions.Expression exp = {ResellerSet.Where(r => r.Name.Contains("AUTOMATED")).OrderBy(r => r.Name).Select(r => new Result() {Reseller = r})}) + 0x299 bytes

    EFPocoAdapter.dll!EFPocoAdapter.Internal.QueryTranslator.Translate<System.Collections.Generic.IEnumerable<Database.ResellerQuery.Result>>(System.Linq.Expressions.Expression pocoExpression = {ResellerSet.Where(r => r.Name.Contains("AUTOMATED")).OrderBy(r => r.Name).Select(r => new Result() {Reseller = r})}, out System.Func<EFPocoAdapter.EFPocoContext,object,System.Collections.Generic.IEnumerable<Database.ResellerQuery.Result>> adapterFunction = null) + 0x80 bytes

    EFPocoAdapter.dll!EFPocoAdapter.EFPocoQuery<Database.ResellerQuery.Result>.GetEnumerator() + 0xb0 bytes

    > OPS.exe!Database.CustomerByNameQuery.CustomerByNameQuery(Database.ResellerQuery.ParameterSource _parmsGetter = {Method = {Parameters Get_ResellerQueryParameters()}}) Line 513 + 0xa bytes C#

    — END STACK —

    Your adapter code has been able to benefit us so far, but I really cannot continue unless I can find a way to query in a way that will let me add functionality to the resulting items.  Any advice or insight will be helpful.

    p.s. I have dug into the query translator a bit and I am wondering if perhaps the VisitParameter function which calls TranslateType is partially responsible.  I wonder if the assignment would work if the parameter were not translated to an adapter when the parameter is for a member assignment.  What do you think?

    Thanks.

  5. jlddodger:

    I am aware of this problem. Unfortunately fixing that is much harder than it seems, but there may be worksarounds.

    As part of the query translation EFPocoAdapter must translate queries in terms of POCO objects to use Adapter objects and generate a function that will convert the results back to POCO world. Translating POCO types to Adapter types is easy (there is 1-1 mapping between them and Adapter has all required properties of a POCO). Translating anonymous types is also doable, because C# compiler conveniently generates them using generic types where all property types are type arguments. Translating non-generic, non-entity types which have references to POCO entities is very hard as it would require a new type to be generated.

    You may be able to work around the problem by switching to LINQ to objects before final projection (by calling AsEnumerable()):

    var b = (from r in db.CustomerSet

           where r.Name.Contains("AUTOMATED")

           orderby r.Name select r)

    .AsEnumerable()

    .Select(c=> new Result() { Customer = c });

    Let me know when it works for you.

    Jarek

  6. jlddodger says:

    Alright, that sounds reasonable for an explanation.  Just so that I am clear, calling AsEnumerable() will cross the Lazy Barrier and evaluate everything both on-the-client and eagerly…. is this correct?

    Proceeding from the assumption that that is correct, I was wondering if perhaps I could change the basic layout of the receiving class to function with the Member-Initializer-Assignment instead of changing to client-side evaluation.  For example, you had said that the reason anonymous types were possible is because the compiler instantiates a generic class where each parameter has a corresponding type to one of the generic class type parameters.

    First off, really awesome insight into C# for me.  Also, you said that it is easy to convert to an adapter type since it has all of the necessary parameters and the relationship to POCOs is one-to-one.

    Can I directly take advantage of these facts in any way?  Can I query for the adapter type? Or even declare my class as generic (hmm… can class type-parameters be used in member-initialization lists…) and construct it similar to an anonymous class type.

    If I am wrong about the performance change going to client-side evaluation, then just let me know.  I would love to find out that the easy answer is also the best.

  7. jlddodger says:

    Okay, just ignore the previous post.  I have determined through testing that deferred loading is not affected by crossing to client evaluation.  Also, because entire POCO objects are being requested (an entire Customer object), it makes no difference in network transfer amount whether the result object is constructed on the server- or client-side.  Technically, I believe it may be more efficient to construct the result object on the client-side.   Thank you for all your help.

  8. jimitndiaye says:

    Hi, I having a bit of problem. When trying to compile the generated PocoAdapters, I keep getting the following error: ‘SomeClrNamespace.PocoAdapters.EntityAdapter’ must be a non-abstract type with a public parameterless constructor in order to use it as parameter ‘TAdapterType’ in the generic type or method ‘EFPocoAdapter.DataClasses.PocoAdapterBase<TPocoClass>.DetectChanges<TAdapterType,TValue>(TValue, System.Data.Objects.DataClasses.EntityReference<TAdapterType>, string)’

    EntityAdapter and the Poco entity it targets, Entity are both abstract.

    Does DetectChanges absolutely need the ‘new’ constraint? Cause if does then the generated adapter cannot handle the case wherein an abstract class is the target of an EntityReference relationship. For instance in your sample NorthWindEF model, if Territory had a NavigationProperty back to the Employee, the generated Adapter wouldn’t work.

    Can you recommend a work around?

    Many Thanks,

    Jimit Ndiaye

  9. jimitndiaye says:

    Is there any intention to support function imports in the generated Poco container?

  10. wuwu1976 says:

    Hello!

    First of all, thanks for the great job done with the poco adapter…

    I have found a problem for which I really hope you will help me…

    So here it is:

    I have following poco class: Group with long Id (primary key), string Name

    My problem is, that when I get the group from 2 contexts, even though they are the same, the method groups.Contains(group) returns false, but it should return true.

    HashSet<Group> groups = new HashSet<Group>();

    using (MyEntities context1 = new MyEntities ()) {

     Group group = context1.Groups.First(g => g.Id == 3);

     groups.Add(group);

    }

    using (MyEntities context2 = new MyEntities ()) {

     Group group = context2.Groups.First(g => g.Id == 3);

     if (!groups.Contains(group)) {

       groups.Add(group);

     }

    }

    To make it work, I have override the GetHashCode method on the Group poco class:

    public override int GetHashCode() {

     //Group id is a primary key

     return Id.GetHashCode();

    }  

    So the code works now. But I get exceptions from the PocoAdapter when trying to detach the group, or when calling DetectChanges, SaveChanges, etc. The exception is "InvalidOperationException("Entity is already attached with another active context. Cannot reattach.")" and comes from your method GetAdapterObject(…). When it calls _adapterObjects.TryGetValue(entity, out value), the value returned is null.

    As far as I can understand, you add the entities to the _adapterObjects dictionary and it adds them before they are filled with values…

    Could you please help me out?  

    Thanks a lot in advance.

    Kind regards

    Marc Wuergler