Reputation: 41
The AutoMapper.Extensions.OData
github repo instructs to go here first, so I hope this finds the right people. I'm not exactly sure if this is related to AutoMapper.AspNetCore.OData.EFCore
, AutoMapper.Extensions.ExpressionMapping
, neither, or both!
I have a sample repo here that reproduces the problem. Steps to get set up and run the project can be found in the README.
Summary
I have an entity and a DTO each named Foo
that each have a flags enum such as:
namespace Domain.Enumerations;
[Flags]
public enum FooType
{
One = 0b0001,
Two = 0b0010,
Three = 0b0100,
Four = 0b1000
}
namespace Domain.Components.Foos;
public class Foo
{
public Guid Id { get; set; }
public FooType Type { get; set; }
}
namespace ApiModel;
[Flags]
public enum FooType
{
One = 0b0001,
Two = 0b0010,
Three = 0b0100,
Four = 0b1000
}
public class Foo
{
public Guid Id { get; set; }
public FooType Type { get; set; }
}
The domain entity Foo
is bound to the database via EF Core and the model/dto Foo
is used to build the OData EDM. I'm using AutoMapper's OData/EFCore/ExpressionMapping extensions to translate between entity Foo
and model Foo
.
If I query my API with $filter=type eq 'Four'
everything works great. If I query my API with $filter=type has 'Four'
the application throws an exception:
System.InvalidOperationException: The LINQ expression 'DbSet().Where(f => f.Type.HasFlag((Enum)Four))' could not be translated.
Additional information: Translation of method 'System.Enum.HasFlag' failed. If this method can be mapped to your custom function, see https://go.microsoft.com/fwlink/?linkid=2132413 for more information. 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.QueryableMethodTranslatingExpressionVisitor.Translate(Expression expression)
at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.Translate(Expression expression)
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__DisplayClass12_01.<ExecuteAsync>b__0() at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func
1 compiler)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable1.GetAsyncEnumerator(CancellationToken cancellationToken) at System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable
1.GetAsyncEnumerator()
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable1 source, CancellationToken cancellationToken) at AutoMapper.AspNet.OData.QueryableExtensions.GetAsync[TModel,TData](IQueryable
1 query, IMapper mapper, Expression1 filter, Expression
1 queryFunc, ICollection1 includeProperties, AsyncSettings asyncSettings) at AutoMapper.AspNet.OData.QueryableExtensions.GetAsync[TModel,TData](IQueryable
1 query, IMapper mapper, ODataQueryOptions`1 options, QuerySettings querySettings)
at Domain.Components.Foos.Queries.GetFoosODataQueryHandler.Handle(GetFoosODataQuery query, CancellationToken ct) in C:\Projects\AutoMapper.OData.EFCore.Issue\Domain\Components\Foos\Queries\GetFoosOData.cs:line 29
In some of our older projects, we've been able to use a flags enum has
such as this but only when the DbSet<Foo>
is returned directly from the controller as an IQueryable
- this becomes problematic with a versioned API as the versioned API contracts won't be 1-to-1 with the database schema.
I've considered introducing database views that perform this translation layer and then exposing view pocos as the OData EDM but exposing the data layer in this way wouldn't be ideal from an architecture / design perspective.
Expression Mapping Debug
I've added some unit tests that attempt to illustrate and compare the differences between
Expression<Func<Foo, bool>> filter = x => x.Type.HasFlag(FooType.Three);
and
Expression<Func<ApiModel.Foo, bool>> modelFilter = x => x.Type.HasFlag(ApiModel.FooType.Three);
var filter = _mapper.MapExpression<Expression<Func<Foo, bool>>>(modelFilter);
I, so far, am unable to find a difference between the two filter
s, but the former works and the latter does not.
Has anyone run into something similar who knows a solution? I'm hoping I can continue using AutoMapper to perform mapping between layers vs moving this responsibility into the database.
At any rate, thanks for taking a look!
Upvotes: 0
Views: 161