简体   繁体   中英

Searching Against Projected EF Core Navigation Properties

Using EF Core 3.1, I'm trying to perform a search against a nullable navigation property (aka left join) after it has been projected to a different type.

For example:

var dtos = await query
    .Select(x => new RootEntityDto
    {
        // ...

        Nested = x.NestedId.HasValue
            ? new NestedEntityDto
            {
                // ...

                Description = x.Nested.Description
            }
            : null
    })
    .Where(x => x.Nested.Description.Contains("2"))
    .ToArrayAsync();

The problem is, it gives me an error over the ternary that may conditionally result in null:

The LINQ expression 'DbSet<RootEntity>
    .LeftJoin(
        outer: DbSet<NestedEntity>, 
        inner: f => EF.Property<Nullable<int>>(f, "NestedId"), 
        outerKeySelector: o => EF.Property<Nullable<int>>(o, "Id"), 
        innerKeySelector: (o, i) => new TransparentIdentifier<RootEntity, NestedEntity>(
            Outer = o, 
            Inner = i
        ))
    .Where(f => EF.Property<Nullable<int>>(f.Inner, "Id") != null ? new NestedEntityDto{ 
        Description = f.Inner.Description, 
    }
     : null.Description.Contains("2"))' 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.

If I try precisely the same thing before projecting the entity to a DTO, I have no problems:

var entities = await query
    .Where(x => x.Nested.Description.Contains("2"))
    .ToArrayAsync();

I realize the simple solution is to perform the Where statement before the projection occurs, but that's not an option. The above examples are simplified in order to illustrate the intent. I'm also not interested in evaluating the query client-side just because I don't know a better alternative to the ternary.

Ideally, I'm just looking for the best practice for conditionally projecting a left join navigation property, such that I can still perform searches on it.

Edit: I decided to try AutoMapper 10.1.1 and received a similar error:

var dtos = await query
    .ProjectTo<RootEntityDto>(_mapper.ConfigurationProvider, x => x.Nested)
    .Where(x => x.Nested.Description.Contains("2"))
    .ToArrayAsync();
The LINQ expression 'DbSet<RootEntity>
    .LeftJoin(
        outer: DbSet<NestedEntity>, 
        inner: f => EF.Property<Nullable<int>>(f, "NestedId"), 
        outerKeySelector: o => EF.Property<Nullable<int>>(o, "Id"), 
        innerKeySelector: (o, i) => new TransparentIdentifier<RootEntity, NestedEntity>(
            Outer = o, 
            Inner = i
        ))
    .Where(f => EF.Property<Nullable<int>>(f.Inner, "Id") == null ? null : new NestedEntityDto{ 
        Description = f.Inner.Description
    }
    .Description.Contains("2"))' 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.

EF Core 3 completely changed how the queries were evaluated and not all query constructs are supported. I suspect trying to filter after the DTO projection is the underlying issue here. Could you test upgrading to EF Core 5 with this query and see if it still fails there as well as more query patterns are supported with 5.

I did some research into what AutoMapper was doing and discovered a convention that works:

var dtos = await query
    .Select(x => new RootEntityDto
    {
        // ...

        Nested = x.Nested == null
            ? null
            : new NestedEntityDto
            {
                // ...

                Description = x.Nested.Description
            }
    })
    .Where(x => x.Nested.Description.Contains("2"))
    .ToArrayAsync();

Notice that I inverted the ternary and switched what would have been !x.NestedId.HasValue to x.Nested == null . But even with those changes, I still had to upgrade to EF Core 5.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM