2016-04-09 10 views
1

eine Entity Framework Abfrage gegeben, wieAnwenden von LINQ Filter basierend auf einem mehrdimensionalen Array

var query = (from property in _dbContext.Properties 
     join location in _db.Locations 
      on property.Id equals location.PropertyId 
select new PropertyDetail 
{ 
    Url = property.Url, 
    Type = property.Type, 
    Title = property.Title, 
    Continent = location.Continent, 
    Country = location.Country, 
    State = location.State, 
}); 

Ich habe Filter angewendet wie:

if (!string.IsNullOrWhitespace(searchFilters.Type)) 
{ 
    query = query.Where(model => model.Type == searchFilters.Type); 
} 
if (!string.IsNullOrWhitespace(searchFilters.Title)) 
{ 
    query = query.Where(model => model.Title.Contains(searchFilters.Title)); 
} 

Angesichts der folgenden mehrdimensionales Array

var locations = new[] 
{ 
    new[] {"Africa", "Algeria", ""}, 
    new[] {"Asia", "Hong Kong", ""}, 
    new[] {"Asia", "Singapore", ""}, 
    new[] {"Oceania", "Australia", "New South Wales"}, 
    new[] {"North America", "United States", "California"} 
}; 

Wie kann die "Abfrage" weiter eingeschränkt werden, nur die Einträge, die den angegebenen Speicherort entsprechen s {Kontinent, Land, Bundesland (optional)}?

Antwort

0

Leider LINQ to Entities unterstützt derzeit keine verbindet sich zu inmemory Sammlung, noch Contains für nicht primitive inmemory Sammlung. Der einzige Weg, den ich sehe (tatsächlich gibt es einen anderen, der hier beschrieben wird Entity Framework LINQ Get all items part of another collection, aber jetzt denke ich, dass dies geeigneter ist), ist OR Filter mit einigen Ausdruck Build Helper zu konstruieren.

Zum Beispiel mit der PredicateUtils Klasse von Establish a link between two lists in linq to entities where clause, könnte es so aussehen:

Zuerst eine wenig Hilfsmethode

static Expression<Func<PropertyDetail, bool>> LocationFilter(string value, int index) 
{ 
    if (!string.IsNullOrEmpty(value)) 
    { 
     if (index == 0) return d => d.Continent == value; 
     if (index == 1) return d => d.Country == value; 
     if (index == 2) return d => d.State == value; 
    } 
    return null; 
} 

hinzufügen und dann

var locationsFilter = locations.Select(location => location.Select(LocationFilter) 
    .Aggregate(PredicateUtils.And)).Aggregate(PredicateUtils.Or); 
if (locationsFilter != null) 
    query = query.Where(locationsFilter); 

Für Vollständigkeit verwenden Hier ist die verwendete Hilfsklasse:

public static class PredicateUtils 
{ 
    sealed class Predicate<T> 
    { 
     public static readonly Expression<Func<T, bool>> True = item => true; 
     public static readonly Expression<Func<T, bool>> False = item => false; 
    } 
    public static Expression<Func<T, bool>> Null<T>() { return null; } 
    public static Expression<Func<T, bool>> True<T>() { return Predicate<T>.True; } 
    public static Expression<Func<T, bool>> False<T>() { return Predicate<T>.False; } 
    public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right) 
    { 
     if (Equals(left, right)) return left; 
     if (left == null || Equals(left, True<T>())) return right; 
     if (right == null || Equals(right, True<T>())) return left; 
     if (Equals(left, False<T>()) || Equals(right, False<T>())) return False<T>(); 
     var body = Expression.AndAlso(left.Body, right.Body.Replace(right.Parameters[0], left.Parameters[0])); 
     return Expression.Lambda<Func<T, bool>>(body, left.Parameters); 
    } 
    public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right) 
    { 
     if (Equals(left, right)) return left; 
     if (left == null || Equals(left, False<T>())) return right; 
     if (right == null || Equals(right, False<T>())) return left; 
     if (Equals(left, True<T>()) || Equals(right, True<T>())) return True<T>(); 
     var body = Expression.OrElse(left.Body, right.Body.Replace(right.Parameters[0], left.Parameters[0])); 
     return Expression.Lambda<Func<T, bool>>(body, left.Parameters); 
    } 

    static Expression Replace(this Expression expression, Expression source, Expression target) 
    { 
     return new ExpressionReplacer { Source = source, Target = target }.Visit(expression); 
    } 

    class ExpressionReplacer : ExpressionVisitor 
    { 
     public Expression Source; 
     public Expression Target; 
     public override Expression Visit(Expression node) 
     { 
      return node == Source ? Target : base.Visit(node); 
     } 
    } 
} 

UPDATE: Wie in den Kommentaren aufgefordert, hier ist die Lösung für locationsList<Location> sein:

var locationsFilter = locations.Select(location => 
{ 
    var filter = PredicateUtils.Null<PropertyDetail>(); 
    if (!string.IsNullOrEmpty(location.Continent)) 
     filter = filter.And(d => d.Continent == location.Continent); 
    if (!string.IsNullOrEmpty(location.Country)) 
     filter = filter.And(d => d.Country == location.Country); 
    if (!string.IsNullOrEmpty(location.State)) 
     filter = filter.And(d => d.State == location.State); 
    return filter; 
}).Aggregate(PredicateUtils.Or); 
+0

Danke Ivan, das scheint zu funktionieren (ich habe buchstäblich Stunden damit verbracht, damit zu kämpfen und das ist die erste Lösung, die funktioniert hat). Es ist jedoch möglich, dies mit einer neuen Liste () zu arbeiten, da es viel einfacher wäre, diese Liste aufzubauen, anstatt ein mehrdimensionales Array zu verwenden, wie zum Beispiel: var locations = new [] –

+0

@MarkAtkins Bitte schön. Und ja, es ist möglich, siehe das Update. –

+0

Danke Ivan, du hast mein schwieriges Problem gelöst. Ich denke nicht, dass ich mir das selbst leicht ausgedacht hätte. Du bist ein Star :) –

0

Dies erfordert eine korrelierte Unterabfrage in SQL. Unter der Annahme, dass sie immer dieselbe Position einnehmen, können Sie Array-Indexer verwenden, um auf die Elemente in Ihrem gezackten Array zuzugreifen.

query = query.Where(model => 
    locations.Any(location => 
     location[0] == model.Continent && 
     location[1] == model.Country && 
     (string.IsNullOrEmpty(location[2]) || location[2] == model.State))); 

aktualisieren: Da LINQ to Entities nicht Array Indexer unterstützen, könnten Sie Ihre gezackten Array in eine Sammlung von anonymen Typen konvertieren. (Auf lange Sicht wäre es besser sein, eine Klasse zu erstellen, für Ihre Filter instanziieren. Dies wäre intuitiver als zu erinnern, was die Elemente an jedem Index repräsentieren.)

var locationsTyped = 
    locations.Select(location => new 
    { 
     Continent = location[0], 
     Country = location[1], 
     State = location[2], 
    }).ToArray(); 

query = query.Where(model => 
    locationsTyped.Any(location => 
     location.Continent == model.Continent && 
     location.Country == model.Country && 
     (string.IsNullOrEmpty(location.State) || location.State == model.State))); 
+0

Danke, das aussah, was ich suchte. Zur Laufzeit erhalte ich jedoch den folgenden Fehler: Der LINQ-Ausdrucksknotentyp 'ArrayIndex' wird in LINQ to Entities nicht unterstützt. –

+0

@MarkAtkins: Ich habe etwas (ungetesteten) Code hinzugefügt, um das gezackte Array in einen anonymen Typ umzuwandeln; Schau, ob das funktioniert. – Douglas

+0

Danke @Douglas, aber wieder hatte ich kein Glück damit, es schien den anonymen Typ nicht zu mögen. Also habe ich versucht, eine tatsächliche Klasse zu verwenden, um die Details in einer neuen Liste () zu speichern, aber dies hat den Laufzeitfehler gegeben: Konnte keinen konstanten Wert vom Typ 'MyNamespace.SearchFilters' erstellen. In diesem Kontext werden nur primitive Typen oder Aufzählungstypen unterstützt. –

Verwandte Themen