5

Ich habe ein Projekt, in dem das Front-End-JavaScript eine Liste mit Spalten angibt, nach denen sortiert werden soll.So ordnen Sie dynamisch nach bestimmten Entitätseigenschaften in EntityFramework 7 (Core) an

Dann im Back-End habe ich Multi-Layer-Anwendung. Typisches Szenario

  1. Service-Schicht (die Service-Modelle (DTO) Eigenschaften entsprechen, der die Client-Seite will von bestellen)
  2. Domain Schicht (es macht Repository-Schnittstellen für den Zugriff beharrten Objekte)
  3. ORM-Schicht (es implementiert das Repository und verwendet Entity Framework 7 (aka Entity Framework Core) eine SQL-Server-Datenbank zuzugreifen)

Bitte beachten sie, dass System.Linq.Dynamic nicht unterstützt wird für DNX-Core v5.0 oder. NET Platform v5.4 kann ich nicht verwenden dass Bibliothek

Ich habe folgende Implementierung in meinen Sachen Repository:

public async Task<IEnumerable<Thing>> GetThingsAsync(IEnumerable<SortModel> sortModels) 
    { 
     var query = GetThingsQueryable(sortModels); 
     var things = await query.ToListAsync(); 
     return things; 
    } 

    private IQueryable<Thing> GetThingsQueryable(IEnumerable<SortModel> sortModels) 
    { 

     var thingsQuery = _context.Things 
       .Include(t => t.Other) 
       .Where(t => t.Deleted == false); 

     // this is the problematic area as it does not return a valid queyable 
     string orderBySqlStatement = GetOrderBySqlStatement(sortModels); 
     thingsQuery = thingsQuery.FromSql(orderBySqlStatement); 
     return thingsQuery ; 
    } 

    /// this returns something like " order by thingy1 asc, thingy2 desc" 
    private string GetOrderBySqlStatement(IEnumerable<SortModel> sortModels) 
    { 
     IEnumerable<string> orderByParams = sortModels.Select(pair => { return pair.PairAsSqlExpression; }); 
     string orderByParamsConcat = string.Join(", ", orderByParams); 
     string sqlStatement = orderByParamsConcat.Length > 1 ? $" order by {orderByParamsConcat}" : string.Empty; 
     return sqlStatement; 
    } 

und dies ist das Objekt, das einen Spaltennamen und eine Reihenfolge nach Richtung (auf oder ab) enthält

public class SortModel 
{ 
    public string ColId { get; set; } 
    public string Sort { get; set; } 

    public string PairAsSqlExpression 
    { 
     get 
     { 
      return $"{ColId} {Sort}"; 
     } 
    } 
} 

Dieser Ansatz versucht, eine SQL-Anweisung mit der Entität zu mischen, die Entity für die vorherige Abfrage erstellt. Aber ich habe ein:

Microsoft.Data.Entity.Query.Internal.SqlServerQueryCompilationContextFactory:Verbose: Compiling query model: 'from Thing t in {value(Microsoft.Data.Entity.Query.Internal.EntityQueryable`1[MyTestProj.Data.Models.Thing]) => AnnotateQuery(Include([t].DeparturePort)) => AnnotateQuery(Include([t].ArrivalPort)) => AnnotateQuery(Include([t].Consignments))} where (([t].CreatorBusinessId == __businessId_0) AndAlso (Convert([t].Direction) == __p_1)) select [t] => AnnotateQuery(QueryAnnotation(FromSql(value(Microsoft.Data.Entity.Query.Internal.EntityQueryable`1[MyTestProj.Data.Models.Thing]), " order by arrivalDate asc, arrivalPortCode asc", []))) => Count()' 
Microsoft.Data.Entity.Query.Internal.SqlServerQueryCompilationContextFactory:Verbose: Optimized query model: 'from Thing t in value(Microsoft.Data.Entity.Query.Internal.EntityQueryable`1[MyTestProj.Data.Models.Thing]) where (([t].CreatorBusinessId == __businessId_0) AndAlso (Convert([t].Direction) == __p_1)) select [t] => Count()' 
Microsoft.Data.Entity.Query.Internal.QueryCompiler:Error: An exception occurred in the database while iterating the results of a query. 
System.InvalidOperationException: The Include operation is not supported when calling a stored procedure. 
    at Microsoft.Data.Entity.Query.ExpressionVisitors.RelationalEntityQueryableExpressionVisitor.VisitEntityQueryable(Type elementType) 
    at Microsoft.Data.Entity.Query.ExpressionVisitors.EntityQueryableExpressionVisitor.VisitConstant(ConstantExpression constantExpression) 
    at System.Linq.Expressions.ConstantExpression.Accept(ExpressionVisitor visitor) 
    at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node) 
    at Microsoft.Data.Entity.Query.ExpressionVisitors.ExpressionVisitorBase.Visit(Expression expression) 
    at Microsoft.Data.Entity.Query.EntityQueryModelVisitor.ReplaceClauseReferences(Expression expression, IQuerySource querySource, Boolean inProjection) 
    at Microsoft.Data.Entity.Query.EntityQueryModelVisitor.CompileMainFromClauseExpression(MainFromClause mainFromClause, QueryModel queryModel) 
    at Microsoft.Data.Entity.Query.RelationalQueryModelVisitor.CompileMainFromClauseExpression(MainFromClause mainFromClause, QueryModel queryModel) 
    at Microsoft.Data.Entity.Query.EntityQueryModelVisitor.VisitMainFromClause(MainFromClause fromClause, QueryModel queryModel) 
    at Remotion.Linq.Clauses.MainFromClause.Accept(IQueryModelVisitor visitor, QueryModel queryModel) 
    at Remotion.Linq.QueryModelVisitorBase.VisitQueryModel(QueryModel queryModel) 
    at Microsoft.Data.Entity.Query.EntityQueryModelVisitor.VisitQueryModel(QueryModel queryModel) 
    at Microsoft.Data.Entity.Query.RelationalQueryModelVisitor.VisitQueryModel(QueryModel queryModel) 
    at Microsoft.Data.Entity.Query.Internal.SqlServerQueryModelVisitor.VisitQueryModel(QueryModel queryModel) 
    at Microsoft.Data.Entity.Query.EntityQueryModelVisitor.CreateAsyncQueryExecutor[TResult](QueryModel queryModel) 
    at Microsoft.Data.Entity.Storage.Database.CompileAsyncQuery[TResult](QueryModel queryModel) 
    at Microsoft.Data.Entity.Query.Internal.QueryCompiler.<>c__DisplayClass19_0`1.<CompileAsyncQuery>b__0() 
    at Microsoft.Data.Entity.Query.Internal.CompiledQueryCache.GetOrAddAsyncQuery[TResult](Object cacheKey, Func`1 compiler) 
    at Microsoft.Data.Entity.Query.Internal.QueryCompiler.CompileAsyncQuery[TResult](Expression query) 
    at Microsoft.Data.Entity.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken) 
Exception thrown: 'System.InvalidOperationException' in EntityFramework.Core.dll 
Exception thrown: 'System.InvalidOperationException' in mscorlib.ni.dll 
Exception thrown: 'System.InvalidOperationException' in mscorlib.ni.dll 
Microsoft.AspNet.Diagnostics.Entity.DatabaseErrorPageMiddleware:Verbose: System.InvalidOperationException occurred, checking if Entity Framework recorded this exception as resulting from a failed database operation. 
Microsoft.AspNet.Diagnostics.Entity.DatabaseErrorPageMiddleware:Verbose: Entity Framework recorded that the current exception was due to a failed database operation. Attempting to show database error page. 
Microsoft.Data.Entity.Storage.Internal.SqlServerConnection:Verbose: Opening connection 'Server=(localdb)\mssqllocaldb;Database=SpeediCargo;Trusted_Connection=True;MultipleActiveResultSets=true'. 
Microsoft.Data.Entity.Storage.Internal.SqlServerConnection:Verbose: Closing connection 'Server=(localdb)\mssqllocaldb;Database=SpeediCargo;Trusted_Connection=True;MultipleActiveResultSets=true'. 
Microsoft.Data.Entity.Storage.Internal.SqlServerConnection:Verbose: Opening connection 'Server=(localdb)\mssqllocaldb;Database=SpeediCargo;Trusted_Connection=True;MultipleActiveResultSets=true'. 
Microsoft.Data.Entity.Storage.Internal.SqlServerConnection:Verbose: Closing connection 'Server=(localdb)\mssqllocaldb;Database=SpeediCargo;Trusted_Connection=True;MultipleActiveResultSets=true'. 
Microsoft.Data.Entity.Storage.Internal.SqlServerConnection:Verbose: Opening connection 'Server=(localdb)\mssqllocaldb;Database=SpeediCargo;Trusted_Connection=True;MultipleActiveResultSets=true'. 
Microsoft.Data.Entity.Storage.Internal.RelationalCommandBuilderFactory:Information: Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] 
SELECT OBJECT_ID(N'__EFMigrationsHistory'); 
Microsoft.Data.Entity.Storage.Internal.SqlServerConnection:Verbose: Closing connection 'Server=(localdb)\mssqllocaldb;Database=SpeediCargo;Trusted_Connection=True;MultipleActiveResultSets=true'. 
Microsoft.Data.Entity.Storage.Internal.SqlServerConnection:Verbose: Opening connection 'Server=(localdb)\mssqllocaldb;Database=SpeediCargo;Trusted_Connection=True;MultipleActiveResultSets=true'. 
Microsoft.Data.Entity.Storage.Internal.RelationalCommandBuilderFactory:Information: Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] 

Es scheint, es ist nicht möglich, mit dem Rest der Linq-Abfrage SQL für den Auftrag von Teil zu mischen? Oder das Problem ist, dass ich nicht die Spalten mit dem [t] voranstellen und Entity nicht in der Lage zu verstehen, was sind diese Spalten?

Gibt es in jedem Fall ein Beispiel oder eine Empfehlung, wie ich mit Entity 7 und dem Core-.NET-Framework erreichen kann, was ich will?

+0

ich ein Teil der Anwendung hoffen ist für so etwas wie 'GetThingsQueryable Prüfung (neue Liste () {new SortModel() {ColId = "A", Sortieren = "; DROP TABLE Studenten"}) ' –

+0

Ich stimme zu Eric, ich sollte auf SQL-Injektionen achten – iberodev

+0

Ich denke' FromSql' erwartet eine vollständige SQL-Anweisung. Und es "mischt" sich nicht mit dem Vorherigen. –

Antwort

14

FromSql kann definitiv nicht verwendet werden, um SQL zu mischen. Wie bei dynamischen Abfragen üblich, müssen Sie auf System.Linq.Expressions zurückgreifen.

So etwas wie folgt aus:

public static class QueryableExtensions 
{ 
    public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, IEnumerable<SortModel> sortModels) 
    { 
     var expression = source.Expression; 
     int count = 0; 
     foreach (var item in sortModels) 
     { 
      var parameter = Expression.Parameter(typeof(T), "x"); 
      var selector = Expression.PropertyOrField(parameter, item.ColId); 
      var method = string.Equals(item.Sort, "desc", StringComparison.OrdinalIgnoreCase) ? 
       (count == 0 ? "OrderByDescending" : "ThenByDescending") : 
       (count == 0 ? "OrderBy" : "ThenBy"); 
      expression = Expression.Call(typeof(Queryable), method, 
       new Type[] { source.ElementType, selector.Type }, 
       expression, Expression.Quote(Expression.Lambda(selector, parameter))); 
      count++; 
     } 
     return count > 0 ? source.Provider.CreateQuery<T>(expression) : source; 
    } 
} 

Und dann:

var thingsQuery = _context.Things 
     .Include(t => t.Other) 
     .Where(t => t.Deleted == false) 
     .OrderBy(sortModels); 
+1

Gibt es eine Probe, die Erweiterung, wo Hinweis zeigen. – adopilot

+0

@adopilot Nicht sicher, ob ich der Frage folge. Fragen Sie nach dynamischen 'Wo'? –

+0

Ja, ich möchte Bulid Extenzion wie dieses versuchen, aber wo Anhaltspunkte. Ich möchte es auf dynamische Weise tun, wo du uns dynmaisch zeigst. – adopilot