5

Ho un progetto in cui il front-end JavaScript specifica un elenco di colonne da ordinare.Come ordinare dinamicamente da determinate proprietà dell'entità in EntityFramework 7 (Core)

Quindi nel back-end ho un'applicazione multistrato. scenario tipico

  1. livello di servizio (i modelli di servizio (DTO) proprietà corrispondono qualunque sia il lato client vuole ordinare da)
  2. strato Domain (espone interfacce repository per accedere agli oggetti persistenti) strato
  3. ORM (implementa il repository e utilizza Entity Framework 7 (aka Entity Framework core) per accedere a un database di SQL Server)

prega di notare che System.Linq.Dynamic non è supportata per DNX Nucleo v5.0 o. NET Platform v5.4 quindi non posso usare quella biblioteca

Ho la seguente applicazione nel mio repository cose:

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; 
    } 

e questo è l'oggetto che contiene un nome di colonna e un ordine dalla direzione (crescente o decrescente)

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

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

Questo approccio tenta di combinare un'istruzione SQL con qualsiasi entità creata per l'interrogazione precedente. Ma ottengo un:

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'] 

Sembra che non sia possibile combinare SQL per l'ordine per parte con il resto della query di linq? Oppure il problema è che non sto anteponendo le colonne con [t] e Entity non è in grado di capire quali sono quelle colonne?

In ogni caso, c'è qualche esempio o raccomandazione su come ottenere ciò che voglio con Entity 7 e con il framework core .net?

+0

Spero che una parte della domanda sta verificando qualcosa di simile a 'GetThingsQueryable (nuova Lista () {new SortModel() {ColId = "A", Sort = "; DROP TABLE Studenti"}) ' –

+0

Sono d'accordo Eric, dovrei essere consapevole delle iniezioni SQL – iberodev

+0

Penso che' FromSql' si aspetta un'istruzione SQL completa. E non "mescola" con il precedente. –

risposta

14

FromSql sicuramente non può essere utilizzato per mescolare SQL. Così come al solito con le query dinamiche, è necessario ricorrere a System.Linq.Expressions.

Per esempio, qualcosa di simile:

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; 
    } 
} 

E poi:

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

C'è qualche campione che mostra estendere dove indizio. – adopilot

+0

@adopilot Non sono sicuro di seguire la domanda. Stai chiedendo un dinamico 'Where'? –

+0

Sì, voglio provare l'extenzion bulid come questo ma per gli indizi. Voglio provare dinamicamente dove lo stesso modo in cui ci fai vedere dinamicamente. – adopilot

Problemi correlati