(DbContext) SaveChanges() 如何用于大量记录? - 寻求和找到最佳性能的解决方案

How can (DbContext) SaveChanges() be used for large number of records? - the solution for the best performance is to be sought and found

对于大量记录可以运行SaveChanges(),但是性能不能接受。

这是一个例子 两个 table:

[联系人](身份证、姓名、邮政编码、城市、状态)

[zip_city](身份证、邮编、城市)

需要更新 table contact 中所有列 zip 的状态,城市在 table zip_city

中可用

有效,当记录数小于10000条时性能acceptable

但是对于 +10,000 条记录,Visual Studio(调试器)抱怨它花费的时间太长:CLR 无法在 60 秒内从 COM 上下文 0xb67898 转换到 COM 上下文 0xb67728.... 在构建查询 "qry"

var ctx = new DbContext();

var qry = ctx.contact.Join(ctx.zip_city, c => new { c.zip, c.city }, z => new { z.zip, z.city }, (c, z) => new { c, z })
           .Select(x => new dtoContact { id = x.c.id }).ToList();

foreach (var con in ctx.contact)
{
    if (qry.Any(x => x.Id == con.id))
    {
        con.status = "P/O";
    }
    else
    {
        con.status = "???";
    }
}

ctx.SaveChanges();

使用以下代码,它会通过 运行 ctx.SaveChanges();

引发相同的消息
var ctx = new DbContext();

var zc = ctx.zip_city.ToList();

foreach (var con in ctx.contact)
{
    if (zc.Any(x => x.zip == con.zip && x.city == con.city))
    {
        con.status = "P/O";
    }
    else
    {
        con.status = "???";
    }
}

ctx.SaveChanges();

参考:数据传输对象

public class dtoContact 
{
    public int id { get; set; }
    public string name { get; set; }
    public string zip { get; set; }
    public string city { get; set; }
    public string status { get; set; }
}

信息:Visual Studio(调试器)消息! google 翻译自 VS15 德文版

托管调试助手“ContextSwitchDeadlock”在 "C: \ Projects \ Sweepstakes_EF6 \ TrafficSightAct \ bin \ Debug \ Sweepstakes.exe" 中遇到问题。 附加信息:CLR 在 60 秒内无法从 COM 上下文 0xb67898 转换到 COM 上下文 0xb67728。拥有目标上下文/单元的线程要么等待而不移动消息,要么处理一个非常持久的操作而不移动 Windows 消息。这种情况通常会降低性能,甚至可能导致应用程序停止响应或增加内存使用量。

在这种情况下,我想推荐使用 SqlCommand。

例如:

var ctx = new DbContext();

string sql = "update contact c inner join zip_city z " +
             "on c.plz = z.plz and c.ort = z.ort set c.status = 'P/O'; ";
sql = sql  + "update contact set status = '???' where status <> 'P/O'; ";

ctx.Database.ExecuteSqlCommand(sql);

我想,您的 27,000 条记录可能只需要几秒钟。

请尝试。

感谢大家提出宝贵意见!

为了让上面的问题评论保持一致,我在这里创建一个答案。

我在评论中测试了在 table 联系人中提供的 27,000 条记录和 table zip_city 中的 47,000 条记录的建议,但没有任何数据库索引更改(添加 Panagiotis Kanavos 建议的索引)。

以下为性能测试结果

原码1,耗时113秒.

var ctx = new DbContext();
var qry = ctx.contact.Join(ctx.zip_city, c => new { c.zip, c.city }, z => new { z.zip, z.city }, (c, z) => new { c, z })
           .Select(x => new dtoContact { id = x.c.id }).ToList();
foreach (var con in ctx.contact)
{
    if (qry.Any(x => x.Id == con.id))
    {
        con.status = "P/O";
    }
    else
    {
        con.status = "???";
    }
}
ctx.SaveChanges();

基于 DevilSuichiro,“bulk savechanges”在 MySQL 的 DbContext 中不可用,因此这里仅对 100 个实体的单个批次调用 SaveChanges()。 需要 107 秒.

var ctx = new DbContext();
var qry = ctx.contact.Join(ctx.zip_city, c => new { c.zip, c.city }, z => new { z.zip, z.city }, (c, z) => new { c, z })
           .Select(x => new dtoContact { id = x.c.id }).ToList();
int count = 0;
foreach (var con in ctx.contact)
{
    if (qry.Any(x => x.Id == con.id))
    {
        con.status = "P/O";
    }
    else
    {
        con.status = "???";
    }
    ++count;
    if (count % 100 == 0)
    {
        ctx.SaveChanges();
    }
}
ctx.SaveChanges();

在Rand Random的基础上,使用HashSet来提高性能。 仍然需要 100 多秒

var ctx = new DbContext();
var qry = ctx.contact.Join(ctx.zip_city, c => new { c.zip, c.city }, z => new { z.zip, z.city }, (c, z) => new { c, z }).Select(id = x.c.id);
var ids = new HashSet<int>(qry);           
foreach (var con in ctx.contact)
{
    if (ids.Contains(con.id))
    {
        con.status = "P/O";
    }
    else
    {
        con.status = "???";
    }
}
ctx.SaveChanges();

原码2,耗时67秒.

var ctx = new DbContext();
var zc = ctx.zip_city.ToList();
foreach (var con in ctx.contact)
{
    if (zc.Any(x => x.zip == con.zip && x.city == con.city))
    {
        con.status = "P/O";
    }
    else
    {
        con.status = "???";
    }
}
ctx.SaveChanges();

基于 Panagiotis Kanavos,为此数据更新使用任何 ORM(如此处 DbContext SaveChanges() )确实不是合适的解决方案。

我测试了来自 alex, nvoigt 的代码,不到 2 秒

var ctx = new DbContext();
string sql = "update contact set status = '???'; " + 
    "update contact c inner join zip_city z on c.plz = z.plz and c.ort = z.ort set c.status = 'P/O'; ";
ctx.Database.ExecuteSqlCommand(sql);