应用基于多维数组的LINQfilter

给定entity framework查询,例如

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

我已应用filter,例如:

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

如何将“查询”进一步限制为仅包括与指定位置{大陆,国家,州(可选)}匹配的条目?

不幸的是,LINQ to Entities目前不支持对内存集合的连接,也不支持非原始内存集合的Contains 。 我看到的唯一方法(实际上还有另一个在这里描述的entity frameworkLINQ获取所有项目的另一个集合的一部分 ,但现在我认为这更合适)是使用一些表达式构建帮助器构造ORfilter。

例如,使用PredicateUtils类从linq中的两个列表到实体where子句之间建立链接 ,它可以是这样的:

首先,添加一个小帮助方法

 static Expression> 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 { public static readonly Expression> True = item => true; public static readonly Expression> False = item => false; } public static Expression> Null() { return null; } public static Expression> True() { return Predicate.True; } public static Expression> False() { return Predicate.False; } public static Expression> And(this Expression> left, Expression> right) { if (Equals(left, right)) return left; if (left == null || Equals(left, True())) return right; if (right == null || Equals(right, True())) return left; if (Equals(left, False()) || Equals(right, False())) return False(); var body = Expression.AndAlso(left.Body, right.Body.Replace(right.Parameters[0], left.Parameters[0])); return Expression.Lambda>(body, left.Parameters); } public static Expression> Or(this Expression> left, Expression> right) { if (Equals(left, right)) return left; if (left == null || Equals(left, False())) return right; if (right == null || Equals(right, False())) return left; if (Equals(left, True()) || Equals(right, True())) return True(); var body = Expression.OrElse(left.Body, right.Body.Replace(right.Parameters[0], left.Parameters[0])); return Expression.Lambda>(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 locations解决方案:

 var locationsFilter = locations.Select(location => { var filter = PredicateUtils.Null(); 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); 

这需要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不支持数组索引器,因此您可以将锯齿状数组转换为匿名类型的集合。 (从长远来看,最好创建一个用于实例化filter的类。这比记住每个索引所代表的元素更直观。)

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