c#使用Linq2Sql查找表列中的匹配单词

时间:2010-04-11 10:47:32

标签: c# linq-to-sql list comparison

我正在尝试使用Linq2Sql返回包含字符串列表中的值的所有行。 linq2sql类对象有一个字符串属性,其中包含以空格分隔的单词。

public class MyObject
{
    public string MyProperty { get; set; }
}

示例MyProperty值为:

MyObject1.MyProperty = "text1 text2 text3 text4"
MyObject2.MyProperty = "text2"

例如,使用字符串集合,我传递以下列表

var list = new List<>() { "text2", "text4" }

这将返回上面示例中的两个项目,因为它们都包含“text2”值。

我使用下面的代码尝试了以下代码,但由于我的扩展方法,无法评估Linq2Sql。

public static IQueryable<MyObject> WithProperty(this IQueryable<MyProperty> qry,
    IList<string> p)
{
    return from t in qry
        where t.MyProperty.Contains(p, ' ')
        select t;
}

我还写了一个扩展方法

public static bool Contains(this string str, IList<string> list, char seperator)
{
    if (str == null) return false;
    if (list == null) return true;

    var splitStr = str.Split(new char[] { seperator },
        StringSplitOptions.RemoveEmptyEntries);

    bool retval = false;
    int matches = 0;

    foreach (string s in splitStr)
    {
        foreach (string l in list)
        {
            if (String.Compare(s, l, true) == 0)
            {
                retval = true;
                matches++;
            }
        }
    }

    return retval && (splitStr.Length > 0) && (list.Count == matches);
 }

关于如何实现这一目标的任何帮助或想法?

2 个答案:

答案 0 :(得分:1)

你走在正确的轨道上。扩展方法WithProperty的第一个参数必须是IQueryable<MyObject>类型,而不是IQueryable<MyProperty>

无论如何,您不需要IQueryable的扩展方法。只需在lambda中使用Contains方法进行过滤即可。这应该有效:

List<string> searchStrs = new List<string>() { "text2", "text4" }

IEnumerable<MyObject> myFilteredObjects = dataContext.MyObjects
                   .Where(myObj => myObj.MyProperty.Contains(searchStrs, ' '));

<强>更新

上面的代码段工作。这是因为Contains方法无法转换为SQL语句。我想了一下这个问题,并通过思考'我将如何在SQL中执行此操作来找到解决方案?':您可以通过查询每个单个关键字并将所有结果合并在一起来实现。遗憾的是,Linq-to-SQL的延迟执行阻止了在一个查询中完成所有操作。所以我想出了妥协的妥协。它查询每个关键字。这可以是以下之一:

  • 等于字符串
  • 在两个分隔符之间
  • 在字符串的开头,然后是分隔符
  • 或在字符串的末尾,以分隔符为首。

这跨越了一个有效的表达式树,并可通过Linq-to-SQL转换为SQL。在查询之后,我不会通过立即获取数据并将其存储在列表中来推迟执行。之后所有名单都已合并。

public static IEnumerable<MyObject> ContainsOneOfTheseKeywords(
        this IQueryable<MyObject> qry, List<string> keywords, char sep)
{
    List<List<MyObject>> parts = new List<List<MyObject>>();

    foreach (string keyw in keywords)
        parts.Add((
            from obj in qry
            where obj.MyProperty == keyw ||
                  obj.MyProperty.IndexOf(sep + keyw + sep) != -1 ||
                  obj.MyProperty.IndexOf(keyw + sep) >= 0 ||
                  obj.MyProperty.IndexOf(sep + keyw) ==
                      obj.MyProperty.Length - keyw.Length - 1
            select obj).ToList());

    IEnumerable<MyObject> union = null;
    bool first = true;
    foreach (List<MyObject> part in parts)
    {
        if (first)
        {
            union = part;
            first = false;
        }
        else
            union = union.Union(part);
    }

    return union.ToList();
}

并使用它:

List<string> searchStrs = new List<string>() { "text2", "text4" };

IEnumerable<MyObject> myFilteredObjects = dataContext.MyObjects
                    .ContainsOneOfTheseKeywords(searchStrs, ' ');

这个解决方案除了优雅之外别无他物。对于10个关键字,我必须查询数据库10次,每次捕获数据并将其存储在内存中。这会浪费内存并且性能不佳。我只是想证明它在Linq中是可能的(也许它可以在这里或那里进行优化,但我认为它不会变得完美)。

我强烈建议将该函数的逻辑交换到数据库服务器的存储过程中。一个查询,由数据库服务器优化,不浪费内存。

另一种选择是重新考虑您的数据库设计。如果要查询一个字段的内容(您将此字段视为一个关键字数组,由空格分隔),您可能只是选择了不适当的数据库设计。您宁愿创建一个带有表的外键的新表。新表只有一个关键字。查询将更多更简单,更快速,更易理解。

答案 1 :(得分:0)

我没有尝试过,但如果我没记错的话,这应该可行:

from t in ctx.Table
where list.Any(x => t.MyProperty.Contains(x))
select t

如果您希望Any()中的所有字符串匹配,则可以将All()替换为list

修改

为了澄清我试图对此做些什么,这里是一个没有linq编写的类似查询,用于解释AllAny的使用

where list.Any(x => t.MyProperty.Contains(x))

转换为:

where t.MyProperty.Contains(list[0]) || t.MyProperty.Contains(list[1]) ||
      t.MyProperty.Contains(list[n])

where list.Any(x => t.MyProperty.Contains(x))

转换为:

where t.MyProperty.Contains(list[0]) && t.MyProperty.Contains(list[1]) &&
      t.MyProperty.Contains(list[n])