【问题标题】:c# finding matching words in table column using Linq2Sqlc# 使用 Linq2Sql 在表列中查找匹配的单词
【发布时间】:2010-04-11 10:47:32
【问题描述】:

我正在尝试使用 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);
 }

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

【问题讨论】:

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


    【解决方案1】:

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

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

    List<string> searchStrs = new List<string>() { "text2", "text4" }
    
    IEnumerable<MyObject> myFilteredObjects = dataContext.MyObjects
                       .Where(myObj => myObj.MyProperty.Contains(searchStrs, ' '));
    

    更新:

    上面的代码 sn-p 确实工作。这是因为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 个关键字,我必须查询 db 10 次,每次都捕获数据并将其存储在内存中。这是浪费内存并且性能很差。我只是想证明它在 Linq 中是可能的(也许可以在这里或那里进行优化,但我认为它不会变得完美)。

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

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

    【讨论】:

    • 不抱歉,这不起作用。我的自定义 Contains 方法无效(这只是解释我尝试做的事情的一个示例),因为它无法被评估,因为它不能被转换为 SQL。
    • 第二个被剪断的代码现在可以工作了,只是用一个示例数据库对其进行了测试。
    • 是的,我正在考虑使用存储过程来执行此操作,因为使用 linq2sql 效率非常低。我选择将值存储在一个列中,而不是将信息链接在另一个表中,以便在创建/编辑 MyObjects 时使用。看来我有两个选择,要么在自定义存储过程中执行,要么将 MyProperties 拆分到另一个表中。
    • 我会选择第二种选择。删除同样简单,因为触发器可以删除所有具有该已删除行的外键的相关行。添加也不是真的更复杂。
    • 在大多数情况下,最好对查询进行优化,并在插入/删除/更新复杂性方面有所损失。
    【解决方案2】:

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

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

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

    编辑:

    为了澄清我试图用这个做什么,这里有一个没有 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])
    

    【讨论】:

    • 我不确定这是否有意义,因为 MyProperty 包含一个完整的字符串“text1 text2 text3”,需要将其分解为单个值,然后与列表进行比较。我尝试了您的建议并得到以下错误: Local sequence cannot be used in LINQ to SQL implementation of query operators except the Contains() operator
    • 你的回答与问题无关!?它不起作用,并且 Any 和 All 运算符所做的事情与 OP 想要的完全不同。
    • @phild:OP 已经说过它不起作用,但是以这种方式使用 Any 和 All 运算符是正确的。请参阅我的回答中的解释。如果不是因为他提到的错误,这种方式就可以了
    • 哦,我明白了。对不起,这当然可行,除了不单独比较单词,而是在字段中的某处搜索字符串。
    猜你喜欢
    • 2012-06-29
    • 1970-01-01
    • 2018-10-13
    • 2018-02-04
    • 2022-06-10
    • 2017-05-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多