LINQ to SQL - 选择从任何列表开始的位置

时间:2014-01-21 21:40:44

标签: c# linq linq-to-sql expression

使用Linq-to-SQL项目并使用生成的SQL观察一些奇怪的行为。基本上我有一个字符串数组,我需要选择列以其中一个字符串开头的所有行。

using (SqlConnection sqlConn = new SqlConnection(connString))
{
    using (IdsSqlDataContext context = new IdsSqlDataContext(sqlConn))
    {
        //generated results should start with one of these.
        //in real code base they are obviously not hardcoded and list is variable length
        string[] args = new string[] { "abc", "def", "hig" };

        IQueryable<string> queryable = null;

        //loop through the array, the first time through create an iqueryable<>, and subsequent passes union results onto original
        foreach (string arg in args)
        {
            if (queryable == null)
            {
                queryable = context.IdsForms.Where(f => f.MatterNumber.StartsWith(arg)).Select(f => f.MatterNumber);
            }
            else
            {
                queryable = queryable.Union(context.IdsForms.Where(f => f.MatterNumber.StartsWith(arg)).Select(f => f.MatterNumber));
            }
        }

        //actually execute the query.
        var result = queryable.ToArray();
    }
}

我希望生成的sql在功能上等同于以下内容。

select MatterNumber 
from IdsForm 
where MatterNumber like 'abc%' or MatterNumber like 'def%' or MatterNumber like 'hig%'

但是生成的实际SQL是在下面,注意'hig%'是所有三个like子句的参数。

exec sp_executesql N'SELECT [t4].[MatterNumber]
FROM (
    SELECT [t2].[MatterNumber]
    FROM (
        SELECT [t0].[MatterNumber]
        FROM [dbo].[IdsForm] AS [t0]
        WHERE [t0].[MatterNumber] LIKE @p0
        UNION
        SELECT [t1].[MatterNumber]
        FROM [dbo].[IdsForm] AS [t1]
        WHERE [t1].[MatterNumber] LIKE @p1
        ) AS [t2]
    UNION
    SELECT [t3].[MatterNumber]
    FROM [dbo].[IdsForm] AS [t3]
    WHERE [t3].[MatterNumber] LIKE @p2
    ) AS [t4]',N'@p0 varchar(4),@p1 varchar(4),@p2 varchar(4)',@p0='hig%',@p1='hig%',@p2='hig%'

3 个答案:

答案 0 :(得分:3)

看起来你正在关闭循环变量。这是C#中常见的问题。发生的情况是arg的值在查询运行时评估,而不是在创建时评估。

创建一个临时变量来保存值:

foreach (string arg in args)
{
    var temp = arg;
    if (queryable == null)
    {
        queryable = context.IdsForms.Where(f => f.MatterNumber.StartsWith(temp)).Select(f => f.MatterNumber);
    }
    else
    {
        queryable = queryable.Union(context.IdsForms.Where(f => f.MatterNumber.StartsWith(temp)).Select(f => f.MatterNumber));
    }
}

您可以阅读关于结束循环变量的this Eric Lippert帖子。正如Eric在文章的顶部所说,正如@Magus在评论中指出的那样,这在C#5中已经发生了变化,因此foreach变量是每次迭代的新副本。如上所述,创建临时变量是向前兼容的。

答案 1 :(得分:1)

联合是正确的,因为你在linq中使用了union来进行sql查询。它们都是hig%的原因是因为lambda f => f.MatterNumber.StartsWith(arg)在循环参数周围创建了一个闭包。要修复,请在循环中声明一个局部变量

    foreach (string arg in args)
    {
        var _arg = arg;
        if (queryable == null)
        {
            queryable = context.IdsForms.Where(f => f.MatterNumber.StartsWith(_arg)).Select(f => f.MatterNumber);
        }
        else
        {
            queryable = queryable.Union(context.IdsForms.Where(f => f.MatterNumber.StartsWith(_arg)).Select(f => f.MatterNumber));
        }
    }

但我同意工会似乎没必要。如果要检查的字符串数组不会改变,那么您可以使用标准的where子句。否则你可以看一下谓词构建器! Check here

答案 2 :(得分:0)

这个怎么样?

queryable = context..IdsForms.Where(f =>
            {
               foreach (var arg in args)
               {
                   if (f.MatterNumber.StartsWith(arg))
                      return true;
               }
               return false;
            }).Select(f => f.MatterNumber);