zzzprojects / EntityFramework-Plus

Entity Framework Plus extends your DbContext with must-haves features: Include Filter, Auditing, Caching, Query Future, Batch Delete, Batch Update, and more
https://entityframework-plus.net/
MIT License
2.25k stars 318 forks source link

Failed to query by interface or inheritance #139

Open wonea opened 7 years ago

wonea commented 7 years ago

I have the following entity which inherits from an abstract base class (which in turn has an interface and inherited base), and implements an interface.

public class TestProcess : InheritedTestClass, IProcess, IStuff
{
   // properties
}

However the following fail;

var ctx = new CoreContext();

ctx.Filter<IProcess>(q => q.Where(x => x.Title == ""));

and

ctx.Filter<InheritedTestClass>(q => q.Where(x => x.Title == ""));

With this error;

((System.ArgumentException)ex).Message

Object of type 'Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[MyEntity.Process.IProcess]' cannot be converted to type 'Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[MyEntity.Process.TestProcess]'.

The following works (just to test);

ctx.Filter<TestProcess>(q => q.Where(x => x.Title == ""));

I'm using these references;

<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.6' ">
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.1" />
    <PackageReference Include="Z.EntityFramework.Plus.QueryFilter.EFCore" Version="1.4.21" />
    <PackageReference Include="Z.EntityFramework.Plus.QueryFuture.EFCore" Version="1.4.29" />
</ItemGroup>
JonathanMagnan commented 7 years ago

Hello @wonea ,

This is currently due to some limitation of EF Core not handling cast yet correctly in LINQ.

See the limitation section:

Entity Framework Core:

There is currently a workaround with enabling ForceCast ( I will add this section to the Wiki) See the comment here: https://github.com/zzzprojects/EntityFramework-Plus/issues/74#issuecomment-258886038

JonathanMagnan commented 7 years ago

We added the text to the Wiki : Entity Framework Core - Limitations

Let me know if everything is clear.

Best Regards,

Jonathan

wonea commented 7 years ago

Thanks for your reply, and wiki update. That work around sadly doesn't work for me, either inheritance or interface. Guess I'll have to wait until EF Core 1.2

JonathanMagnan commented 7 years ago

Hello @wonea ,

I did a test with your scenario + ForceCast, and it seems to work well.

Here is the full code used, let me know if that helped

Code:

using System.Data.SqlClient;
using System.Linq;
using System.Windows.Forms;
using Microsoft.EntityFrameworkCore;
using Z.Test.EntityFramework.Plus;

namespace Z.EntityFramework.Plus.Lab.EFCore
{
    public partial class Form_Request_QueryFilter_WithInheritance : Form
    {
        public Form_Request_QueryFilter_WithInheritance()
        {
            InitializeComponent();

            // CLEAR
            using (var ctx = new CurrentContext())
            {
                ctx.TestProcesss.RemoveRange(ctx.TestProcesss);
                ctx.SaveChanges();
            }

            // SEED
            using (var ctx = new CurrentContext())
            {
                ctx.TestProcesss.Add(new TestProcess() {Title = ""});
                ctx.TestProcesss.Add(new TestProcess() {Title = "A"});
                ctx.TestProcesss.Add(new TestProcess() {Title = "B"});
                ctx.TestProcesss.Add(new TestProcess() {Title = ""});
                ctx.TestProcesss.Add(new TestProcess() {Title = "C"});

                ctx.SaveChanges();
            }

            // TEST
            QueryFilterManager.ForceCast = true;

            using (var ctx = new CurrentContext())
            {
                ctx.Filter<IProcess>(q => q.Where(x => x.Title == ""));

                var testProcess = ctx.TestProcesss.ToList();
            }
        }

        public class CurrentContext : DbContext
        {
            public CurrentContext()
            {
                Database.EnsureCreated();
            }

            public DbSet<TestProcess> TestProcesss { get; set; }

            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                optionsBuilder.UseSqlServer(new SqlConnection(My.Config.ConnectionStrings.TestDatabase));

                base.OnConfiguring(optionsBuilder);
            }

            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                //foreach (var entity in modelBuilder.Model.GetEntityTypes())
                //{
                //    entity.Relational().TableName = GetType().DeclaringType.FullName.Replace(".", "_") + "_" + entity.DisplayName();
                //}

                base.OnModelCreating(modelBuilder);
            }
        }

        public interface InheritedTestClass
        {
        }

        public interface IProcess
        {
            string Title { get; set; }
        }

        public interface IStuff
        {
        }

        public class TestProcess : InheritedTestClass, IProcess, IStuff
        {
            public int ID { get; set; }

            public string Title { get; set; }
        }
    }
}

Best Regards,

Jonathan

wonea commented 7 years ago

Forgot to mention, I'm using PostgreSQL <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="1.1.0" />

A little different

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    if (!optionsBuilder.IsConfigured)
    {
        optionsBuilder.UseNpgsql(ConnectionString);
    }
}
JonathanMagnan commented 7 years ago

Hello @wonea ,

Did you try it with my example? I just did the test with PostgreSQL and everything is still working.

using System.ComponentModel.DataAnnotations.Schema;
using System.Configuration;
using System.Linq;
using System.Windows.Forms;
using Microsoft.EntityFrameworkCore;
using Npgsql;

namespace Z.EntityFramework.Plus.Lab.EFCore.PostgreSQL
{
    public partial class Form_Request_QueryFilter_WithInheritance : Form
    {
        public Form_Request_QueryFilter_WithInheritance()
        {
            InitializeComponent();
            // CLEAR
            using (var ctx = new CurrentContext())
            {
                ctx.TestProcesss.RemoveRange(ctx.TestProcesss);
                ctx.SaveChanges();
            }

            // SEED
            using (var ctx = new CurrentContext())
            {
                ctx.TestProcesss.Add(new TestProcess {Title = ""});
                ctx.TestProcesss.Add(new TestProcess {Title = "A"});
                ctx.TestProcesss.Add(new TestProcess {Title = "B"});
                ctx.TestProcesss.Add(new TestProcess {Title = ""});
                ctx.TestProcesss.Add(new TestProcess {Title = "C"});

                ctx.SaveChanges();
            }

            // TEST
            QueryFilterManager.ForceCast = true;

            using (var ctx = new CurrentContext())
            {
                ctx.Filter<IProcess>(q => q.Where(x => x.Title == ""));

                var testProcess = ctx.TestProcesss.ToList();
            }
        }

        public class CurrentContext : DbContext
        {
            public CurrentContext()
            {
                Database.EnsureCreated();
            }

            public DbSet<TestProcess> TestProcesss { get; set; }

            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                optionsBuilder.UseNpgsql(new NpgsqlConnection(ConfigurationManager.ConnectionStrings["CodeFirstEntities"].ConnectionString));

                base.OnConfiguring(optionsBuilder);
            }

            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                //foreach (var entity in modelBuilder.Model.GetEntityTypes())
                //{
                //    entity.Relational().TableName = GetType().DeclaringType.FullName.Replace(".", "_") + "_" + entity.DisplayName();
                //}

                base.OnModelCreating(modelBuilder);
            }
        }

        public interface InheritedTestClass
        {
        }

        public interface IProcess
        {
            string Title { get; set; }
        }

        public interface IStuff
        {
        }

        [Table("TestProcesss", Schema = "dbo")]
        public class TestProcess : InheritedTestClass, IProcess, IStuff
        {
            public int ID { get; set; }

            public string Title { get; set; }
        }
    }
}

Package

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Microsoft.EntityFrameworkCore" version="1.1.1" targetFramework="net461" />
  <package id="Microsoft.EntityFrameworkCore.Relational" version="1.1.1" targetFramework="net461" />
  <package id="Microsoft.Extensions.Caching.Abstractions" version="1.1.1" targetFramework="net461" />
  <package id="Microsoft.Extensions.Caching.Memory" version="1.1.1" targetFramework="net461" />
  <package id="Microsoft.Extensions.DependencyInjection" version="1.1.0" targetFramework="net461" />
  <package id="Microsoft.Extensions.DependencyInjection.Abstractions" version="1.1.0" targetFramework="net461" />
  <package id="Microsoft.Extensions.Logging" version="1.1.1" targetFramework="net461" />
  <package id="Microsoft.Extensions.Logging.Abstractions" version="1.1.1" targetFramework="net461" />
  <package id="Microsoft.Extensions.Options" version="1.1.1" targetFramework="net461" />
  <package id="Microsoft.Extensions.Primitives" version="1.1.0" targetFramework="net461" />
  <package id="Microsoft.NETCore.Platforms" version="1.1.0" targetFramework="net461" />
  <package id="Microsoft.Win32.Primitives" version="4.3.0" targetFramework="net461" />
  <package id="NETStandard.Library" version="1.6.1" targetFramework="net461" />
  <package id="Npgsql" version="3.1.9" targetFramework="net461" />
  <package id="Npgsql.EntityFrameworkCore.PostgreSQL" version="1.1.0" targetFramework="net461" />
  <package id="Remotion.Linq" version="2.1.1" targetFramework="net461" />
  <package id="System.AppContext" version="4.3.0" targetFramework="net461" />
  <package id="System.Collections" version="4.3.0" targetFramework="net461" />
  <package id="System.Collections.Concurrent" version="4.3.0" targetFramework="net461" />
  <package id="System.Collections.Immutable" version="1.3.0" targetFramework="net461" />
  <package id="System.ComponentModel" version="4.3.0" targetFramework="net461" />
  <package id="System.Console" version="4.3.0" targetFramework="net461" />
  <package id="System.Diagnostics.Debug" version="4.3.0" targetFramework="net461" />
  <package id="System.Diagnostics.DiagnosticSource" version="4.3.0" targetFramework="net461" />
  <package id="System.Diagnostics.Tools" version="4.3.0" targetFramework="net461" />
  <package id="System.Diagnostics.Tracing" version="4.3.0" targetFramework="net461" />
  <package id="System.Globalization" version="4.3.0" targetFramework="net461" />
  <package id="System.Globalization.Calendars" version="4.3.0" targetFramework="net461" />
  <package id="System.Interactive.Async" version="3.0.0" targetFramework="net461" />
  <package id="System.IO" version="4.3.0" targetFramework="net461" />
  <package id="System.IO.Compression" version="4.3.0" targetFramework="net461" />
  <package id="System.IO.Compression.ZipFile" version="4.3.0" targetFramework="net461" />
  <package id="System.IO.FileSystem" version="4.3.0" targetFramework="net461" />
  <package id="System.IO.FileSystem.Primitives" version="4.3.0" targetFramework="net461" />
  <package id="System.Linq" version="4.3.0" targetFramework="net461" />
  <package id="System.Linq.Expressions" version="4.3.0" targetFramework="net461" />
  <package id="System.Linq.Queryable" version="4.0.1" targetFramework="net461" />
  <package id="System.Net.Http" version="4.3.0" targetFramework="net461" />
  <package id="System.Net.Primitives" version="4.3.0" targetFramework="net461" />
  <package id="System.Net.Sockets" version="4.3.0" targetFramework="net461" />
  <package id="System.ObjectModel" version="4.3.0" targetFramework="net461" />
  <package id="System.Reflection" version="4.3.0" targetFramework="net461" />
  <package id="System.Reflection.Extensions" version="4.3.0" targetFramework="net461" />
  <package id="System.Reflection.Primitives" version="4.3.0" targetFramework="net461" />
  <package id="System.Resources.ResourceManager" version="4.3.0" targetFramework="net461" />
  <package id="System.Runtime" version="4.3.0" targetFramework="net461" />
  <package id="System.Runtime.CompilerServices.Unsafe" version="4.3.0" targetFramework="net461" />
  <package id="System.Runtime.Extensions" version="4.3.0" targetFramework="net461" />
  <package id="System.Runtime.Handles" version="4.3.0" targetFramework="net461" />
  <package id="System.Runtime.InteropServices" version="4.3.0" targetFramework="net461" />
  <package id="System.Runtime.InteropServices.RuntimeInformation" version="4.3.0" targetFramework="net461" />
  <package id="System.Runtime.Numerics" version="4.3.0" targetFramework="net461" />
  <package id="System.Security.Cryptography.Algorithms" version="4.3.0" targetFramework="net461" />
  <package id="System.Security.Cryptography.Encoding" version="4.3.0" targetFramework="net461" />
  <package id="System.Security.Cryptography.Primitives" version="4.3.0" targetFramework="net461" />
  <package id="System.Security.Cryptography.X509Certificates" version="4.3.0" targetFramework="net461" />
  <package id="System.Text.Encoding" version="4.3.0" targetFramework="net461" />
  <package id="System.Text.Encoding.Extensions" version="4.3.0" targetFramework="net461" />
  <package id="System.Text.RegularExpressions" version="4.3.0" targetFramework="net461" />
  <package id="System.Threading" version="4.3.0" targetFramework="net461" />
  <package id="System.Threading.Tasks" version="4.3.0" targetFramework="net461" />
  <package id="System.Threading.Timer" version="4.3.0" targetFramework="net461" />
  <package id="System.Xml.ReaderWriter" version="4.3.0" targetFramework="net461" />
  <package id="System.Xml.XDocument" version="4.3.0" targetFramework="net461" />
</packages>
amit-kumar2 commented 7 years ago

First of all, this library is great for abstracting the global filtering from individual queries! I am planning to use it for handling global soft delete data filtering in the EF queries.

I am facing few issues while using it in the .net core version. You example above work, but even with the ForceCast = true, the following two scenarios do not work:

  1. Projections using Select: If I add a projection using Select, then I get an error in the code below: QueryFilterManager.ForceCast = true; ctx.Filter(q => q.Where(x => x.Title == "A")); var testProcessProjection = ctx.TestProcesss.Select(c => new { c.Title }).ToList();

Error Message is: No coercion operator is defined between types '<>f__AnonymousType0`1[System.String]' and 'SampleApp.Program+Test+TestProcess'.

  1. Joins: If I join more than one Entity in the EF query, then I get an error: Unable to cast object of type 'Microsoft.EntityFrameworkCore.Storage.ValueBuffer' to type 'Persons'. The code used for join is: QueryFilterManager.ForceCast = true; ctx.Filter(q => q.Where(x => x.Title == "A")); var testProcess = from tp in ctx.TestProcesss join p in ctx.Persons on tp.ID equals p.ID select tp; var result = testProcess.ToList();

The two entities used for joining are:

        [Table("Persons", Schema = "dbo")]
        public class Persons : InheritedTestClass, IProcess, IStuff
        {
            public int ID { get; set; }

            public string Title { get; set; }
        }
        [Table("TestProcess", Schema = "dbo")]
        public class TestProcess : InheritedTestClass, IProcess, IStuff
        {
            public int ID { get; set; }

            public string Title { get; set; }
        }

Can you please help me with these scenarios?