EF Core, table-per-hierarchy and referencing entity of the same base class generates error

795 Views Asked by At

We have contacts are stored via table-per-hierarchy, A contact is either a company or a person and a person always belongs to a company. Both inherit from contact.

EF Core 2.1 is used.

It looks like this

public abstract class Contact {
    public virtual Guid Id { get; set; }
    public virtual ICollection<Source> Sources{ set; get; } = new Collection<Source>();
}

public class Company : Contact {
    public string CompanyName { set; get; }
    public virtual ICollection<Person> People { set; get; } = new Collection<Person>();
}

public class Person: Contact {
    public string Name { set; get; }
    public DateTime Birthday { set; get; }
    public virtual Company Company { set; get; }
}

So far so good, what we now want to do is query the Sources and include the contacts (all of them, doesn't matter if person or company)

context.Contact.Include(c => c.Contact).FirstOrDefault();

This generates the following exception

Unable to cast object of type 'System.DateTime' to type 'System.Nullable``1[System.Guid]'.'

StackTrace

 at lambda_method(Closure , ValueBuffer )
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalMixedEntityEntry..ctor(IStateManager stateManager, IEntityType entityType, Object entity, ValueBuffer& valueBuffer)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntryFactory.NewInternalEntityEntry(IStateManager stateManager, IEntityType entityType, Object entity, ValueBuffer& valueBuffer)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StartTrackingFromQuery(IEntityType baseEntityType, Object entity, ValueBuffer& valueBuffer, ISet`1 handledForeignKeys)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryBuffer.StartTracking(Object entity, IEntityType entityType)
   at lambda_method(Closure , QueryContext , AdSourceCrm , Object[] )
   at Microsoft.EntityFrameworkCore.Query.Internal.IncludeCompiler._Include[TEntity](QueryContext queryContext, TEntity entity, Object[] included, Action`3 fixup)
   at lambda_method(Closure , TransparentIdentifier`2 )
   at System.Linq.Enumerable.SelectEnumerableIterator`2.MoveNext()
   at System.Linq.Enumerable.TryGetFirst[TSource](IEnumerable`1 source, Boolean& found)
   at lambda_method(Closure )
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.ResultEnumerable`1.GetEnumerator()
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider._TrackEntities[TOut,TIn](IEnumerable`1 results, QueryContext queryContext, IList`1 entityTrackingInfos, IList`1 entityAccessors)+MoveNext()
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.MoveNext()
   at System.Linq.Enumerable.TryGetFirst[TSource](IEnumerable`1 source, Boolean& found)
   at System.Linq.Enumerable.First[TSource](IEnumerable`1 source)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass15_1`1.<CompileQueryCore>b__0(QueryContext qc)
   at System.Linq.Queryable.FirstOrDefault[TSource](IQueryable`1 source)
   at EfTest.Program.Main(String[] args) in Program.cs:line 18

We already spent hours (more like days) trying to get to the root of the cause and a fix but to no avail.

EF Core somehow trips with our construct (maybe the construct itself is wrong)

If you disable LazyLoading the problem disappears.

Any idea what's the cause and what would fix this issue?

EDIT 2 I remove Sources since they don't seem to be involved in the problem

EDIT 3 I added a sample repo

Just change the ConnectionString in DbContextFactory to your local path.

2

There are 2 best solutions below

0
On BEST ANSWER

I was able to reproduce it with the provided repo in both EF Core 2.1.4 and 2.2 preview.

As you mentioned in the last update, the problem is somehow related to lazy loading (proxies?), because w/o UseLazyLoadingProxies() the code works as expected (that's why I wasn't able to reproduce it initially).

Since this is apparently EF Core bug, there is nothing you can do than reporting it to the EF Core Issue Tracker and wait to be fixed. Unfortunately mist likely you won't get that included in the upcoming 2.2 release, but who knows, it's worth trying.

1
On

Seems to me like a bug in Entity Framework, you can however prevent this from happening by explicitly specifying the auto-generated properties, for example add the CompanyId property to the Person class explicitly

That will prevent the faulty mapping of the Birthday column to the CompanyId column done by EF Core

public class Person : Base
{
    // ...

    public virtual Company Company { set; get; }    
    public Guid? CompanyId { get; set; }
}