MongoDB C#驱动程序:API与Linq性能比较

4
我正在尝试使用IRepository模式(C#,MVC5)制作一个MongoDB Web应用程序,以使单元测试更容易。只是想知道有人能否提供关于为什么这样做更快的信息。
这是使用最新的MongoDB c#驱动程序。
在我的IRepository类中,我有以下内容。
IQueryable<T> SearchFor();

List<T> SearchFor(FilterDefinition<T> filter);

发现一个 Stack Overflow 的帖子,建议使用 IQueryable 来提高速度,而不是使用 IEnumerable。
下面是 MongoRepository 类的代码。
public IQueryable<T> SearchFor() {
    return _collection.AsQueryable<T>();
}

public List<T> SearchFor(FilterDefinition<T> filter) {
    return _collection.Find(filter).ToList();
}

据我所知,“Filter definition” 是通常编写查询数据库的方式。
以下是从数据库获取数据的调用。
IQueryable<Client> asd4 = collection.SearchFor().Where(x => x.ClientDesc == "<search text>");

FilterDefinition<Client> filter1 = Builders<Client>.Filter.Eq("ClientDesc", "<search text>");
List<Client> asd10 = collection.SearchFor(filter1).ToList<Client>();

请注意,我应该使用IQueryable和Linq路线,纯粹是因为IRepository不应包含技术依赖类(如FilterDefinition)。
当针对具有30k个简单文档的集合进行测试并测试不同方法的速度时,我获得了以下结果。
使用IQueryable完成需要3ms,使用FilterDefinition需要43ms。
我想知道为什么在IQueryable上使用Linq查询比使用API发送请求来返回特定值更快?
更新:根据@lenkan的建议,我已经为IQueryable添加了一个for循环。
public void PerformanceTest(IRepository<Client> collection) {
    Stopwatch sw = new Stopwatch();

    // Delete all records
    // ******************
    System.Diagnostics.Debug.WriteLine("*****************");

    sw.Start();
    collection.DeleteAll();
    sw.Stop();
    System.Diagnostics.Debug.WriteLine("Deleting all records: " + sw.Elapsed);


    // Create 30k Records
    // ******************
    System.Diagnostics.Debug.WriteLine("*****************");

    sw.Reset();
    sw.Start();
    // Create 30k records
    for (int i = 0; i < 30000; i++) {
        Client testclient = new Client() {
            ClientDesc = "hahahahahahahahah " + i
        };
        collection.Add(testclient);
    }
    sw.Stop();
    System.Diagnostics.Debug.WriteLine("Created: 30k rows: " + sw.Elapsed);


    // Test IQueryable & LINQ
    // **********************
    System.Diagnostics.Debug.WriteLine("*********************");
    System.Diagnostics.Debug.WriteLine("* IQueryable & LINQ *");
    System.Diagnostics.Debug.WriteLine("*********************");

    sw.Reset();
    sw.Start();
    IQueryable<Client> asd4 = collection.SearchFor().Where(x => x.ClientDesc == "hahahahahahahahah 10");
    foreach (Client item in asd4) {
        string aaaaaa = item.ClientDesc;
    }
    sw.Stop();
    System.Diagnostics.Debug.WriteLine("Find one from start: " + sw.Elapsed);

    sw.Reset();
    sw.Start();
    IQueryable<Client> asd7 = collection.SearchFor().Where(x => x.ClientDesc == "hahahahahahahahah 10");
    foreach (Client item in asd7) {
        string aaaaaa = item.ClientDesc;
    }
    sw.Stop();
    System.Diagnostics.Debug.WriteLine("Find one from start: " + sw.Elapsed);

    sw.Reset();
    sw.Start();
    IQueryable<Client> asd5 = collection.SearchFor().Where(x => x.ClientDesc == "hahahahahahahahah 29999");
    foreach (Client item in asd5) {
        string bbbbbb = item.ClientDesc;
    }
    sw.Stop();
    System.Diagnostics.Debug.WriteLine("Find one from end: " + sw.Elapsed);

    sw.Reset();
    sw.Start();
    for (int i = 10000; i < 10050; i++) {
        IQueryable<Client> asd6 = collection.SearchFor().Where(x => x.ClientDesc == "hahahahahahahahah " + i);
        foreach (Client item in asd6) {
            string aaaaaa = item.ClientDesc;
        }
    }
    sw.Stop();
    System.Diagnostics.Debug.WriteLine("Find in loop of 50: " + sw.Elapsed);


    // Test Filter & LINQ
    // ***********************
    System.Diagnostics.Debug.WriteLine("*****************");
    System.Diagnostics.Debug.WriteLine("* List & Filter *");
    System.Diagnostics.Debug.WriteLine("*****************");

    sw.Reset();
    sw.Start();
    FilterDefinition<Client> filter1 = Builders<Client>.Filter.Eq("ClientDesc", "hahahahahahahahah 10");
    List<Client> asd10 = collection.SearchFor(filter1).ToList<Client>();
    foreach (Client item in asd10) {
        string aaaaaa = item.ClientDesc;
    }
    sw.Stop();
    System.Diagnostics.Debug.WriteLine("Find one from start: " + sw.Elapsed);

    sw.Reset();
    sw.Start();
    FilterDefinition<Client> filter2 = Builders<Client>.Filter.Eq("ClientDesc", "hahahahahahahahah 29999");
    List<Client> asd11 = collection.SearchFor(filter2).ToList<Client>();
    foreach (Client item in asd11) {
        string cccccc = item.ClientDesc;
    }
    sw.Stop();
    System.Diagnostics.Debug.WriteLine("Find one from end: " + sw.Elapsed);

    sw.Reset();
    sw.Start();
    for (int i = 10000; i < 10050; i++) {
        FilterDefinition<Client> filter3 = Builders<Client>.Filter.Eq("ClientDesc", "hahahahahahahahah " + i);
        List<Client> asd12 = collection.SearchFor(filter3).ToList<Client>();
    }
    sw.Stop();
    System.Diagnostics.Debug.WriteLine("Find in loop of 50: " + sw.Elapsed);

    // Delete all records
    // ******************
    System.Diagnostics.Debug.WriteLine("*****************");

    sw.Start();
    collection.DeleteAll();
    sw.Stop();
    System.Diagnostics.Debug.WriteLine("Deleting all records: " + sw.Elapsed);

}

这是现在的结果。看起来使用IQueryable进行枚举会对性能产生初始影响,但当您调用后续搜索时(即),事情似乎会加速。
*****************
Deleting all records: 00:00:00.0670336
*****************
Created: 30k rows: 00:00:04.6829844
*********************
* IQueryable & LINQ *
*********************
Find one from start: 00:00:00.0878309
Find one from start: 00:00:00.0120098
Find one from end: 00:00:00.0116334
Find in loop of 50: 00:00:00.5890532
*****************
* List & Filter *
*****************
Find one from start: 00:00:00.0248407
Find one from end: 00:00:00.0118345
Find in loop of 50: 00:00:00.5377828
*****************
Deleting all records: 00:00:00.7029368

1
有趣。你能提供一份用于基准测试的最小代码示例吗?从你提供的调用中,你从未枚举asd4 IQueryable。 - lenkan
在原帖中添加了回复。 - Dwiea
我仍然认为使用Linq而不是FilterDefinitions作为一种方法来从接口中移除特定于技术的类会有很大的好处。尽管性能会稍微受到一些影响(主要是在管理后台),但仍然值得这个差距。 - Dwiea
1个回答

13

您最初的问题是为什么使用LINQ比使用API快那么多。答案是因为LINQ采用了延迟执行的方式,查询实际上并没有执行。只有在尝试遍历结果(foreach/.ToList()/等)时才会执行查询。

您可能已经计时了这个语句:

IQueryable<Client> asd4 = collection.SearchFor().Where(x => x.ClientDesc == "<search text>");

当您应该计时此陈述时:

List<Client> asd4 = collection.SearchFor().Where(x => x.ClientDesc == "<search text>").ToList();

您在更新期间显示的性能数字似乎是合理的。LINQ实际上比使用直接API稍微慢一些,因为它将抽象添加到查询中。这种抽象使您能够轻松地将MongoDB更改为另一个数据源(SQL Server/Oracle/MySQL/XML等),而不需要进行许多代码更改,但您需要付出轻微的性能损失来获得这种抽象。


1
请问有人能分享一下在执行查询时,Find和IQueryable的实际基准测试结果吗? - Dmitry Karpenko

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