2016-06-19 14 views
0

Ich habe Produktentität:LINQ Filter exakte Übereinstimmungen wie SQL IN und Starts Kombination passt

public class Product : DomainBase 
{ 
    public virtual string Name { get; set; } 
} 

Und sollte es Option Produkte auswählen, indem Sie Filter, die eine Reihe von Namen enthält, wie:

public static IEnumerable<Product> SearchArrayQueryLinq(IEnumerable<string> names) 
    { 
     using (var session = Database.OpenSession()) 
     { 
      var products = session.Query<Product>(); 

      var result = products.Where(product => names.Any(name => product.Name.Contains(name))); 

      return result.ToList(); 
     } 
    } 

aber es wirft

System.NotSupportedException: Die angegebene Methode wird nicht unterstützt.

Was ist richtig Ansatz, um solche Filterung zu erreichen?

Antwort

1

Ohne mehr zu wissen, welche Datenbank, die Sie oder welche Bibliothek Sie eine Verbindung (ist es RavenDB .. eine schnelle Google getan zu haben ?) dann ist es schwer, völlig sicher zu sein, was das Problem ist.

Ich glaube jedoch, dass Sie der IQueryable-Erweiterungsmethode "Where" einen Ausdruck geben, und die Bibliothek versucht, dies in Suchkriterien umzuwandeln, die gegen die Datenbank ausgeführt werden sollen, und fehlgeschlagen, weil "Any" wird in verschachtelten Kriterien wie dem nicht unterstützt (wieder, ich rate).

Die LINQ-Ausdrücke, die in die Datenbanksprache (z. B. SQL) übersetzt werden können oder nicht, variieren je nach der Bibliothek, die die Übersetzung durchführt, und variieren je nach der Datenbank, mit der gesprochen wird.

Zum Beispiel die folgenden (die im Grunde ist das, was Sie tun wollen) arbeitet mit Entity Framework fein:

private static void Test(IEnumerable<string> names) 
    { 
     using (var context = new NORTHWNDEntities()) 
     { 
      foreach (var product in context.Products.Where(product => names.Any(name => product.ProductName.Contains(name)))) 
      { 
       Console.WriteLine(product.ProductName); 
      } 
     } 
     Console.ReadLine(); 
    } 

Eine einfache Möglichkeit für Sie, Ihren Code zu

public static IEnumerable<Product> SearchArrayQueryLinq(IEnumerable<string> names) 
{ 
    using (var session = Database.OpenSession()) 
    { 
     var products = session.Query<Product>(); 
     return result = products.ToList().Where(product => names.Any(name => product.Name.Contains(name))); 
    } 
} 

zu ändern Dies sollte funktionieren .. es wird jedoch alle Produkte aus der Datenbank und führen Sie die Filterung im Speicher. Dies ist weniger effizient als die Datenbank die Suche durchführen zu lassen.

Eine Alternative wäre ein „Expression < Func < Produkt, bool > >“ zu erzeugen, selbst filtern das ist einfacher für die Bibliothek, die Sie zu übersetzen verwenden. Wenn stattdessen ein verschachteltes "Any" -Kriterium verwendet wird, könnten Sie einen einfachen Satz von "ODER" -Namensüberprüfungen erzeugen, dann gibt es eine bessere Änderung des Funktionsumfangs. Das Folgende wird das erreichen - aber es ist ziemlich viel Code. Wenn dies an mehreren Stellen erforderlich ist, kann ein Teil des Codes allgemeiner und wiederverwendet werden.

private static IEnumerable<Product> SearchArrayQueryLinq(IEnumerable<string> names) 
    { 
     using (var context = new NORTHWNDEntities()) 
     { 
      return context.Products.Where(GetCombinedOrFilter(names)).ToList(); 
     } 
    } 

    private static Expression<Func<Product, bool>> GetCombinedOrFilter(IEnumerable<string> names) 
    { 
     var filter = GetNameFilter(names.First()); 
     foreach (var name in names.Skip(1)) 
      filter = CombineFiltersAsOr(filter, GetNameFilter(name)); 
     return filter; 
    } 

    private static Expression<Func<Product, bool>> GetNameFilter(string name) 
    { 
     return product => product.ProductName.Contains(name); 
    } 

    private static Expression<Func<Product, bool>> CombineFiltersAsOr(Expression<Func<Product, bool>> x, Expression<Func<Product, bool>> y) 
    { 
     // Combine two separate expressions into one by combining as "Or". In order for this to work, instead of there being a parameter 
     // for each expression, the parameter from the first expression must be shared between them both (otherwise things will go awry 
     // when this is translated into a database query) - this is why ParameterRebinder.ReplaceParameters is required. 
     var expressionParameter = x.Parameters.Single(); 
     return Expression.Lambda<Func<Product, bool>>(
      Expression.Or(x.Body, ParameterRebinder.ReplaceParameters(y.Body, toReplace: y.Parameters.Single(), replaceWith: expressionParameter)), 
      expressionParameter 
     ); 
    } 

    // Borrowed and tweaked from https://blogs.msdn.microsoft.com/meek/2008/05/02/linq-to-entities-combining-predicates/ 
    public sealed class ParameterRebinder : ExpressionVisitor 
    { 
     public static Expression ReplaceParameters(Expression expression, ParameterExpression toReplace, ParameterExpression replaceWith) 
     { 
      return new ParameterRebinder(toReplace, replaceWith).Visit(expression); 
     } 

     private readonly ParameterExpression _toReplace, _replaceWith; 
     private ParameterRebinder(ParameterExpression toReplace, ParameterExpression replaceWith) 
     { 
      _toReplace = toReplace; 
      _replaceWith = replaceWith; 
     } 

     protected override Expression VisitParameter(ParameterExpression p) 
     { 
      if (p == _toReplace) 
       p = _replaceWith; 
      return base.VisitParameter(p); 
     } 
    } 

Update: Ich habe nicht Ihre nhibernate Tag bemerken - hoppla! Die Kriterien, die Methoden kombinieren, die nhibernate hat, ist wahrscheinlich einfacher als all das. :) Ich hätte Ihre Antwort kommentiert, anstatt meine eigene zu aktualisieren, aber ich habe noch nicht die erforderliche 50 rep.

+0

wow! Danke vielmals! –

0

Sie versuchen, beide Arten von Bedingungen zu mischen und IEnumerable Methoden auf Zeichenfolgeneigenschaften anzuwenden.

Ihre Abfrage sollte wie folgt aussehen:

var result = products.Where(product => names.Contains(product.Name)); 

exakte Übereinstimmungen zu finden.

Für eine Kombination von exakten Übereinstimmungen und StartsWith es soll wie folgt aussehen:

var results = products.Where(product => (names.Contains(product.Name) || names.Any(name => name.StartsWith(product.Name)))); 
0

Wie ich es tat Eintauchen in NHibenrate Dokumentation, enthält es CriteriaAPI, so kam ich auf diese

  using (var session = Database.OpenSession()) 
      { 
       var products = session.CreateCriteria<Product>(); 

       if (names == null) 
       { 
        return products.List<Product>(); 
       } 

       var orClause = Expression.Disjunction(); 

       foreach (var name in names) 
       { 
        orClause.Add(Expression.Like(nameof(Product.Name), name, MatchMode.Start)); 
       } 

       products.Add(orClause); 

       return products.List<Product>(); 
      }