首页 > 解决方案 > 是否可以通过复合主键列表从数据库中选择巨大的列表

问题描述

我有 2 个数据库。从我的数据库中,我正在获取列表项(我按日期获取,最多可以有 300 000 个元素)

public class Item
{
    public string A { get; set; }
    public string B { get; set; }
    public string C { get; set; }
    public DateTime Date { get; set; }
}

在其他数据库中(我不控制那个数据库,我可以从中读取,我无法更改这个数据库中的任何内容)我需要选择列表

public class OtherDbItem
{
    public string X { get; set; }
    public string Y { get; set; }
    public string Z { get; set; }
    public string FewOtherProperties { get; set; }
}

其中 X、Y、Z 是主键,我需要选择所有 otherDbItems 其中 Item.A = OtherDbItem.X 和 Item.B = OtherDbItem.Y 和 Item.C = OtherDbItem.Z (而不是将 OtherDbItems 映射到我的模型并保存在我的数据库)。

我正在使用 2 个不同的 EF Core DbContext 来连接数据库。

我试过了:

        var otherDbItems = new List<OtherDbItem>();

        foreach (var item in Items)
        {
            var otherDbItem = await this.context.OtherDbItems.FindAsync(item.A, item.B, item.C);

            if (otherDbItem != null)
            {
                otherDbItems.Add(otherDbItem);
            }
        }

        return otherDbItems;

但这可能是 300 000 个项目,因此对数据库有 300 000 个请求,显然这不是最佳的,也不可接受。

我也试过:

        var ids = items.Select(item => item.A + item.B + item.C).ToList();
        var otherDbItems = await this.context.OtherDbItems.Where(otherDbItem => ids.Contains(otherDbItem.X + otherDbItem.Y + otherDbItem.Z)).ToListAsync();

但这会导致大量的 sql 查询,速度很慢,并导致 ConnectionTimeOut。

是否可以快速可靠地获得 OtherDbItems?我必须得到这个项目的部分吗?例如 .take(1000).skip(0) 1 次通话中的项目?如果是,这部分应该有多大?

标签: c#linq.net-coreentity-framework-core

解决方案


我不能肯定这是最好的方法,因为我不是 EF 专家,但我最近遇到了类似的情况,我正在处理来自外部 JSON 导出到 EF Core 数据库的同步。该操作的一部分是验证如果导出更改,将基于导入数据增长的现有 EF Core 条目仍然有效,这足以说明随着数据库增长到必须验证的大约一百万条记录,我们遇到了超时和昂贵的查询问题。

我们最终采用的方法实际上最终提高了我们原始流程的速度,即批量操作。我们所做的与方法不同的一件事take()skip()是我们实际上在输入端进行了批处理。本质上,我们一次收集 1000 个 id 并将其用于查询,然后再进行下一个查询。因此,您的代码/数据可能看起来像这样:

int chunkIndex = 0;
int batch = 1000;
var ids = items.Select(item => item.A + item.B + item.C).ToList();
while (chunkIndex < ids.Count)
{
    var chunkIDs = ids.GetRange(chunkIndex,
        chunkIndex + batch >= ids.Count ? ids.Count - chunkIndex : batch);

    var otherDbItems = await this.context.OtherDbItems.Where(otherDbItem => chunkIDs.Contains(otherDbItem.X + otherDbItem.Y + otherDbItem.Z)).ToListAsync();

    chunkIndex += batch;
}

因此,我认为这使您的查询成本更低,因为它不必运行整个事情然后限制结果,但是您的情况略有不同的是,您的源也是一个数据库,而我们的源是 JSON 内容。您可能可以通过在源表中使用take()查询的方法来进一步优化这一点。这方面的语法可能不是 100% 正确,但也许这给出了这个想法:idsItems

int chunkIndex = 0;
int batch = 1000;
// Update dbItemsContext.Items to your source context and table
int totalRecords = dbItemsContext.Items.Count();
while (chunkIndex < totalRecords)
{
    // Update dbItemsContext.Items to your source context and table
    var chunkIDs = dbItemsContext.Items.Select(item => item.A + item.B + item.C).Take(batch).Skip(chunkIndex).ToList();

    var otherDbItems = await this.context.OtherDbItems.Where(otherDbItem => chunkIDs.Contains(otherDbItem.X + otherDbItem.Y + otherDbItem.Z)).ToListAsync();

    chunkIndex += batch;
}

我希望这有助于展示我们的方法,但我认为这条路线您需要锁定表以避免更改,直到您的操作完成。我欢迎任何反馈,因为它也可以改进我们的流程。我还要注意,我们的应用程序/上下文未设置为异步运行,因此您可能需要进行一些额外的修改,或者甚至可能让这些批处理为您的用例异步运行。

关于批量大小的最后说明:您可能需要稍微尝试一下。我们的查询要复杂得多,所以 1000 似乎是我们的最佳选择,但您一次可以多取一点。我不确定是否有任何其他方法可以确定最佳批量大小,而不仅仅是测试一些不同的大小。


推荐阅读