Laden...

Expression<Func> Mapping - InvalidOperationException - Could not be translated

Erstellt von Duesmannr vor 3 Jahren Letzter Beitrag vor 3 Jahren 331 Views
D
Duesmannr Themenstarter:in
161 Beiträge seit 2017
vor 3 Jahren
Expression<Func> Mapping - InvalidOperationException - Could not be translated

Nabend Leute,

Verwendete Systeme/Bibs: EF Core 5, Automapper

Ich kann von der UI eine Specification erhalten, die verschiedene Properties enthält, um den Get Request zu steuern.


public interface ISpecification<TEntity>
    {
        #region Properties

        Expression<Func<TEntity, bool>> Criteria { get; }

        List<Expression<Func<TEntity, object>>> Includes { get; }

        List<string> IncludeStrings { get; }

        PagingSpecification PagingSpecification { get; }

        #endregion //Properties

        #region Methods

        void AddInclude(Expression<Func<TEntity, object>> includeExpression);

        void AddInclude(string includeString);

        void AddPaging(PagingSpecification pagingSpecification);

        #endregion //Methods
    }

Das TEntity ist bei mir ein DTO.
Und in der BLL will ich dann die Specification<DTO> zu Specification<Entity> mappen.

Das würde z.B. ankommen:


ISpecification<CategoryDTO> specification = new BaseSpecification<CategoryDTO>(x => x.Id == idToGet);
//idToGet = Guid variable.

Mit Automapper schaffe ich es nicht, es zu mappen. Der Wert ist immer null. (Criteria)
Deshalb manuell mappen.
Wie mappt man eine Expression? I dont know => google.

Bin dann auf den Eintrag gestoßen.

Wobei ich bei der _parameter Variable das 'c' entfernt habe.
Mapping erfolgreich.

  1. Bild im Anhang: So sieht es dann im Debugger aus.

Lass ich die Expression nun gegen die Db laufen, erhalte ich eine InvalidOperationException, sobald ich auf den Entites mit Where und dem Criteria filtere.
Message:


The LINQ expression 'DbSet<Category>()
    .Where(c => x.Id == __idToGet_0)' 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 [url]https://go.microsoft.com/fwlink/?linkid=2101038[/url] for more information.

In der Message steht "c => x.Id....".
Ich gehe davon aus, dass es an dem "c" liegt. Aber woher kommt das?

P.S.: Ich weiß wie Expressions oberflächlich funktionieren, aber so genau, wie man diese mappt, woraus die konkret bestehen weiß ich nicht. Deswegen habe ich vorerst die Methoden aus dem SO Beitrag 1:1 übernommen.

Grüße
Duesmannr

D
Duesmannr Themenstarter:in
161 Beiträge seit 2017
vor 3 Jahren

Als Nachtrag, hab es über Automapper geschafft.
Aber nicht das ganze Objekt direkt, sondern die Properties einzelnt zu mappen.

Man braucht dafür das Nuget Package von Automapper 'AutoMapper.Extensions.ExpressionMapping'.
Und dann muss man noch beim hinzufügen des Automappers im Service die Methode aufrufen


automapper.AddExpressionMapping();

//So sieht das dann ganz aus
services.AddAutoMapper((serviceProvider, automapper) =>
            {
                automapper.AddExpressionMapping();
                automapper.AddProfile<AutoMapping>();
            }, typeof(ApplicationDbContext).Assembly);

Das hatte ich davor auch schon alles.

Was ich nun mache, das ich die einzelnen Properties vom Specification Objekt durch gehe und diese mappe:


protected virtual ISpecification<TEntity> MapSpecification(ISpecification<TDTOEntity> specification)
        {
            Expression<Func<TEntity, bool>> criteriaExpression = this.Mapper.Map<Expression<Func<TEntity, bool>>>(specification.Criteria);

            ISpecification<TEntity> result = new BaseSpecification<TEntity>(criteriaExpression);

            foreach (Expression<Func<TDTOEntity, object>> includeDTOExpression in specification.Includes)
            {
                Expression<Func<TEntity, object>> includeExpression = this.Mapper.Map<Expression<Func<TEntity, object>>>(includeDTOExpression);

                result.AddInclude(includeExpression);
            }

            specification.IncludeStrings.ForEach(x => result.AddInclude(x));

            result.AddPaging(specification.PagingSpecification);

            return result;
        }

Und das funktioniert auch. Nur unvorteilhaft, wenn Properties dazu kommen, muss man diese ebenfalls mit aufnehmen.

16.834 Beiträge seit 2008
vor 3 Jahren

Das wirst Du nicht 100% lösen können, warum:

Nicht alle Linq-Arten werden auch von EF Core unterstützt bzw. brauchst Du für manche Dinge Workarounds.
Der Workaround ToList für Sub-Listen ist by design um der Query Engine genau zu sagen, was EF Core erzeugen soll. Dadurch entstehen bessere Queries; fordern vom Entwickler jedoch spezifischen Linq-Code.

Und ob das jetzt so ne gute Idee ist so ein untypisiertes, full generic Konstrukt aufzubauen; die Ideen sieht man immer wieder aber ich persönlich hab da noch nie eine wirklich stabile Lösung gesehen 😕