Linq To SQL:在使用.Contains时保留列表顺序

3

我正在使用Lucene.net构建一个MyListOfIds As List(Of Integer),然后将其传递给我的Linq服务。然后按照以下方式搜索数据库:

Return _EventRepository.Read().Where(Function(e) MyListOfIds.Contains(e.ID)).ToList

现在我知道Lucene已经根据每个词项的权重对MyListOfIds进行排序。问题是,Linq在其SQL搜索中失去了这种顺序。
我的问题是:如何在构建Lambda表达式时保留该排序顺序?
我尝试使用LINQPad查看查询是如何构建的,但由于我必须声明一个变量,LINQPad没有显示结果SQL :-(
以下是我在LINQPad中尝试的内容。
Dim i As New List(Of Integer)
i.Add(1)
i.Add(100)
i.Add(15)
i.Add(3)
i.Add(123)

Dim r = (From e In Events
         Where i.Contains(e.ID)
         Select e)

注意:我的示例是使用VB.NET编写的,但如果回复使用C#也没有关系。
5个回答

2
从L2S查询中获取无序结果,然后使用L2O(linq-to-objects)查询中提供序号位置的.Select重载以与列表相同的顺序重新排序。例如:
var someResult = _EventRepository.Read().Where(e => MyListOfIds.Contains(e.ID)).ToList();

var someResultOrdered =
  from sr in someResult
  join lid in MyListOfIds.Select((v, i) => new { v, i }) on sr.ID equals lid.v
  orderby lid.i
  select sr;

2
我会说LINQ到SQL查询将以数据库的自然顺序(可能是主键?)返回数据,因为在SQL中使用IN条件(应该将其转换为.Contains)不指定任何ORDER,你的LINQ表达式也没有指定。如果将其视为正常的SQL语句,则可以很容易地看出无法轻松地以这种方式指定顺序。
为了对加载的数据进行排序,您可以获取未排序的数据,然后按Lucene的已知顺序对枚举进行排序。尽管如此,您可能仍需编写自定义IComparer。

2

正如hangy所提到的,我认为使用字典方法是正确的方式。我会这样做:

Public Function GetLuceneSearchResults(ByVal ids As List(Of Integer)) As List(Of Domain.Event) Implements IEventService.GetLuceneSearchResults
    Dim Results = (From e In _EventRepository.Read()
                   Where ids.Contains(e.ID)
                   Select e).ToDictionary(Function(e) e.ID, Function(e) e)

    Return (From i In ids
            Where Results.ContainsKey(i)
            Select Results(i)).ToList()
End Function

第一个查询返回一个字典,其中事件ID作为键,事件本身作为值。这样可以获得哈希查找的性能优势。

我将使用DotTrace运行这个程序,并将其与我的方法进行比较。 - Chase Florell
1
好的,这很棒。我用DotTrace两次运行了你的方法,也运行了我的方法两次,每次使用完全相同的查询,并且每次都返回完全相同的结果。你的运行时间为45毫秒和15毫秒,而我的运行时间为75毫秒和80毫秒。谢谢! - Chase Florell
不用谢 :) 我和你的主要区别在于使用了字典。因为它使用哈希查找,返回查询仅需要针对ids列表中的每个数字迭代一次,同时检查是否与Results查询匹配。而你之前的方法需要迭代(id ^ 2)次。所以如果你有5个id,你之前的版本需要迭代25次,而使用字典只需要迭代5次。我相信其他人能够更好地解释这个问题,哈哈。 - rossisdead
我实际上没有预料到使用字典方法在处理这几个项目时会表现得更好。这就是为什么始终建议使用基准测试来比较不同的可能性。 :) - hangy

1

你可能需要一个IDictionary,其中键只是递增的(就像标识列),而值是来自Lucene的实际ID。

然后,在你的LINQ2SQL语句中,不要使用“where”子句,而是在字典值和数据库中的列之间进行“join”。在你的LINQ中,按字典键排序。

编辑:添加示例

这里有一个例子:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1 {
    class Program {
        static void Main(string[] args) {

            var ids = new Dictionary<int, int>();

            //key is just a sort sequence, value is the ID from Lucene
            ids.Add(1, 27);
            ids.Add(2, 25);
            ids.Add(3, 29);

            var ctx = new DataClasses1DataContext();

            var tabs = (from t in ctx.Tabs
                         where ids.Values.Contains(t.TabID)
                         select t).ToList();


            var sorted = from t in tabs
                         join id in ids on t.TabID equals id.Value
                         orderby id.Key
                         select t;

            foreach (var sortedItem in sorted) {
                Console.WriteLine(sortedItem.TabID);
            }
            Console.ReadLine();


        }
    }

}


虽然这可能是一个好主意,但我对LINQ和JOINS不熟悉,甚至开始尝试去理解它都很困难...这是不好的吗 :-s - Chase Florell
让我试试 :) 我们系统也遇到了同样的问题。如果不指定 order by(随机 = 无论性能如何最好),SQL 将随机检索记录。但由于我们的系统大量使用缓存,所以我只是一次做一个。 - turtlepick
谢谢更新。你看到我发布的答案了吗?当涉及性能时,你对那种方法和你自己的想法有什么看法? - Chase Florell
您不能将外部字典连接与LINQ混合使用。执行ToList()将在第二个查询上使用对象的LINQ而不是Linq2SQL。 - turtlepick
对于你来说,只需要第二个查询LINQ语句就足够了,因为它是一个Lucene对象,而不是LINQ2SQL。 - turtlepick

1

OP答案

@hangy让我想到了正确的方向。这是我想出来的...

欢迎提出建议!

    Public Function GetLuceneSearchResults(ByVal ids As List(Of Integer)) As List(Of Domain.Event) Implements IEventService.GetLuceneSearchResults
        Dim Results = _EventRepository.Read().Where(Function(e) ids.Contains(e.ID)).AsQueryable
        Dim Output As New List(Of Domain.Event)

        For Each i In ids
            Output.Add(Results.Where(Function(e) e.ID = i).SingleOrDefault)
        Next

        Return Output
    End Function

现在不考虑性能方面的猜测,这绝对按预期工作。我很想听听您对性能增强的想法,或者这是否完全不切实际。谢谢。


我认为这不会对性能造成很大影响,因为Lucene只向服务发送了5个ID,所以在那个“对于每个”循环中,我最多只需处理5条记录。 - Chase Florell
1
如果你知道不会有成千上万的条目,那么这应该可以正常工作。我想你说得对,这不会影响性能。你可能可以创建一个结果的字典(.ToDictionary扩展方法),其中键是e.ID。但是,如果你总是只从Lucene获取5个ID,那么字典版本实际上可能会更慢。;) 如果你真的需要它,可能值得进行基准测试。 - hangy
是的,我的网站每页只允许显示5个结果,而Lucene在计算页面编号等方面发挥作用。事件服务绝对只会处理发送到视图(页面)的最终5个结果。 - Chase Florell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接