zzzprojects / EntityFramework.DynamicFilters

Global filtering for Entity Framework.
https://entityframework-dynamicfilters.net/
MIT License
501 stars 87 forks source link

EntityCommandCompilationException when using .Include() on class with subclass #159

Open jehhynes opened 4 years ago

jehhynes commented 4 years ago

Description

When using DbSet.Include(x => x.Relationship) where Relationship is a type which has a subclass, we receive a EntityCommandCompilationException.

I've included the full exception below, and a test case which will run in the DynamicFiltersTests project (and fail with the following exception). This is using the latest codebase on master.

Exception

 Message: 
    Test method DynamicFiltersTests.NavigationTests.FilterIncludesWithSubclass threw exception: 
    System.Data.Entity.Core.EntityCommandCompilationException: An error occurred while preparing the command definition. See the inner exception for details. ---> System.ArgumentException: The specified navigation requires a navigation source of a type that is compatible with 'Transient.reference[DynamicFiltersTests.Employee]'.
    Parameter name: from
  Stack Trace: 
    at ArgumentValidation.RequireCompatibleType(DbExpression from, RelationshipEndMember end, Boolean allowAllRelationshipsInSameTypeHierarchy)
    at ArgumentValidation.ValidateNavigate(DbExpression navigateFrom, RelationshipEndMember fromEnd, RelationshipEndMember toEnd, RelationshipType& relType, Boolean allowAllRelationshipsInSameTypeHierarchy)
    at DbExpressionBuilder.Navigate(DbExpression navigateFrom, RelationshipEndMember fromEnd, RelationshipEndMember toEnd)
    at DefaultExpressionVisitor.Visit(DbRelationshipNavigationExpression expression)
    at DbRelationshipNavigationExpression.Accept[TResultType](DbExpressionVisitor`1 visitor)
    at DefaultExpressionVisitor.VisitExpression(DbExpression expression)
    at DefaultExpressionVisitor.VisitList[TElement](IList`1 list, Func`2 map)
    at DefaultExpressionVisitor.VisitExpressionList(IList`1 list)
    at DefaultExpressionVisitor.Visit(DbNewInstanceExpression expression)
    at DynamicFilterQueryVisitorCSpace.Visit(DbNewInstanceExpression expression) in DynamicFilterQueryVisitorCSpace.cs line: 396
    at DbNewInstanceExpression.Accept[TResultType](DbExpressionVisitor`1 visitor)
    at DefaultExpressionVisitor.VisitExpression(DbExpression expression)
    at DefaultExpressionVisitor.VisitList[TElement](IList`1 list, Func`2 map)
    at DefaultExpressionVisitor.VisitExpressionList(IList`1 list)
    at DefaultExpressionVisitor.Visit(DbNewInstanceExpression expression)
    at DynamicFilterQueryVisitorCSpace.Visit(DbNewInstanceExpression expression) in DynamicFilterQueryVisitorCSpace.cs line: 396
    at DbNewInstanceExpression.Accept[TResultType](DbExpressionVisitor`1 visitor)
    at DefaultExpressionVisitor.VisitExpression(DbExpression expression)
    at DefaultExpressionVisitor.Visit(DbProjectExpression expression)
    at DynamicFilterQueryVisitorCSpace.Visit(DbProjectExpression expression) in DynamicFilterQueryVisitorCSpace.cs line: 418
    at DbProjectExpression.Accept[TResultType](DbExpressionVisitor`1 visitor)
    at DynamicFilterInterceptor.TreeCreated(DbCommandTreeInterceptionContext interceptionContext) in DynamicFilterInterceptor.cs line: 43
    at DbCommandTreeDispatcher.<Created>b__0(IDbCommandTreeInterceptor i, DbCommandTreeInterceptionContext c)
    at InternalDispatcher`1.Dispatch[TInterceptionContext,TResult](TResult result, TInterceptionContext interceptionContext, Action`2 intercept)
    at DbCommandTreeDispatcher.Created(DbCommandTree commandTree, DbInterceptionContext interceptionContext)
    at DbProviderServices.CreateCommandDefinition(DbCommandTree commandTree, DbInterceptionContext interceptionContext)
    at ObjectQueryExecutionPlanFactory.CreateCommandDefinition(ObjectContext context, DbQueryCommandTree tree)
    at  --- End of inner exception stack trace ---
    at ObjectQueryExecutionPlanFactory.CreateCommandDefinition(ObjectContext context, DbQueryCommandTree tree)
    at ObjectQueryExecutionPlanFactory.Prepare(ObjectContext context, DbQueryCommandTree tree, Type elementType, MergeOption mergeOption, Boolean streaming, Span span, IEnumerable`1 compiledQueryParameters, AliasGenerator aliasGenerator)
    at EntitySqlQueryState.GetExecutionPlan(Nullable`1 forMergeOption)
    at <>c__DisplayClass7.<GetResults>b__6()
    at ObjectContext.ExecuteInTransaction[T](Func`1 func, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction, Boolean releaseConnectionOnSuccess)
    at <>c__DisplayClass7.<GetResults>b__5()
    at DefaultSqlExecutionStrategy.Execute[TResult](Func`1 operation)
    at ObjectQuery`1.GetResults(Nullable`1 forMergeOption)
    at IEnumerable<T>.GetEnumerator>b__0()
    at LazyEnumerator`1.MoveNext()
    at List`1.ctor(IEnumerable`1 collection)
    at Enumerable.ToList[TSource](IEnumerable`1 source)
    at NavigationTests.FilterIncludesWithSubclass() in NavigationTests.cs line: 21
Exception message:
Stack trace:

Fiddle or Project

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
using EntityFramework.DynamicFilters;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace DynamicFiltersTests
{
    [TestClass]
    public class NavigationTests
    {

        [TestMethod]
        public void FilterIncludesWithSubclass()
        {
            using (var context = new TestContext())
            {
                var list = context.Entity1s
                    .Include(x => x.User)
                    .ToList();
            }
        }

        #region Models

        public class Depot : PersistentObject, IFilterTenant
        {
            public virtual Tenant Tenant { get; set; }
        }

        public class Employee : UserAccount
        {
            public virtual Depot Depot { get; set; }
        }

        public class UserAccount : PersistentObject, IFilterTenant
        {
            public virtual Tenant Tenant { get; set; }
        }

        public class Entity1 : PersistentObject, IFilterTenant
        {
            [ForeignKey("Id")]
            public virtual UserAccount User { get; set; }
            public virtual Tenant Tenant { get; set; }
        }

        public class Tenant : PersistentObject
        {

        }

        public interface IPersistentObject
        {
            long Id { get; set; }
        }

        public abstract class PersistentObject : IPersistentObject
        {
            [Key]
            public virtual long Id { get; set; }
        }

        #endregion

        #region TestContext

        public class TestContext : TestContextBase<TestContext>, ITestContext
        {
            public long? FilterTenantId { get; set; }

            public DbSet<Tenant> Tenants { get; set; }
            public DbSet<UserAccount> UserAccounts { get; set; }
            public DbSet<Depot> Depots { get; set; }
            public DbSet<Entity1> Entity1s { get; set; }

            public override void Seed()
            {

            }

            protected override void OnModelCreating(DbModelBuilder modelBuilder)
            {
                base.OnModelCreating(modelBuilder);

                modelBuilder.Filter<IFilterTenant, TestContext, long?>("TenantFilter", (o, id) => o.Tenant.Id == id, ctx => ctx.FilterTenantId);
            }
        }

        public interface IHasTenant
        {
            Tenant Tenant { get; set; }
        }
        public interface IFilterTenant : IHasTenant { }

        #endregion
    }
}

Further technical details

JonathanMagnan commented 4 years ago

Hello @jehhynes ,

Thank you for reporting, we will look at it if that's possible to support this.

Best Regards,

Jonathan


Performance Libraries context.BulkInsert(list, options => options.BatchSize = 1000); Entity Framework ExtensionsEntity Framework ClassicBulk OperationsDapper Plus

Runtime Evaluation Eval.Execute("x + y", new {x = 1, y = 2}); // return 3 C# Eval FunctionSQL Eval Function

JonathanMagnan commented 4 years ago

Hello @jehhynes ,

My developer and me tried to find a fix but unfortunately, both of us failed without any progression.

So we choose to leave the issue open but stop to work on it until we get another idea to try or another issue that gives us some hint that might lead to a resolution.