Skip to content

[Netcore] 使用EF更新资料时仅更新变更的字段

Jinxin Chen edited this page Dec 11, 2019 · 1 revision

默认生成的CRUD代码的问题

如果我们建立了Model,然后通过vs code generation出CRUD的代码,Edit的代码类似下面这样:

public async Task<IActionResult> Edit(string id, [FromForm] Article article)
{
    if (id != article.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        _dbContext.Update(item);
        await _dbContext.SaveChangesAsync();

        return RedirectToAction("Index");
    }

    return View(article);
}

如果查看log,会发现生成的update sql更新了所有字段,即使我们只更新了某一个字段:

UPDATE `Article` SET `Body` = @p2, `CategoryId` = @p4, `Clicks` = @p6, `DatePublished` = @p8, `Image` = @p10, `IsPublished` = @p12, `
Slug` = @p14, `Summary` = @p16, `Tags` = @p18, `Title` = @p20
      WHERE `Id` = @p0;

如果只更新变更的字段?

将代码调整一下,从context中取得既有的entity,赋值之后再更新:

var existItem = await _dbContext.Article.FirstAsync(r => r.Id == id);

existItem.Title = article.Title;
existItem.Summary = article.Summary;
existItem.Body = article.Body;
existItem.Slug = article.Slug;
existItem.IsPublished = article.IsPublished;
existItem.DatePublished = article.DatePublished;
existItem.CategoryId = article.CategoryId;

await _dbContext.SaveChangesAsync();

仅更新Summary字段,查看log,发现生成的sql仅更新Summary字段:

UPDATE `Article` SET `Summary` = @p2
      WHERE `Id` = @p0;

为什么每个字段有重新赋值但是却只更新了修改的字段?

查看entity framework的源码可以看到,ChangeDetector已经做好了字段的比对,只有和原值不相等的属性才会被标记为修改状态,才会被更新:

public virtual void DetectChanges(InternalEntityEntry entry)
{
    var entityType = entry.EntityType;

    foreach (var property in entityType.GetProperties())
    {
        if (property.GetOriginalValueIndex() >= 0
            && !Equals(entry[property], entry.GetOriginalValue(property)))
        {
            entry.SetPropertyModified(property);
        }
    }

    foreach (var property in entityType.GetProperties())
    {
        DetectKeyChange(entry, property);
    }

    if (entry.HasRelationshipSnapshot)
    {
        foreach (var navigation in entityType.GetNavigations())
        {
            DetectNavigationChange(entry, navigation);
        }
    }
}

文件在这里:

https://github.com/aspnet/EntityFramework/blob/ab9c919d2496ae7e655331328483b634dc3a4f7b/src/EFCore/ChangeTracking/Internal/ChangeDetector.cs#L121

有没有什么更简单的方法?

上面的方法,要对entity的每个属性都进行赋值,操作比较繁琐,也不是一个优秀码农想要的。

这里可以使用 AutoMapper 包来简化操作,它可以帮助我们将一个对象映射到另一个对象,简化后的代码如下(Initialize方法可以放入statup.cs中,执行一次就好):

AutoMapper.Mapper.Initialize(cfg => cfg.CreateMap<Article, Article>());
AutoMapper.Mapper.Map(item, existItem);

AutoMapper 的 github 地址如下:

https://github.com/AutoMapper/AutoMapper
Clone this wiki locally