EntityFrameworkCore.Projectables icon indicating copy to clipboard operation
EntityFrameworkCore.Projectables copied to clipboard

Not working with IQueryable.Contains

Open phuc1640 opened this issue 1 year ago • 2 comments

Let say we have a DbSet that have model like so

public class User {
  public int Id { get; set; }
  public int UserName { get; set; }
  public DateTimeOffset? SendMoneyAt { get; set; }
  [Projectable]
  public bool IsBacker => SendMoneyAt != null;
}

When we try a query like

var backerUsers = DbSet<User >().Where(u => u.IsBacker);

var usersWithLetterA = DbSet<User >().Where(u => u.UserName.Contains("A")).Select(u => u.Id);

var backerUsersWithLetterA = users.Where(u => usersWithLetterA.Contains(u.Id));

The query of backerUsersWithLetterA cannot be translated.

Is this a bug or a some limitation?

phuc1640 avatar Aug 08 '24 03:08 phuc1640

I'm unable to reproduce this:

SELECT [u].[Id], [u].[SendMoneyAt], [u].[UserName]
FROM [User] AS [u]
WHERE [u].[Id] IN (
    SELECT [u0].[Id]
    FROM [User] AS [u0]
    WHERE [u0].[UserName] LIKE N'%A%'
)

test case:

        public class User
        {
            public int Id { get; set; }
            public string? UserName { get; set; }
            public DateTimeOffset? SendMoneyAt { get; set; }
            [Projectable]
            public bool IsBacker => SendMoneyAt != null;
        }

        [Fact]
        public void Issue112()
        {
            var dbContext = new SampleDbContext<User>();
            var usersWithLetterA = dbContext.Set<User>().Where(u => u.UserName!.Contains("A")).Select(u => u.Id);
            var backerUsersWithLetterA = dbContext.Set<User>().Where(u => usersWithLetterA.Contains(u.Id));

            Verifier.Verify(backerUsersWithLetterA.ToQueryString());
        }

koenbeuk avatar Aug 18 '24 23:08 koenbeuk

fyi, I hit the same error when calling Contains on a projectable property like this: myDbSet.Where(x => list.Contains(x.ProjectableProperty)).ToList();

Translation of member 'ProjectableProperty' on entity type 'MyType' failed. This commonly occurs when the specified member is unmapped.

@koenbeuk, I believe you carried a small bug in @phuc1640's example to the test case:

var dbContext = new SampleDbContext<User>();
var backerUsers = dbContext.Set<User>().Where(u => u.IsBacker); // missing in your test case
var usersWithLetterA = dbContext.Set<User>().Where(u => u.UserName!.Contains("A")).Select(u => u.Id);
var backerUsersWithLetterA = backerUsers.Where(u => usersWithLetterA.Contains(u.Id)); // I believe the intention was to refer to backerUsers instead of the User set

Verifier.Verify(backerUsersWithLetterA.ToQueryString());

That said I haven't executed this test myself, so I can't say whether it reproduces the error.

samolesen avatar Sep 10 '24 14:09 samolesen

The culprit here is this line: backerUsers.Where(u => usersWithLetterA.Contains(u.Id)); where if u.Id was a projectable property then it would throw this error

koenbeuk avatar Oct 23 '24 00:10 koenbeuk

I have written some tests and ran them with version 3.0.4 source code. I could create the InvalidOperationException only in the case when the [Projectable] is used inside an IQueryable which then is used by the Contains call. Sadly I was not able to find a simple example which uses a [Projectable] property as the argument of the Contains call which produces an exception. I put the result of the test run above the Verify call of each test case. Maybe this helps to find a fix.

public class User
{
    public int Id { get; set; }
    public string? UserName { get; set; }
    public DateTimeOffset? SendMoneyAt { get; set; }
    [Projectable]
    public bool IsBacker => SendMoneyAt != null;
    [Projectable]
    public int OtherId => Id;
}

[Fact]
public void Issue112_ProjectableQueryableContains()
{
    var dbContext = new SampleDbContext<User>();
    var backerUserIds = dbContext.Set<User>().Where(u => u.IsBacker).Select(u => u.Id);
    var usersWithLetterA = dbContext.Set<User>().Where(u => u.UserName!.Contains("A"));
    var backerUsersWithLetterA = usersWithLetterA.Where(u => backerUserIds.Contains(u.Id));

    // Result with 3.0.4: InvalidOperationException
    /* System.InvalidOperationException : The LINQ expression 'DbSet<User>()
        .Where(u0 => u0.IsBacker)' could not be translated. Additional information: 
        Translation of member 'IsBacker' on entity type 'User' failed. This commonly 
        occurs when the specified member is unmapped. Either rewrite the query in a 
        form that can be translated, or switch to client evaluation explicitly by inserting 
        a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. 
        See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
    */
    Verifier.Verify(backerUsersWithLetterA.ToQueryString());
}

[Fact]
public void Issue112_ProjectableQueryableJoin()
{
    var dbContext = new SampleDbContext<User>();
    var backerUserIds = dbContext.Set<User>().Where(u => u.IsBacker);
    var usersWithLetterA = dbContext.Set<User>().Where(u => u.UserName!.Contains("A"));
    var backerUsersWithLetterA = usersWithLetterA.Join(backerUserIds, x => x.Id, x => x.Id, (u, _) => u);

    // Result with 3.0.4: Works
    /* SELECT [u].[Id], [u].[SendMoneyAt], [u].[UserName]
       FROM [User] AS [u]
       INNER JOIN (
           SELECT [u0].[Id]
           FROM [User] AS [u0]
           WHERE [u0].[SendMoneyAt] IS NOT NULL
       ) AS [t] ON [u].[Id] = [t].[Id]
       WHERE [u].[UserName] LIKE N'%A%' 
    */
    Verifier.Verify(backerUsersWithLetterA.ToQueryString());
}

[Fact]
public void Issue112_QueryableContainsUsesProjectableProperty()
{
    var dbContext = new SampleDbContext<User>();
    var backerUsers = dbContext.Set<User>().Where(u => u.IsBacker);
    var usersWithLetterA = dbContext.Set<User>().Where(u => u.UserName!.Contains("A")).Select(x => x.Id);
    var backerUsersWithLetterA = backerUsers.Where(u => usersWithLetterA.Contains(u.OtherId));

    // Result with 3.0.4: Works
    /* SELECT [u].[Id], [u].[SendMoneyAt], [u].[UserName]
       FROM [User] AS [u]
       WHERE [u].[SendMoneyAt] IS NOT NULL AND EXISTS (
           SELECT 1
           FROM [User] AS [u0]
           WHERE ([u0].[UserName] LIKE N'%A%') AND ([u0].[Id] = [u].[Id]))
    */
    Verifier.Verify(backerUsersWithLetterA.ToQueryString());
}

[Fact]
public void Issue112_ArrayContainsUsesProjectableProperty()
{
    var dbContext = new SampleDbContext<User>();
    var backerUsers = dbContext.Set<User>().Where(u => u.IsBacker);
    var usersWithLetterA = new[] { 1, 2, 3 };
    var backerUsersWithLetterA = backerUsers.Where(u => usersWithLetterA.Contains(u.OtherId));

    // Result with 3.0.4: Works
    /* SELECT [u].[Id], [u].[SendMoneyAt], [u].[UserName]
       FROM [User] AS [u]
       WHERE [u].[SendMoneyAt] IS NOT NULL AND [u].[Id] IN (1, 2, 3)
    */
    var text = backerUsersWithLetterA.ToQueryString();
    Verifier.Verify(backerUsersWithLetterA.ToQueryString());
}

Yarith avatar Oct 23 '24 08:10 Yarith

@samolesen thank you for correcting my example

phuc1640 avatar Nov 22 '24 02:11 phuc1640

FYI, I noticed this issue and ran the tests against the changes in my PR #124 and as I expected, it's been fixed as a side-effect of the changes made there.

wassim-k avatar Dec 07 '24 02:12 wassim-k

Since #124 has been merged, this should now be fixed

koenbeuk avatar Dec 07 '24 19:12 koenbeuk