简体   繁体   English

在Linq-to-Entities查询表达式中使用元组或其他复杂类型

[英]Using a tuple or some other complex type in a Linq-to-Entities query expression

So I want to search within a table of customers all of the customers which in each has its name, email address, or phone numbers match all of the query keywords. 因此,我想在客户表中搜索所有具有相同名称,电子邮件地址或电话号码的所有查询关键字都匹配的客户。

... Which is probably easier to understand in code than in English: ...用英语比用英语更容易理解:

public IQueryable<Contact> SearchCustomers(string query)
{
    var ws = from w in query.Split()
                where !String.IsNullOrWhiteSpace(w)
                select w;

    var q =
        from c in Customers
        where ws.All(w =>
                c.FirstName == w
                || c.LastName == w
                || c.EmailAddress == w
                || c.HomePhone == PhoneNumber.Pack(w)
                || c.CellPhone == PhoneNumber.Pack(w))
        select c;

    return q;
}

But I can't call PhoneNumber.Pack on the database, so I need to make w a format which will store both the raw value of w as well as the Pack ed value, and I have to do that on the client's side. 但是我无法在数据库上调用PhoneNumber.Pack ,因此我需要将w为既存储w的原始值又存储Pack ed值的格式,而我必须在客户端执行此操作。 The problem is that Linq doesn't like having tuples or arrays in the expression arguments, and it doesn't support String.IndexOf , so I can't throw two strings in one and then take substrings. 问题在于Linq不喜欢在表达式参数中包含元组或数组,并且不支持String.IndexOf ,因此我不能将两个字符串合而为一,然后接受子字符串。

Any other ways to get around this? 还有其他解决方法吗? Or maybe a restatement of the query? 还是重述查询?

Edit: The generated SQL looks like this: 编辑:生成的SQL看起来像这样:

SELECT 
[Extent1].[ID] AS [ID], 
[Extent1].[FirstName] AS [FirstName], 
[Extent1].[LastName] AS [LastName], 
(etc)
FROM [dbo].[Contacts] AS [Extent1]
WHERE ( NOT EXISTS (SELECT 
    1 AS [C1]
    FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]
    WHERE ( NOT ([Extent1].[FirstName] = N'rei' OR [Extent1].[LastName] = N'rei' OR [Extent1].[EmailAddress] = N'rei' OR [Extent1].[HomePhone] = N'rei' OR [Extent1].[CellPhone] = N'rei')) OR (CASE WHEN ([Extent1].[FirstName] = N'rei' OR [Extent1].[LastName] = N'rei' OR [Extent1].[EmailAddress] = N'rei' OR [Extent1].[HomePhone] = N'rei' OR [Extent1].[CellPhone] = N'rei') THEN cast(1 as bit) WHEN ( NOT ([Extent1].[FirstName] = N'rei' OR [Extent1].[LastName] = N'rei' OR [Extent1].[EmailAddress] = N'rei' OR [Extent1].[HomePhone] = N'rei' OR [Extent1].[CellPhone] = N'rei')) THEN cast(0 as bit) END IS NULL)
))
public IQueryable<Contact> SearchCustomers(string query)
{
    var ws = from w in query.Split()
                where !String.IsNullOrWhiteSpace(w)
                select new { Unpacked = w , Packed = PhoneNumber.Pack(w) };

    var q = Customers;
    foreach(var x in ws)
    {
        string ux = x.Unpacked;
        string px = x.Packed;
        q = q.Where(
               c=> 
                c.FirstName == ux
                || c.LastName == ux
                || c.EmailAddress == ux
                || c.HomePhone == px
                || c.CellPhone == px
            );
    }
    return q;
}

This will produce the desired result, and temp variable inside foreach will resolve your issue. 这将产生所需的结果,并且foreach中的temp变量将解决您的问题。

I'd create a private struct: 我将创建一个私有结构:

private struct UnpackedAndPacked
{
    public string Unpacked {get;set;}
    public string Packed {get;set;}
}

var ws = from w in query.Split()
         where !String.IsNullOrWhiteSpace(w)
         select new UnpackedAndPacked
                    {
                        Unpacked=w, 
                        Packed=PhoneNumber.Pack(w)
                    };  

Then change the condition: 然后更改条件:

    where ws.All(w => 
                 c.FirstName == w.Unpacked
                  || c.LastName == w.Unpacked
                  || c.EmailAddress == w.Unpacked
                  || c.HomePhone == w.Packed
                  || c.CellPhone == w.Packed)
    select c;

I looked into this further, and I think you're not going to get this done as-is. 我对此进行了进一步研究,我认为您不会按原样完成此工作。 The problem is that, because of the ws.All , it wants to create a set of SQL clauses once for each value in the ws sequence. 问题在于,由于ws.All ,它想为ws序列中的每个值创建一次SQL子句集。 It needs that to be a sequence of primitive types, like string. 它需要是原始类型的序列,例如字符串。

If you could change your code to have two query parameters, then I think it might work. 如果您可以将代码更改为具有两个查询参数,那么我认为它可能会起作用。 You'd need one set of parameters for the things that don't need packing, and one for those that do. 对于不需要打包的东西,您需要一组参数,对于需要打包的东西,则需要一组参数。 You would then change this into a LINQ methods chain and do a Union between the two. 然后,您可以将其更改为LINQ方法链,并在两者之间进行联合。 Example to follow. 以下示例。


It worked. 有效。 My code is below. 我的代码如下。 Note that I used the AdventureWorks2008R2 database, so mine is a bit more complicated than yours - I have a collection of email addresses and of phones to deal with; 请注意,我使用了AdventureWorks2008R2数据库,因此我的数据库要比您的数据库复杂一些-我有一组电子邮件地址和电话可以处理; a match on either of those is accepted: 可以匹配以下任何一个:

public static IQueryable<Person> SearchCustomers(
    AdventureWorksEntities entities, string nameQuery, string phoneQuery)
{
    var wsu = from w in nameQuery.Split()
        where !String.IsNullOrWhiteSpace(w)
        select w;
    var wsp = from w in phoneQuery.Split()
        where !String.IsNullOrWhiteSpace(w)
        select Pack(w);
    return
        entities.People.Where(
            c => wsu.All(w => c.FirstName == w || c.LastName == w)).
            Union(
                entities.People.Where(
                    c =>
                    wsp.All(
                        w =>
                        c.PersonPhones.Any(p => p.PhoneNumber == w) ||
                        c.EmailAddresses.Any(a => a.EmailAddress1 == w))));
}

Note also that I found another way to get trace output : 还要注意,我发现了另一种获取跟踪输出的方法

IQueryable<Person> query = SearchCustomers(entities, "w1 w2",
                                           "(602) (408)");
var oc = (ObjectQuery<Person>) query;
Console.WriteLine(oc.ToTraceString());

Note that query.Where(a).Where(b) is the same as query.Where(a & b) , and qry.All() is essentially taking a range of conditions and chaining together AND statements, something like (word 1 is found) && (word 2 is found) && (word 3 is found) ... 请注意, query.Where(a).Where(b)query.Where(a & b)和qry.All()实质上是采用了一系列条件并将AND语句链接在一起,例如(word 1 is found) && (word 2 is found) && (word 3 is found) ...

You can use that to do the following (I'm using extension methods, so that I can chain this onto the end of any other IQueryable<Customer> ). 您可以使用它来执行以下操作(我正在使用扩展方法,以便可以将其链接到任何其他IQueryable<Customer>的末尾)。

    [System.Runtime.CompilerServices.Extension()]
    public static IQueryable<Customer> Search(this IQueryable<Customer> query, string searchTerm)
    {
        string[] queryWords = searchTerm.Split(" ");

        foreach (string w in queryWords) {
            string word = w;
            string packedWord = Pack(word);

            query = query.Where(c => c.FirstName == word || c.LastName == word || c.HomePhone == packedWord || c.CellPhone == packedWord);
        }
        return query;
    }

Or VB equivalent 或相当于VB

<System.Runtime.CompilerServices.Extension()>
Public Function Search(query As IQueryable(Of Customer), searchTerm As String) As IQueryable(Of Customer)
    Dim queryWords = searchTerm.Split(" ")

    For Each w In queryWords
        Dim word = w
        Dim packedWord = Pack(word)

        query = query.Where(Function(c) c.FirstName = word OrElse
                                c.LastName = word OrElse
                                c.HomePhone = packedWord OrElse
                                c.CellPhone = packedWord)
    Next
    Return query
End Function

I would split it into 2 methods: 我将其分为两种方法:

  • SearchCustomer 搜索客户
  • SearchCustomerPhoneNumber 搜索客户电话号码

In SearchCustomerPhoneNumber you convert the parameter to packed before doing the query. 在SearchCustomerPhoneNumber中,您在执行查询之前将参数转换为压缩的。

Since the phone number will not contain letters and the others will, it is possible to check which of the methods should be run. 由于电话号码将不包含字母,而其他号码将包含字母,因此可以检查应运行哪种方法。 The splitting will actually reduce the load on the database. 拆分实际上将减少数据库的负载。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM