LINQ to Entities - 具有多列的 where..in 子句
来查询表单的数据
class Location {
string Country;
string City;
string Address;
…
}
我正在尝试使用 LINQ-to-EF:通过按元组(国家、城市、地址)查找位置 。我尝试过
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
}
,但 LINQ 不想接受匿名类型(我理解这是在 LINQ 中表达元组的方式)作为 Contains() 的参数。
有没有一种“好”的方式在 LINQ 中表达这一点,同时能够在数据库上运行查询?或者,如果我只是迭代键和 Union() 一起查询,这会对性能不利吗?
I'm trying to query data of the form with LINQ-to-EF:
class Location {
string Country;
string City;
string Address;
…
}
by looking up a location by the tuple (Country, City, Address). I tried
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
}
but LINQ doesn't want to accept an anonymous type (which I understand is the way to express tuples in LINQ) as the parameter to Contains().
Is there a "nice" way to express this in LINQ, while being able to run the query on the database? Alternately, if I just iterated over keys and Union()-ed the queries together, would that be bad for performance?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(13)
我将用更广泛的 IEnumerable 的 Any 扩展方法替换 Contains (这是一种特定于列表和数组的方法):
也可以这样写:
I'd replace Contains (which is a method specific to lists and arrays) with the wider IEnumerable's Any extension method:
This can also be written:
怎么样:
更新
不幸的是,EF 会抛出 NotSupportedException,如果您需要查询在数据库端运行,这会取消此答案的资格。
更新2
尝试使用自定义类和元组进行各种连接 - 都不起作用。我们谈论的数据量是多少?如果它不是太大,您可以在客户端处理它(方便)或使用联合(如果不是更快,至少传输的数据更少)。
How about:
UPDATE
Unfortunately EF throws NotSupportedException on that, which disqualifies this answer if you need the query to run on DB side.
UPDATE 2
Tried all kinds of joins using custom classes and Tuples - neither works. What data volumes are we talking about? If it's nothing too big, you could either process it client-side (convenient) or use unions (if not faster, at least less data is transmitted).
虽然我无法让@YvesDarmaillac 的代码工作,但它向我指出了这个解决方案。
您可以构建一个表达式,然后分别添加每个条件。为此,您可以使用Universal PredicateBuilder(源代码在最后)。
这是我的代码:
不过,需要注意的一件事是过滤器列表(本例中的
keys
变量)不能太大,否则您可能会达到参数限制,但有例外像这样:因此,在此示例中(每行三个参数),要过滤的位置不能超过 700 个。
使用两项进行过滤,最终的SQL中会生成6个参数。生成的 SQL 如下所示(格式更清晰):
请注意 EntityFramework 如何正确忽略初始“false”表达式,并且不将其包含在最终 SQL 中。
最后,这是 Universal PredicateBuilder 的代码,用于记录。
Although I couldn't get @YvesDarmaillac's code to work, it pointed me to this solution.
You can build an expression and then add each condition separately. To do this, you can use the Universal PredicateBuilder (source at the end).
Here's my code:
One thing to beware of, though, is that the filter list (the
keys
variable in this example) can't be too large, or you may reach the parameters limit, with an exception like this:So, in this example (with three parameters per line), you can't have more than 700 Locations to filter.
Using two items to filter, it will generate 6 parameters in the final SQL. The generated SQL will look like below (formatted to be clearer):
Notice how the initial "false" expression is properly ignored and not included in the final SQL by EntityFramework.
Finally, here's the code for the Universal PredicateBuilder, for the record.
我的解决方案是构建一个新的扩展方法WhereOr,它使用ExpressionVisitor来构建查询:
以下生成在数据库上执行的干净的sql代码:
My solution is to build a new extension method WhereOr which use an ExpressionVisitor to build the query :
The following generates clean sql code executed on database :
需要是:
would need to be:
存在一个 EF 扩展,其设计用于非常相似的情况。它是 EntityFrameworkCore.MemoryJoin (名称可能会令人困惑,但它支持 EF6 和 EF核)。正如作者的文章 它修改传递到服务器的 SQL 查询,并使用本地列表中的数据注入 VALUES 构造。并且查询是在DB服务器上执行的。
所以对于你的情况,用法可能是这样的
There is an EF extension exists which was designed to very similar case. It is EntityFrameworkCore.MemoryJoin (name might be confusing, but it supports both EF6 and EF Core). As stated in author's article it modifies SQL query passed to server and injects VALUES construction with data from your local list. And query is executed on DB server.
So for your case usage might be like this
您是否尝试过仅使用 Tuple 类?
Have you tried just using the Tuple class?
如果您不需要大量组合键,则只需向数据添加
LocationKey
属性即可。为了避免浪费大量存储空间,可以将其设为组合属性的哈希码。然后查询将仅在
LocationKey
上有一个条件。最后,在客户端过滤结果以删除具有相同哈希但位置不同的实体。它看起来像:
然后简单地查询 LocationKey 属性。
不理想,但应该可以。
If you're not going to need a lot of key combinations, you can simply add a
LocationKey
property to your data. To avoid wasting a lot of storage, maybe make it the hash code of the combined properties.Then query on will simply have a condition on
LocationKey
. Finally, in the client side filter the results to drop entities that had the same hash but not the same location.It would look something like:
Then simply query on the LocationKey property.
Not ideal, but it should work.
我认为这对您不起作用,因为当您在
Contains
方法中新建对象时,它每次都会创建一个新对象。由于这些对象是匿名的,因此将它们与它们的引用进行比较的方式对于每个对象来说都是不同的。另外,看看雅切克的回答。
I don't think that will work for you since when you are newing up an object in the
Contains
method it will create a new object each time. Since those object are anonymous the way they will be compared are against their reference which will be different for each object.Also, look at Jacek's answer.
尝试一下。
Give this a try.
我认为正确的方法是
它看起来未优化,但查询提供程序将在将查询转换为 sql 时进行优化。当使用元组或其他类时,查询提供程序不知道如何将它们转换为 sql 以及导致 NotSupportedException 的原因
-编辑-
如果您有多个关键元组,我认为您必须循环遍历它们并对每个元组执行上述查询。同样,这可能看起来没有优化,但是在单个查询中检索所有位置的查询可能会变得很长:
最快的方法可能是执行简单的查询,但将它们作为单个 sql 脚本发送,使用多个结果集来实际获取每个值。不过我不确定你能否让 EF 做到这一点。
i think the proper way to do it is
It looks unoptimized but the query provider will go out and do the optimization when it transforms the query to sql. When using tuples or other classes, the query provider doesnt know how to transform them into sql and that what causes the NotSupportedException
-edit-
If you have multiple key tuples i think you have to loop through them all and do the above query for each one. again, that might seem underoptimized, but the query for retriving all the locations in a single query would probably end up beeing quite long:
The fastest way of doing it is probably to do the simple queries, but send them as a single sql script and use multiple result sets for actually getting each value. Im not sure you can get EF to do that though.
您可以投影字符串连接键并在投影上进行匹配。但是,请注意,您将无法使用在列上构建的任何索引,并且将进行字符串匹配,这可能会很慢。
You can project a string concat key and match on the projection. However, do note that you will not be able to use any indexes built on the columns and will be doing a string match which could prove to be slow.
如何使用基于多列的 LINQ to SQL 检查是否存在
考虑:
您应该执行如下操作:
这将生成以下 SQL:
How to check if exists using LINQ to SQL based on multiple columns
Considering:
You should do something like this:
Which will produce the following SQL: