Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support translation of member access on properties mapped by value conversion #20721

Closed
bdebaere opened this issue Apr 22, 2020 · 6 comments
Closed
Labels
closed-out-of-scope This is not something that will be fixed/implemented and the issue is closed. customer-reported

Comments

@bdebaere
Copy link

Filtering on a complex type is no longer possible in EntityFrameworkCore 3.1.3. Consider the code below. This code used to work with EntityFrameworkCore 2.2 without a problem. The reason why I am not using OwnsOne is because I want to apply a conversion as the entity is saved as a JSON in one column, and I want Address to always be visible (i.e. not something to use .Include() on). Am I doing something wrong here?

using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text.Json;
using Microsoft.EntityFrameworkCore;

namespace ComplexEntityQuerying
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var dbContext = new MyDbContext())
            {
                var result = dbContext.Customers.Where(customer => customer.Address.Street == "test").ToList();
            }

            Console.ReadLine();
        }
    }

    public class MyDbContext : DbContext
    {
        public DbSet<Customer> Customers { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(
                "Server=(LocalDb)\\MSSQLLocalDB;Database=master;Integrated Security=True;");

            base.OnConfiguring(optionsBuilder);
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            var customer = modelBuilder
                .Entity<Customer>();

            customer.ToTable("Customer");
            customer
                .Property(entity => entity.Address)
                .HasConversion(
                    toDatabase => JsonSerializer.Serialize(toDatabase, null),
                    toEntity => JsonSerializer.Deserialize<Address>(toEntity, null));

            base.OnModelCreating(modelBuilder);
        }
    }

    public class Customer
    {
        [Key]
        public Guid Id { get; set; }

        public Address Address { get; set; }
    }
    
    public class Address
    {
        public string Street { get; set; }
    }
}
System.InvalidOperationException: 'The LINQ expression 'DbSet<Customer>
    .Where(c => c.Address.Street == "test")' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.'

   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.<VisitMethodCall>g__CheckTranslated|8_0(ShapedQueryExpression translated, <>c__DisplayClass8_0& ) in C:\Users\redacted\AppData\Local\JetBrains\Shared\vAny\DecompilerCache\decompiler\A3DBE483-6645-4FDB-B51D-FEEB0B5C4997\24\69814530\QueryableMethodTranslatingExpressionVisitor.cs:line 404
   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression) in C:\Users\redacted\AppData\Local\JetBrains\Shared\vAny\DecompilerCache\decompiler\A3DBE483-6645-4FDB-B51D-FEEB0B5C4997\24\69814530\QueryableMethodTranslatingExpressionVisitor.cs:line 390
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression) in C:\Users\redacted\AppData\Local\JetBrains\Shared\vAny\DecompilerCache\decompiler\5314A29F-EBC1-4288-A1BC-53BF7B46D725\36\32255f85\RelationalQueryableMethodTranslatingExpressionVisi.cs:line 65
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query) in C:\Users\redacted\AppData\Local\JetBrains\Shared\vAny\DecompilerCache\decompiler\A3DBE483-6645-4FDB-B51D-FEEB0B5C4997\19\18ce3d1c\QueryCompilationContext.cs:line 76
   at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async) in C:\Users\redacted\AppData\Local\JetBrains\Shared\vAny\DecompilerCache\decompiler\A3DBE483-6645-4FDB-B51D-FEEB0B5C4997\fc\fe642646\Database.cs:line 75
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async) in C:\Users\redacted\AppData\Local\JetBrains\Shared\vAny\DecompilerCache\decompiler\A3DBE483-6645-4FDB-B51D-FEEB0B5C4997\57\2fb65efb\QueryCompiler.cs:line 98
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass9_0`1.<Execute>b__0() in C:\Users\redacted\AppData\Local\JetBrains\Shared\vAny\DecompilerCache\decompiler\A3DBE483-6645-4FDB-B51D-FEEB0B5C4997\57\2fb65efb\QueryCompiler.cs:line 89
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQueryCore[TFunc](Object cacheKey, Func`1 compiler) in C:\Users\redacted\AppData\Local\JetBrains\Shared\vAny\DecompilerCache\decompiler\A3DBE483-6645-4FDB-B51D-FEEB0B5C4997\69\374f8673\CompiledQueryCache.cs:line 87
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler) in C:\Users\redacted\AppData\Local\JetBrains\Shared\vAny\DecompilerCache\decompiler\A3DBE483-6645-4FDB-B51D-FEEB0B5C4997\69\374f8673\CompiledQueryCache.cs:line 60
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query) in C:\Users\redacted\AppData\Local\JetBrains\Shared\vAny\DecompilerCache\decompiler\A3DBE483-6645-4FDB-B51D-FEEB0B5C4997\57\2fb65efb\QueryCompiler.cs:line 89
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression) in C:\Users\redacted\AppData\Local\JetBrains\Shared\vAny\DecompilerCache\decompiler\A3DBE483-6645-4FDB-B51D-FEEB0B5C4997\b0\56eeeeef\EntityQueryProvider.cs:line 91
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetEnumerator() in C:\Users\redacted\AppData\Local\JetBrains\Shared\vAny\DecompilerCache\decompiler\A3DBE483-6645-4FDB-B51D-FEEB0B5C4997\a6\dd079ce5\EntityQueryable`1.cs:line 102
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at ComplexEntityQuerying.Program.Main(String[] args) in C:\Users\redacted\source\repos\ComplexEntityQuerying\ComplexEntityQuerying\Program.cs:line 17

Further technical details

EF Core version: 3.1.3
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: .NET Core 3.1
Operating system: Windows
IDE: Visual Studio 2019 16.3

@smitpatel
Copy link
Member

It was client evaluated before.

@bdebaere
Copy link
Author

@smitpatel Interesting! Is there a way to get the desired behavior, either through adding custom code or by allowing client evaluation for this specific case?

@bdebaere
Copy link
Author

@smitpatel The issue is that a ColumnExpression cannot be generated for Address.Street, while it can for Address.

In RelationalSqlTranslatingExpressionVisitor, TryBindMember succeeds for Address, but then fails for Address.Street due to the Expression having to be an EntityReferenceExpression.

Can you provide some guidance on how I can tackle this?

        protected override Expression VisitMember(MemberExpression memberExpression)
        {
            Check.NotNull(memberExpression, nameof(memberExpression));

            var innerExpression = Visit(memberExpression.Expression);

            return TryBindMember(innerExpression, MemberIdentity.Create(memberExpression.Member))
                ?? (TranslationFailed(memberExpression.Expression, Visit(memberExpression.Expression), out var sqlInnerExpression)
                    ? null
                    : Dependencies.MemberTranslatorProvider.Translate(sqlInnerExpression, memberExpression.Member, memberExpression.Type));
        }

        private Expression TryBindMember(Expression source, MemberIdentity member)
        {
            if (!(source is EntityReferenceExpression entityReferenceExpression))
            {
                return null;
            }

            var entityType = entityReferenceExpression.EntityType;
            var property = member.MemberInfo != null
                ? entityType.FindProperty(member.MemberInfo)
                : entityType.FindProperty(member.Name);

            return property != null ? BindProperty(entityReferenceExpression, property) : null;
        }

@bdebaere
Copy link
Author

@smitpatel Of course, I can replace MemberExpressions in the original expression tree with a custom expression when it follows a specific pattern to enforce custom handling. But I was hoping to change EntityFrameworkCore's code to fix this.

@ajcvickers
Copy link
Member

@bdebaere This particular case is covered by #4021 since using the conversion is basically a workaround here. Beyond this, we will keep this open for translating accesses on converted objects like this. However, the cases where this will work are probably going to be somewhat constricted, at least at first, since the conversion can do just about anything and not all accesses will be possible/easy to translate.

@ajcvickers ajcvickers self-assigned this Apr 27, 2020
@ajcvickers ajcvickers added this to the Backlog milestone Apr 27, 2020
@ajcvickers ajcvickers changed the title Filtering on complex type Support translation of member access on properties mapped by value conversion Apr 27, 2020
@ajcvickers ajcvickers modified the milestones: Backlog, 7.0.0 Oct 21, 2021
@smitpatel smitpatel removed this from the 7.0.0 milestone Oct 28, 2021
@ajcvickers ajcvickers added this to the Backlog milestone Nov 3, 2021
@ajcvickers ajcvickers added closed-out-of-scope This is not something that will be fixed/implemented and the issue is closed. and removed type-enhancement labels Nov 10, 2021
@AndriySvyryd
Copy link
Member

See #10434

@ajcvickers ajcvickers reopened this Oct 16, 2022
@ajcvickers ajcvickers closed this as not planned Won't fix, can't repro, duplicate, stale Oct 16, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-out-of-scope This is not something that will be fixed/implemented and the issue is closed. customer-reported
Projects
None yet
Development

No branches or pull requests

4 participants