pbristow
pbristow

Reputation: 2175

EF Core w/ Lazy Loading: NotImplementedException: 'This is a DynamicProxy2 error: The interceptor attempted to 'Proceed' for method

My simple line of code Db.CoverageParts.Add(newPart); is blowing the following exception from EF Core 5.0.6 / Castle 4.4.1 with Lazy Loading enabled:

System.NotImplementedException
  HResult=0x80004001
  Message=This is a DynamicProxy2 error: The interceptor attempted to 'Proceed' for method 'Microsoft.EntityFrameworkCore.Infrastructure.ILazyLoader get_LazyLoader()' which has no target. When calling method without target there is no implementation to 'proceed' to and it is the responsibility of the interceptor to mimic the implementation (set return value, out arguments etc)
  Source=Castle.Core
  StackTrace:
   at Castle.DynamicProxy.AbstractInvocation.ThrowOnNoTarget()
   at Castle.DynamicProxy.Internal.CompositionInvocation.EnsureValidTarget()
   at Castle.Proxies.Invocations.IProxyLazyLoader_get_LazyLoader.InvokeMethodOnTarget()
   at Castle.DynamicProxy.AbstractInvocation.Proceed()
   at Castle.DynamicProxy.StandardInterceptor.PerformProceed(IInvocation invocation)
   at Castle.DynamicProxy.StandardInterceptor.Intercept(IInvocation invocation)
   at Castle.DynamicProxy.AbstractInvocation.Proceed()
   at Castle.Proxies.MLCoveragePartProxy.get_LazyLoader()
   at Microsoft.EntityFrameworkCore.Metadata.Internal.ClrPropertyGetter`2.GetClrValue(Object entity)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetProperty(IPropertyBase propertyBase, Object value, Boolean isMaterialization, Boolean setModified, Boolean isCascadeDelete, CurrentValueType valueType)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetProperty(IPropertyBase propertyBase, Object value, Boolean isMaterialization, Boolean setModified, Boolean isCascadeDelete)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetServiceProperties(EntityState oldState, EntityState newState)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState oldState, EntityState newState, Boolean acceptChanges, Boolean modifyProperties)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState entityState, Boolean acceptChanges, Boolean modifyProperties, Nullable`1 forceStateWhenUnknownKey)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.PaintAction(EntityEntryGraphNode`1 node)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph[TState](EntityEntryGraphNode`1 node, Func`2 handleNode)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.AttachGraph(InternalEntityEntry rootEntry, EntityState targetState, EntityState storeGeneratedWithKeySetTargetState, Boolean forceStateWhenUnknownKey)
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.SetEntityState(InternalEntityEntry entry, EntityState entityState)
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.Add(TEntity entity)
   at --- SNIP THIS IS WHERE MY CODE APPEARS IN THE CALL STACK ---

This is a large project in the process of being upgraded from .NET Framework 4.8 to .NET Core 5. Lazy loading is enabled in EF Core. So far 97% of the code in this project is upgraded, tested, and verified working without any issues.

I am working to reproduce this issue with simplified code, but there is a lot to unwind to produce this issue. I've so far verified this issue occurs in a simple single-threaded version of my code with almost no use of async/await. Similar code throughout the app works without problems.

All libraries have been checked to be the latest RTM versions of .NET, EF Core, and Castle (though the latter is just a dependency of EF Core Proxies).

Can anyone offer any pointers that might give me an avenue of attack?

Upvotes: 5

Views: 2523

Answers (2)

Amjad Abujamous
Amjad Abujamous

Reputation: 920

In my case, I forgot to tell EF Core that there is a new relationship in place and yet added the foreign key and the virtual entity to the business logic. So when it tried to pull the entity based on the foreign key, the lazy loader failed and gave the same error.

I had an organization and a createdBy relationship, where the creator is the ApplicationUser. This is what was added:

protected override void OnModelCreating(ModelBuilder builder)
{
      builder.Entity<Organization>()
          .HasOne(o => o.CreatedBy)
          .WithMany()
          .HasForeignKey("CreatedById")
          .IsRequired(false)
          .OnDelete(DeleteBehavior.Restrict);
}

Organization entity below:

public class Organization: Entity
{
    public string Name { get; set; }
    public string CommercialRegistration { get; set; }
    public DateTime ContractStartDate { get; set; }
    public DateTime ContractEndDate { get; set; }
    // New "CreatedBy" relationship
    public int? CreatedById { get; set; }
    public virtual ApplicationUser CreatedBy { get; set; }
}

After that, AutoMapper was able to use the lazy loader to map the user's properties back.

Upvotes: 0

pbristow
pbristow

Reputation: 2175

It dawned on me that it made no sense that anything would try, much less fail, to access the LazyLoader property on an entity that was just created. Naturally it's not yet bound to a context and the LazyLoader should be null, right??

In fact the real issue is that the new type is a dynamic proxy, not the concrete POCO. The problem stems from how the new entity is created. Because it is a derived type cloned from an instance of an existing entity of the same type, my code does the following:

var newCoveragePart = (CoveragePart)Activator.CreateInstance(oldCoveragePart.GetType());

The problem of course is that this clones a proxy, not the base type, which I assume throws big monkey wrenches in everything. This practice worked fine in EF for .NET Framework, so it's a bit shocking to realize it doesn't work in EF Core. Here's what's needed to fix the issue:

var cpType = (oldCoveragePart is IProxyLazyLoader ? oldCoveragePart.GetType().BaseType : oldCoveragePart.GetType()); 
var newCoveragePart = (CoveragePart)Activator.CreateInstance(cpType);

Side note: I also shallow-clone entities with AutoMapper and add them to the context after doing so. I'm going to have to review that code to make sure it doesn't have the same or similar problem.

Upvotes: 2

Related Questions