Technologie: Blazor Server, .NET 5
DB: MS SQL
Genutzt wird AutoMapper 10.1.1, AutoMapper.Extensions.ExpressionMapping 4.1.1, Microsoft.EntityFrameworkCore 5.0.6
Ich nutze das Query Specification Pattern, DTO's im Frontend und das Repository Pattern.
Im Frontend erstelle ich eine neue Instanz von der Query mit dem benötigten DTO.
ISpecification<PageDTO> specification = new BaseSpecification<PageDTO>(null);
specification.AddInclude(x => x.Topic);
"x.Topic" ist eine Property, die auf das DTO von Topic zeigt.
Die Query übergebe ich in die benötigte BLL Methode und will dann die Query von DTO auf die richtige Entität mappen.
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;
}
Wenn ich beispielsweise bei Criteria sage
Topic:
x => x.Name == "Test";
funktioniert das Mapping. Reicht auch für Criteria.
Aber bei dem Include funktioniert das Mapping nicht.
Sobald ich das gemappte Include zum Repository übergebe und EF Core das Statement verarbeitet, bekomme ich diese Fehlermeldung:
Fehlermeldung:
Error: System.InvalidOperationException: The expression 'Convert(x.Topic, Object)' is invalid inside an 'Include' operation, since it does not represent a property access: 't => t.MyProperty'. To target navigations declared on derived types, use casting ('t => ((Derived)t).MyProperty') or the 'as' operator ('t => (t as Derived).MyProperty'). Collection navigation access can be filtered by composing Where, OrderBy(Descending), ThenBy(Descending), Skip or Take operations. For more information on including related data, see
> .
Kann man die Navigation Property überhaupt Mappen? Oder kann ich mit dem Include gar nicht auf DTO's gehen und muss direkt auf die Entitäten zurück greifen?
Grüße
duesmannr
Ich bin kein riesen Fan von dem Vorgehen, vor allem nicht von dem Beispiel, weil hier untypisiert mit object gearbeitet wird.
Keine Ahnung wer auch immer dachte, dass das eine gute Idee sei.
Das Vorgehen verlagert auch das Executing des Queries vom DAL in die UI - was ich eher fragwürdig bezeichnen würde.
Die Exception hier kommt aber von object
, weil Du hier untypisiert unterwegs bist.
So wie ich das sehe kann Zeile 9 nicht funktionieren. Du musst dem AutoMapper konkrete Typen mitgeben, ansonsten funktioniert die Mapping Engine nicht.
Liegt aber an für sich nicht am Mapper selbst, sondern wie Expressions in .NET funktionieren.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Also würdest du das Query Specification Pattern nicht empfehlen und im DAL eine extra Methode dann dafür haben, die mir explizit die Pages zurück gibt, mit den Includes die ich benötige?
Edit:
Bzw. das Pattern nur für alles andere verwenden außer Includes?
Ich brauche diesen Pattern prinzipiell gar nicht, weil ich das Problem in meiner Software versuche mit Queryable Extensions — AutoMapper documentation zu lösen => Projections
Wo das nicht funktioniert baue ich die Expressions von Hand, die dann in eine View
(auch hier im Forum).
D.h. am Ende haben wir:
Projections
, die automatisch von AutoMapper gelöst werden (inkl Includes)Views
, die manuell zusammen gebaut werden und entweder händische Relationen haben oder aus einer oder mehreren Projections
bestehen.Bis auf ganz wenige Fälle, funktioniert das eigentlich auch sehr gut.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Da ich eine BLL Ebene und eine Repository Ebene besitze, wären die Projections in der Repository Ebene.
Oder verstehe ich das falsch? Da .ProjectTo auf IQueryable setzt und IQueryable in der Repository Ebene bleiben sollte.
Ich weiß aber noch nicht so ganz, wie ich auf das Query Specification Pattern verzichten kann.
Nehmen wir ein Beispiel:
Schüler, Klassen, Schule.
Ich brauche im Frontend eine Klasse und auch nur die dazugehörigen Schüler, nicht die Schule. (Eager Loading wäre ein Include auf Schüler)
Rufe eine generische GET Methode (die nur die Klasse zurückgeben würde ohne die Schüler) in der BLL auf.
Die BLL-Methode ruft die GET-Methode im Repository auf und dann mappe ich den Result in der BLL Methode und gib es zum Frontend zurück. (Als Bsp. im Repository: context.Classes.ToList()).
Wie halte ich das denn jetzt generisch, dass ich bis zum Repository weiterleiten kann, dass ich die Schüler auch brauche?
Oder habe ich einen Denkfehler?
Generisch ist halt leider auch kein Allzweckhammer. Du wirst irgendwo einen Kompromiss finden müssen.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Im Endeffekt also, wenn ich bei Eager Loading bleibe, muss ich für unterschiedliche Includes, jeweils eine seperate Methode haben, weil ich die Expressions für Includes nicht mappen kann. Korrekt?
In Deinem Code geht das in der Form nicht, weil Du mit object arbeitest. Expression auf object gehen nicht.
Im Falle von Projektionen läuft das ja automatisch.
Du kannst ja mal versuchen ob Dein Pattern mit konkreten bzw generischen Typen funktioniert, statt untypisiert mit object.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Ich wüsste aber auch nicht, wie ich in meiner Architektur auf Projektionen setze.
Wie soll ich denn die Expression für die Include Methode typsiert machen?
Na statt object nen (generischen) Typ verwenden.
Siehst ja in der Exception auch, dass object hier nicht akzeptiert wird, sondern der Typ (spätestens durch einen Cast) bekannt sein muss.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code