簡體   English   中英

應用基於多維數組的LINQ過濾器

[英]Applying LINQ filters based on a multi-dimensional 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,
});

我已應用過濾器,例如:

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

給出以下多維數組

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

如何將“查詢”進一步限制為僅包括與指定位置{大陸,國家,州(可選)}匹配的條目?

這需要SQL中所謂的相關子查詢。 假設它們總是占據相同的位置,您可以使用數組索引器來訪問locations鋸齒數組中的元素。

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

更新 :由於LINQ to Entities不支持數組索引器,因此您可以將鋸齒狀數組轉換為匿名類型的集合。 (從長遠來看,最好創建一個用於實例化過濾器的類。這比記住每個索引所代表的元素更直觀。)

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

不幸的是,LINQ to Entities目前不支持對內存集合的連接,也不支持非原始內存集合的Contains 我看到的唯一方法(實際上還有另一個在這里描述的實體框架LINQ獲取所有項目的另一個集合的一部分 ,但現在我認為這更合適)是使用一些表達式構建幫助器來構造OR過濾器。

例如,使用PredicateUtils類從linq中的兩個列表到實體where子句之間建立鏈接 ,它可以是這樣的:

首先,添加一個小幫助方法

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

然后使用

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

為了完整性,這里是使用的助手類:

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

更新:根據評論中的要求,以下是List<Location> locations解決方案:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM