mil*_*ose 20 c# linq-to-entities multiple-columns where-in
我正在尝试使用LINQ-to-EF查询表单的数据:
class Location {
string Country;
string City;
string Address;
…
}
Run Code Online (Sandbox Code Playgroud)
通过元组(国家,城市,地址)查找位置.我试过了
var keys = new[] {
new {Country=…, City=…, Address=…},
…
}
var result = from loc in Location
where keys.Contains(new {
Country=loc.Country,
City=loc.City,
Address=loc.Address
}
Run Code Online (Sandbox Code Playgroud)
但LINQ不希望接受匿名类型(我理解是在LINQ中表示元组的方式)作为Contains()的参数.
是否有一种"好"的方式在LINQ中表达这一点,同时能够在数据库上运行查询?或者,如果我只是迭代密钥和Union() - 一起编写查询,这会对性能有害吗?
怎么样:
var result = locations.Where(l => keys.Any(k =>
k.Country == l.Country &&
k.City == l.City &&
k.Address == l.Address));
Run Code Online (Sandbox Code Playgroud)
UPDATE
不幸的是,EF会抛出NotSupportedException,如果您需要在DB端运行查询,则会取消此答案的资格.
更新2
尝试使用自定义类和元组的各种连接 - 两者都不起作用.我们在谈论什么数据量?如果它不是太大,你可以在客户端处理(方便)或使用联合(如果不是更快,至少传输的数据更少).
小智 5
我的解决方案是构建一个新的扩展方法WhereOr,它使用ExpressionVisitor来构建查询:
public delegate Expression<Func<TSource, bool>> Predicat<TCle, TSource>(TCle cle);
public static class Extensions
{
public static IQueryable<TSource> WhereOr<TSource, TCle>(this IQueryable<TSource> source, IEnumerable<TCle> cles, Predicat<TCle, TSource> predicat)
where TCle : ICle,new()
{
Expression<Func<TSource, bool>> clause = null;
foreach (var p in cles)
{
clause = BatisseurFiltre.Or<TSource>(clause, predicat(p));
}
return source.Where(clause);
}
}
class BatisseurFiltre : ExpressionVisitor
{
private ParameterExpression _Parametre;
private BatisseurFiltre(ParameterExpression cle)
{
_Parametre = cle;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return _Parametre;
}
internal static Expression<Func<T, bool>> Or<T>(Expression<Func<T, bool>> e1, Expression<Func<T, bool>> e2)
{
Expression<Func<T, bool>> expression = null;
if (e1 == null)
{
expression = e2;
}
else if (e2 == null)
{
expression = e1;
}
else
{
var visiteur = new BatisseurFiltre(e1.Parameters[0]);
e2 = (Expression<Func<T, bool>>)visiteur.Visit(e2);
var body = Expression.Or(e1.Body, e2.Body);
expression = Expression.Lambda<Func<T, bool>>(body, e1.Parameters[0]);
}
return expression;
}
}
Run Code Online (Sandbox Code Playgroud)
以下生成在数据库上执行的干净的sql代码:
var result = locations.WhereOr(keys, k => (l => k.Country == l.Country &&
k.City == l.City &&
k.Address == l.Address
)
);
Run Code Online (Sandbox Code Playgroud)
虽然我无法获得@ YvesDarmaillac的代码,但它指出了这个解决方案.
您可以构建表达式,然后单独添加每个条件.为此,您可以使用Universal PredicateBuilder(最后的源代码).
这是我的代码:
// First we create an Expression. Since we can't create an empty one,
// we make it return false, since we'll connect the subsequent ones with "Or".
// The following could also be: Expression<Func<Location, bool>> condition = (x => false);
// but this is clearer.
var condition = PredicateBuilder.Create<Location>(x => false);
foreach (var key in keys)
{
// each one returns a new Expression
condition = condition.Or(
x => x.Country == key.Country && x.City == key.City && x.Address == key.Address
);
}
using (var ctx = new MyContext())
{
var locations = ctx.Locations.Where(condition);
}
Run Code Online (Sandbox Code Playgroud)
但要注意的一点是,过滤器列表(keys本例中的变量)不能太大,或者您可能达到参数限制,例外情况如下:
SqlException:传入的请求包含太多参数.服务器最多支持2100个参数.减少参数数量并重新发送请求.
因此,在此示例中(每行有三个参数),您不能有超过700个要过滤的位置.
使用两个项进行过滤,它将在最终的SQL中生成6个参数.生成的SQL将如下所示(格式化为更清晰):
exec sp_executesql N'
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Country] AS [Country],
[Extent1].[City] AS [City],
[Extent1].[Address] AS [Address]
FROM [dbo].[Locations] AS [Extent1]
WHERE
(
(
([Extent1].[Country] = @p__linq__0)
OR
(([Extent1].[Country] IS NULL) AND (@p__linq__0 IS NULL))
)
AND
(
([Extent1].[City] = @p__linq__1)
OR
(([Extent1].[City] IS NULL) AND (@p__linq__1 IS NULL))
)
AND
(
([Extent1].[Address] = @p__linq__2)
OR
(([Extent1].[Address] IS NULL) AND (@p__linq__2 IS NULL))
)
)
OR
(
(
([Extent1].[Country] = @p__linq__3)
OR
(([Extent1].[Country] IS NULL) AND (@p__linq__3 IS NULL))
)
AND
(
([Extent1].[City] = @p__linq__4)
OR
(([Extent1].[City] IS NULL) AND (@p__linq__4 IS NULL))
)
AND
(
([Extent1].[Address] = @p__linq__5)
OR
(([Extent1].[Address] IS NULL) AND (@p__linq__5 IS NULL))
)
)
',
N'
@p__linq__0 nvarchar(4000),
@p__linq__1 nvarchar(4000),
@p__linq__2 nvarchar(4000),
@p__linq__3 nvarchar(4000),
@p__linq__4 nvarchar(4000),
@p__linq__5 nvarchar(4000)
',
@p__linq__0=N'USA',
@p__linq__1=N'NY',
@p__linq__2=N'Add1',
@p__linq__3=N'UK',
@p__linq__4=N'London',
@p__linq__5=N'Add2'
Run Code Online (Sandbox Code Playgroud)
注意初始"false"表达式是如何被正确忽略的,并且不包含在EntityFramework的最终SQL中.
最后,这里是Universal PredicateBuilder的代码,用于记录.
/// <summary>
/// Enables the efficient, dynamic composition of query predicates.
/// </summary>
public static class PredicateBuilder
{
/// <summary>
/// Creates a predicate that evaluates to true.
/// </summary>
public static Expression<Func<T, bool>> True<T>() { return param => true; }
/// <summary>
/// Creates a predicate that evaluates to false.
/// </summary>
public static Expression<Func<T, bool>> False<T>() { return param => false; }
/// <summary>
/// Creates a predicate expression from the specified lambda expression.
/// </summary>
public static Expression<Func<T, bool>> Create<T>(Expression<Func<T, bool>> predicate) { return predicate; }
/// <summary>
/// Combines the first predicate with the second using the logical "and".
/// </summary>
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
{
return first.Compose(second, Expression.AndAlso);
}
/// <summary>
/// Combines the first predicate with the second using the logical "or".
/// </summary>
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
{
return first.Compose(second, Expression.OrElse);
}
/// <summary>
/// Negates the predicate.
/// </summary>
public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expression)
{
var negated = Expression.Not(expression.Body);
return Expression.Lambda<Func<T, bool>>(negated, expression.Parameters);
}
/// <summary>
/// Combines the first expression with the second using the specified merge function.
/// </summary>
static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
{
// zip parameters (map from parameters of second to parameters of first)
var map = first.Parameters
.Select((f, i) => new { f, s = second.Parameters[i] })
.ToDictionary(p => p.s, p => p.f);
// replace parameters in the second lambda expression with the parameters in the first
var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);
// create a merged lambda expression with parameters from the first expression
return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
}
class ParameterRebinder : ExpressionVisitor
{
readonly Dictionary<ParameterExpression, ParameterExpression> map;
ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
{
this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
}
public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
{
return new ParameterRebinder(map).Visit(exp);
}
protected override Expression VisitParameter(ParameterExpression p)
{
ParameterExpression replacement;
if (map.TryGetValue(p, out replacement))
{
p = replacement;
}
return base.VisitParameter(p);
}
}
}
Run Code Online (Sandbox Code Playgroud)