C# LINQ to Entities-其中..in子句具有多列
我正在尝试使用LINQ to EF查询表单的数据:C# LINQ to Entities-其中..in子句具有多列,c#,linq-to-entities,multiple-columns,where-in,C#,Linq To Entities,Multiple Columns,Where In,我正在尝试使用LINQ to EF查询表单的数据: class Location { string Country; string City; string Address; … } 通过元组查找位置(国家、城市、地址)。我试过了 但是LINQ不想接受匿名类型(我知道这是LINQ中表示元组的方法)作为Contains()的参数 在能够在数据库上运行查询的同时,是否有一种“好”的方式在LINQ中表达这一点?或者,如果我只是对键进行迭代,然后Union()将查询合并
class Location {
string Country;
string City;
string Address;
…
}
通过元组查找位置(国家、城市、地址)。我试过了
但是LINQ不想接受匿名类型(我知道这是LINQ中表示元组的方法)作为Contains()的参数
在能够在数据库上运行查询的同时,是否有一种“好”的方式在LINQ中表达这一点?或者,如果我只是对键进行迭代,然后Union()将查询合并在一起,这会对性能造成影响吗
var result = from loc in Location
where keys.Contains(new {
Country=l.Country,
City=l.City,
Address=l.Address
}
需要:
var result = from loc in Location
where keys.Contains(new {
Country=loc.Country,
City=loc.City,
Address=loc.Address
}
select loc;
那么:
var result = locations.Where(l => keys.Any(k =>
k.Country == l.Country &&
k.City == l.City &&
k.Address == l.Address));
更新
不幸的是,EF在这个问题上抛出NotSupportedException,如果您需要在DB端运行查询,这将取消这个答案的资格
更新2
尝试了使用自定义类和元组的各种连接-两者都不起作用。我们谈论的是什么数据量?如果不是太大,您可以在客户端(方便)处理它,或者使用联合(如果不是更快,至少传输更少的数据)。我认为这对您不起作用,因为在
Contains
方法中新建对象时,每次都会创建一个新对象。由于这些对象是匿名的,因此它们的比较方式与它们的引用不同,每个对象的引用也不同
另外,请看Jacek的答案。您是否尝试过仅使用Tuple类
var keys = new[] {
Tuple.Create("Country", "City", "Address"),
…
}
var result = from loc in Location
where keys.Contains(Tuple.Create(loc.Country, loc.City, loc.Address))
试试这个 我认为正确的方法是
var result = from loc in Location
where loc.Country = _country
where loc.City = _city
where loc.Address = _address
select loc
它看起来未优化,但查询提供程序将在将查询转换为sql时进行优化。当使用元组或其他类时,查询提供程序不知道如何将它们转换为sql,也不知道是什么导致NotSupportedException
-编辑-
如果您有多个关键元组,我认为您必须循环遍历它们,并对每个元组执行上述查询。同样,这似乎没有得到充分优化,但在单个查询中检索所有位置的查询可能会持续很长时间:
select * from locations
where (locations.Country = @country1 and locations.City = @city1, locations.Adress = @adress1)
or (locations.Country = @country2 and locations.City = @city2, locations.Adress = @adress2)
or ...
最快的方法可能是执行简单的查询,但将它们作为单个sql脚本发送,并使用多个结果集实际获取每个值。不过,我不确定您是否可以让EF这样做。如果您不需要很多组合键,您可以简单地在数据中添加一个
LocationKey
属性。为了避免浪费大量存储空间,可以将其作为组合属性的哈希代码
然后,查询将只在LocationKey
上有一个条件。最后,在客户端筛选结果以删除具有相同哈希但不具有相同位置的实体
它看起来像:
class Location
{
private string country;
public string Country
{
get { return country; }
set { country = value; UpdateLocationKey(); }
}
private string city;
public string City
{
get { return city; }
set { city = value; UpdateLocationKey(); }
}
private string address;
public string Address
{
get { return address; }
set { address = value; UpdateLocationKey(); }
}
private void UpdateLocationKey()
{
LocationKey = Country.GetHashCode() ^ City.GetHashCode() ^ Address.GetHashCode();
}
int LocationKey;
…
}
然后只需查询LocationKey属性
不太理想,但它应该可以工作。我将用更广泛的IEnumerable的Any扩展方法替换Contains(这是一种特定于列表和数组的方法):
var result = Location
.Where(l => keys.Any(k => l.Country == k.Country && l.City = k.City && l.Address == k.Address);
这也可以写成:
var result = from l in Location
join k in keys
on l.Country == k.Country && l.City == k.City && l.Address == k.Address
select l;
我的解决方案是构建一个新的扩展方法,其中使用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;
}
}
虽然我无法让@YvesDarmaillac的代码正常工作,但它为我指明了这个解决方案 您可以构建一个表达式,然后分别添加每个条件。要做到这一点,您可以使用UniversalPredicateBuilder(末尾的源代码) 这是我的密码:
// 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);
}
请注意,初始的“false”表达式是如何被正确忽略的,并且没有包含在最终的SQL by EntityFramework中
最后,这里是用于记录的代码
/// <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);
}
}
}
//
///支持查询谓词的高效、动态组合。
///
公共静态类谓词生成器
{
///
///创建一个计算结果为true的谓词。
///
公共静态表达式True(){return param=>True;}
///
///创建计算结果为false的谓词。
///
公共静态表达式False(){return param=>False;}
///
///从指定的lambda表达式创建谓词表达式。
///
公共静态表达式创建(表达式谓词){返回谓词;}
///
///使用逻辑“and”组合第一个谓词和第二个谓词。
///
公共静态表达式和(第一个表达式,第二个表达式)
{
首先返回.Compose(第二个是Expression.AndAlso);
}
///
///使用逻辑“or”组合第一个谓词和第二个谓词。
///
公共静态表达式或(第一个表达式,第二个表达式)
{
首先返回.Compose(第二个是Expression.OrElse);
}
///
///否定谓词。
///
公共静态表达式不是(此表达式)
{
var negated=Expression.Not(Expression.Body);
返回表达式.Lambda(否定,表达式.Parameters);
}
///
///使用指定的合并函数将第一个表达式与第二个表达式合并。
///
静态表达式组合(先此表达式,后表达式,Func merge)
{
//zip参数(从第二个参数映射到第一个参数)
var map=first.Parameters
.Select((f,i)=>new{f,s=second.Parameters[i]})
.ToDictionary(p=>p.s,p=>p.f);
//用第一个lambda表达式中的参数替换第二个lambda表达式中的参数
var secondBody=ParameterRebinder.ReplaceParameters(映射,second.Body);
//使用第一个表达式中的参数创建合并的lambda表达式
返回表达式.Lambda(merge(first.Body,secondBody),first.Parameters);
}
类参数reBinder:ExpressionVisitor
{
只读字典地图;
ParameterRebinder(字典映射)
{
this.map=map??新建字典();
}
公共静态表达式替换参数(字典映射、表达式表达式)
{
返回新参数浏览器(map)。访问(exp);
}
受保护的覆盖表达式
var result = locations.WhereOr(keys, k => (l => k.Country == l.Country &&
k.City == l.City &&
k.Address == l.Address
)
);
// 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);
}
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'
/// <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);
}
}
}
var keys = new[] {
new {Country=…, City=…, Address=…},
…
}
// here is the important part!
var keysQueryable = context.FromLocalList(keys);
var result = from loc in Location
join key in keysQueryable on new { loc.Country, loc.City, loc.Address } equals new { key.Country, key.City, key.Address }
select loc
var stringKeys = keys
.Select(l => $"{l.Country}-{l.City}-{l.Address}")
.ToList();
var result = locations
.Select(l => new
{
Key = l.Country + "-" + l.City + "-" + l.Address)
}
.Where(l => stringKeys.Contains(l.Key))
.ToList();
class Location {
string Country;
string City;
string Address;
…
}
var keys = new[] {
new {Country=…, City=…, Address=…},
…
}
from loc in Location where (
from k in keys where k.Country==loc.Country && k.City==loc.City && k.Address=loc.Address select 1).Any()
FROM [Locations] AS [p0]
WHERE (NOT (EXISTS (
SELECT 1
FROM [Keys] AS [p1]
WHERE [p0].[Country] = [p1].[Country]) AND ([p0].[City] = [p1].[City]) AND ([p0].[Address]=[p1].[Address])))