Home Dashboard Directory Help
Search

Entity Framework 4.1: Using LazyLoading with NoTracking option causes InvalidOperationException by Lukas F


Status: 

Closed
 as Deferred Help for as Deferred


10
0
Sign in
to vote
Type: Bug
ID: 679399
Opened: 7/14/2011 5:00:15 AM
Access Restriction: Public
Moderator Decision: Sent to Engineering Team for consideration
4
Workaround(s)
view
5
User(s) can reproduce this bug

Description

I am using Entitiy Framework 4.1 with DbContext API. For performance reasons I want to work with NoTracking entities so I load the data using .AsNoTracking. Still I want LazyLoading enabled, which works pretty well for almost all cases. Also the documentation sais that LazyLoading works with NoTracking entities.

Now I have a M:N reference between two entities A and B, with collection references on both sides. I load the first entity AsNoTracking and navigate from the first entity to the second entity via the collection reference. Here LazyLoading works. Then on the second entity I try to access the collection of first entity type, and I get this Exception:

InvalidOperationException: When an object is returned with a NoTracking merge option, Load can only be called when the EntityCollection or EntityReference does not contain objects.

StackTrace:
at System.Data.Objects.DataClasses.RelatedEnd.ValidateLoad[TEntity](MergeOption mergeOption, String relatedEndName, Boolean& hasResults)
at System.Data.Objects.DataClasses.EntityCollection`1.Load(List`1 collection, MergeOption mergeOption)
at System.Data.Objects.DataClasses.EntityCollection`1.Load(MergeOption mergeOption)
at System.Data.Objects.DataClasses.RelatedEnd.Load()
at System.Data.Objects.DataClasses.RelatedEnd.DeferredLoad()
at System.Data.Objects.Internal.LazyLoadBehavior.LoadProperty[TItem](TItem propertyValue, String relationshipName, String targetRoleName, Boolean mustBeNull, Object wrapperObject)
at System.Data.Objects.Internal.LazyLoadBehavior.<>c__DisplayClass7`2.<GetInterceptorDelegate>b__1(TProxy proxy, TItem item)
at System.Data.Entity.DynamicProxies.Album_FD0E8EBDF5D687411DC1AB46983F0ED65C6A0FD99D362C7AAE39C532CFEB3FAE.get_AudioTags()
at Xperience.MainViewModel..ctor(Entities entities)

When I look at my object in debugger, I can see that the collection is already filled, even before the collection property is accessed! Probably it gets filled automatically while LazyLoading the second entity. But upon accessing the property from my code, the framework thinks this is the first access, tries to load the property and fails.

When I load the entities without AsNoTracking, everything works as expected. See an example in Steps to reproduce.
Details
Sign in to post a comment.
Posted by bazzinga on 8/17/2013 at 4:13 PM
Since the work item discusses if users are actually hitting this issue, I'd suggest that everyone having to deal with this exception up-votes this Connect item as well as the work item on Codeplex:

https://entityframework.codeplex.com/workitem/288

This exception apparently easily occurs while having an M:N relationship in the data model and using the NoTracking option. In my opinion this combination is very common.
Posted by Microsoft on 10/23/2012 at 1:30 PM
Hello,
Thank you for taking the time to provide feedback. We addressed a lot of connect suggestions in our last release, but we weren’t able to address them all. We have copied this issue to our backlog and will consider it for a future release. You can view this suggestion on our public backlog – http://entityframework.codeplex.com/workitem/list/basic?keywords=DevDiv%20[Id=426870].
~Entity Framework Team
Posted by Microsoft on 5/25/2012 at 8:50 AM
Thanks for reporting this issue. The problem with allowing lazy loading to happen in this case is that doing so would almost certainly result in duplicate entities in the collection since there is no identity resolution for no tracking queries. (No identity resolution means that there is no mechanism to check whether or not an entity is already contained in the collection.) This is something we will look at improving in a future version of EF but it is not something that will be changed for EF5/.NET 4.5.

Workarounds for now are to use tracking queries or use the Include method to eagerly load the required collections.

You can also try switching off lazy loading while accessing the collection if it is known to already contain the entities you are interested in, or removing existing entities from the collection (with lazy loading turned off or in a way which won't trigger lazy loading) before then accessing the property to cause loading to happen. However, note that doing this may also result in duplicates.

Thanks,
EF Team
Posted by MS-Moderator07 [Feedback Moderator] on 5/22/2012 at 6:37 PM
Thanks for your feedback.

We are rerouting this issue to the appropriate group within the Visual Studio Product Team for triage and resolution. These specialized experts will follow-up with your issue.

Posted by jakmas on 9/20/2011 at 7:56 AM
Ok, I tried several things.

First, I am using T4 to generate my objects, DAL repository, Business layer, etc.

I have a method accessing a small table, we will call Header. There is a single method to access these records that I am currently using (this is also a fluent coding access method)
Services.Get<Facade>().GetAllHeadersByChildId(childId).FirstOrDefault();

The first execution of this function works, actually it is called in a ListView where a custom control is getting this value multiple times (10 for the first page).

I then have some ajax that loads the rest of it, via the same methods. I put in a breakpoint at my DAL repository, again same parent method:
Services.Get<Facade>().GetAllHeadersByChildId(childId).FirstOrDefault();

Inside of the DAL it executes ultimately the same query:

public static readonly Func<DataContext, int, IQueryable<Header>> GetHeadersByChildId =
                Compile<int, Header>((tt, id) =>
                    from t in tt.Headerss
                    where t.ChildId == id
                    select t);

As I step through, I get results from the database, as expected, in this instance I only get one, however it could be more, I have examples where there are multiple values as well.

But I step through on the second revolution and I get the following ultimately:

I can see the that this query gets values from my Repository:
var result = _database.Query(CompiledQueries.HeaderQueries.GetHeadersByChildId, ChildId).ToList();

Values are returned. Transaction Scope is then defined as Complete();
It exits the DAL function, my public List<Header> name(int childId){} without issue.

Returns back to my Facade, which is just calling the repository that had already been registered for lazy get earlier in my Business.Facade bootstrap method.

Exits that just fine as well, with results still in tact as expected.
It goes into the primitives, for instance:
/// <summary>
        /// No Metadata Documentation available.
        /// </summary>
        [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
        [DataMemberAttribute()]
        public Nullable<global::System.Int32> ChildId
        {
            get
            {
                return _ChildId;
            }
            set
            {
                OnChildIdChanging(value);
                ReportPropertyChanging("ChildId");
                _ChildId = StructuralObject.SetValidValue(value);
                ReportPropertyChanged("ChildId");
                OnChildIdChanged();
            }
        }
        private Nullable<global::System.Int32> _ChildId;
        partial void OnChildIdChanging(Nullable<global::System.Int32> value);
        partial void OnChildIdChanged();


Somewhere in there, when my Header table navigates back to a parent table, and cycles through the primitives, it throws this error.

Here is the stack trace I was able to isolate:
I found this (it is non-user code, so the debugger auto steps over it)

I have the table that has ChildId as the parent, however when I access it in the navigation inside of the DataContext.Designer.cs and go back to the original table Header, select the Count property (which there is a Header record) the base is listed as:

Message: When an object is returned with a NoTracking merge option, Load can only be called when the EntityCollection or EntityReference does not contain objects.

Inner Exception: null

Source: System.Data.Entity

Stack Trace:
at System.Data.Objects.DataClasses.RelatedEnd.ValidateLoad[TEntity](MergeOption mergeOption, String relatedEndName, Boolean& hasResults)
at System.Data.Objects.DataClasses.EntityCollection`1.Load(List`1 collection, MergeOption mergeOption)
at System.Data.Objects.DataClasses.EntityCollection`1.Load(MergeOption mergeOption)
at System.Data.Objects.DataClasses.RelatedEnd.Load()
at System.Data.Objects.DataClasses.RelatedEnd.DeferredLoad()
at System.Data.Objects.DataClasses.EntityCollection`1.get_Count()

Target Site:

{System.Data.Objects.ObjectQuery`1[TEntity] ValidateLoad[TEntity](System.Data.Objects.MergeOption, System.String, Boolean ByRef)}    System.Reflection.MethodBase {System.Reflection.RuntimeMethodInfo}

Target Site.Base: -        base    {System.Data.Objects.ObjectQuery`1[TEntity] ValidateLoad[TEntity](System.Data.Objects.MergeOption, System.String, Boolean ByRef)}    System.Reflection.MemberInfo {System.Reflection.RuntimeMethodInfo}


Target Site.base.memberType = method
Target Site.base.DeclaringType = +        DeclaringType    {Name = "RelatedEnd" FullName = "System.Data.Objects.DataClasses.RelatedEnd"}    System.Type {System.RuntimeType}
Posted by jakmas on 9/20/2011 at 5:29 AM
I am working on a demo. I have this problem, and am testing some hypotheses’ right now. I would like this issue re-opened if at all possible and I can provide any information that you need.
Posted by Ken L. Cooley on 9/7/2011 at 9:01 AM
The same error happened to me when I tried to do a Lazy Loading navigation from one entity to one of it's foreign keyed entities, and then back to the first entity. I had enabled the NoTracking MergeOption on the context collection (for performance). The workaround was to do a plain context key query (e.g. c => c.Id = entity.Id) on the 2nd entity instead of relying on Lazy Loading.
Posted by Tweety18 on 8/18/2011 at 7:01 AM
This is marked as resolved. How was this resolved and when it will be available?
Thanks
Posted by MS-Moderator07 [Feedback Moderator] on 7/25/2011 at 2:09 AM
Hi, given that we have not heard back from you in 7 days. We will go ahead and close this Connect Issue. If you get a chance to review and provide the information requested earlier, you can go ahead and reactivate this issue.
Posted by MS-Moderator07 on 7/17/2011 at 10:30 PM
I am currently standing by for an update from you and would like to know how things are going on your end. If you could get back to me at your earliest convenience with information I request, we will be able to make headway towards a resolution. I look forward to hearing from you.
Posted by MS-Moderator07 on 7/14/2011 at 7:02 PM
Thank you for submitting feedback on Visual Studio 2010 and .NET Framework. In order to efficiently investigate and reproduce this issue, we are requesting additional information outlined below.

Could you please give us a demo project to demonstrate this issue so that we can conduct further research?

Please submit this information to us within 4 business days. We look forward to hearing from you with this information.

Microsoft Visual Studio Connect Support Team
Posted by MS-Moderator01 on 7/14/2011 at 5:50 AM
Thank you for your feedback, we are currently reviewing the issue you have submitted. If this issue is urgent, please contact support directly(http://support.microsoft.com)
Sign in to post a workaround.
Posted by bazzinga on 8/17/2013 at 4:22 PM
The EF Team mentions the following workarounds (just citing from the comments section):

"Workarounds for now are to use tracking queries or use the Include method to eagerly load the required collections.

You can also try switching off lazy loading while accessing the collection if it is known to already contain the entities you are interested in, or removing existing entities from the collection (with lazy loading turned off or in a way which won't trigger lazy loading) before then accessing the property to cause loading to happen. However, note that doing this may also result in duplicates" -- EF Team
Posted by Brad Wood on 8/9/2013 at 10:22 AM
I'm experiencing this bug when adding an entity to an entity collection that contains previously loaded (but detached) entities. Once I add a new entity, the collection can no longer be accessed. The workaround is to call Count() on the collection before adding any additional.
Posted by jakmas on 9/20/2011 at 8:08 AM
Remove FK reference to the offending object. No other objects in the database were affected. Lazy Loading was never employed for this, as you can see by my steps to reproduce. I was calling the table directly "GetHeadersByChildId(childId)" rather than following the reference trail. Once I removed the FK, all worked.

As I said, no other tables or FK relationships were effected.
Posted by Ken L. Cooley on 9/7/2011 at 9:01 AM
The same error happened to me when I tried to do a Lazy Loading navigation from one entity to one of it's foreign keyed entities, and then back to the first entity. I had enabled the NoTracking MergeOption on the context collection (for performance). The workaround was to do a plain context key query (e.g. c => c.Id = entity.Id) on the 2nd entity instead of relying on Lazy Loading.