Been recently delving into EF Core 6 and I'm getting a client side evalutation error which I'm wondering if there's any way to rewrite this in a more efficient way.
I need to query some Photos
based on a list of TagIds
. For example when filtering by the tags [Portugal,Beach], need to get all photos that include all those tags. So the photo X with [Portugal, Beach, Food] will be included but the photo Y [Portugal] will be out.
I believe I need to achieve something like this but that throws the client side evaluation exception:
_context.Photo
.Include(i => i.PhotoTags)
.ThenInclude(j => j.Tag)
.Where(i => filter.tagIds.All(j => i.PhotoTags.Exists(k => k.TagId == j)))
With the following classes:
public class Photo
{
public Guid Id { get; set; }
public string Description { get; set; }
public DateTime Date { get; set; }
public bool IsCover { get; set; }
public virtual List<PhotoTag> PhotoTags { get; set; }
}
public class PhotoTag : Base
{
public Guid Id { get; set; }
public Tag Tag { get; set; }
public Guid TagId { get; set; }
public Photo Photo { get; set; }
public Guid PhotoId { get; set; }
}
Edit
This is the error I'm getting:
System.InvalidOperationException: The LINQ expression 'j => MaterializeCollectionNavigation(
Navigation: Photo.PhotoTags,
subquery: DbSet<PhotoTag>()
.Where(p0 => EF.Property<Guid?>(EntityShaperExpression:
rvc.Models.Photo
ValueBufferExpression:
ProjectionBindingExpression: EmptyProjectionMember
IsNullable: False
, "Id") != null && object.Equals(
objA: (object)EF.Property<Guid?>(EntityShaperExpression:
rvc.Models.Photo
ValueBufferExpression:
ProjectionBindingExpression: EmptyProjectionMember
IsNullable: False
, "Id"),
objB: (object)EF.Property<Guid?>(p0, "PhotoId")))).Exists(k => k.TagId == j)' 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 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.VisitLambda[T](Expression`1 lambdaExpression)
at System.Linq.Expressions.Expression`1.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.TranslateInternal(Expression expression)
at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.Translate(Expression expression)
at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.TranslateExpression(Expression expression)
at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.TranslateLambdaExpression(ShapedQueryExpression shapedQueryExpression, LambdaExpression lambdaExpression)
at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.TranslateWhere(ShapedQueryExpression source, LambdaExpression predicate)
at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
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)
at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass9_0`1.<Execute>b__0()
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetEnumerator()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at rvc.Services.PhotoService.GetAll(FilterParameters filter) in C:\Users\jjtfs\Work\rvc\server\Services\PhotoService.cs:line 145
CodePudding user response:
This can be rewritten in a translatable form by counting the tags that are in the filter. This count should equal the number of items in the filter:
var count = filter.tagIds.Count();
var result = _context.Photo
.Include(p => p.PhotoTags)
.ThenInclude(pt => pt.Tag)
.Where(p => p.PhotoTags.Count(pt => filter.tagIds.Contains(pt.TagId)) == count);