有效更新断开EF核心的方法



我有一个asp dotnet core Web服务。这与Postgres数据库使用实体框架Core 1.1进行对话。当服务需要根据客户请求而更新数据库记录时,有两种方法。

方法1

  1. 检索要从数据库更新的记录。

  2. 将从客户端收到的值映射到数据库实体。

  3. 在数据库上下文上调用savechanges。

方法2。

  1. 在从客户端收到的记录中传递的数据库上下文上的调用更新。(如果需要,将DTO映射到数据库实体)。

  2. 在数据库上下文上调用savechanges。

两种方法的行为都大不相同。

方法1

pro。仅更新从数据库中的值更改的值。

con。进行两个数据库往返行程。(检索实体,然后更新)。

方法2。

pro。只需一个数据库往返即可执行。

con。更新传递的整个对象图,即使实体中只有一个值更改。

实体框架中的页面核心文档与断开的实体合作尚未编写。

https://learn.microsoft.com/en-us/ef/core/saving/disconnected-entities

我们没有足够的数据来有效测试该系统的完整生产系统,并且在开发阶段,我们试图以最大的风险解决应用程序的各个部分,基于经验证据的优化不是我们现在这段时间投资足够高的优先事项。

鉴于网络服务器和数据库服务器将在同一数据中心内生活,我正在寻找的是一种有点明智的" 10"方法的"起动器",如果需要,我们可能会花一些时间来优化,如果需要。<<<<<<<<<<<<<<

我遇到的问题是,这两种方法的行为是完全不同的,但我没有找到可以帮助我在它们之间进行选择的信息,并且没有具有代表性吞吐量的现实尺寸的测试系统,我怀疑对任何快速而简单的解释我能做的本地测试在很大程度上是毫无意义的WRT,对于生产量表系统的带宽和占用。

我几乎欢迎任何信息或指导。

我的响应是方法2

概要

我要仅修改实体的几列以及添加 modify nested Child Entities

  • 在这里,我正在更新Scenario实体,仅修改ScenarioDate
  • 在其儿童实体中,即导航属性TempScenario,我正在添加新记录
  • 在嵌套的儿童实体中,Scenariostation,我还添加了修改记录
public partial class Scenario
{
    public Scenario()
    {
        InverseTempscenario = new HashSet<Scenario>();
        Scenariostation = new HashSet<Scenariostation>();
    }
    public int Scenarioid { get; set; }
    public string Scenarioname { get; set; }
    public DateTime? Scenariodate { get; set; }
    public int Streetlayerid { get; set; }
    public string Scenarionotes { get; set; }
    public int? Modifiedbyuserid { get; set; }
    public DateTime? Modifieddate { get; set; }
    public int? Tempscenarioid { get; set; }
    
    public virtual Scenario Tempscenario { get; set; }
    public virtual ICollection<Scenario> InverseTempscenario { get; set; }
    public virtual ICollection<Scenariostation> Scenariostation { get; set; }
}

public partial class Scenariostation
{
    public Scenariostation()
    {
        Scenariounit = new HashSet<Scenariounit>();
    }
    public int Scenariostationid { get; set; }
    public int Scenarioid { get; set; }
    public int Stationid { get; set; }
    public bool? Isapplicable { get; set; }
    public int? Createdbyuserid { get; set; }
    public int? Modifiedbyuserid { get; set; }
    public DateTime? Modifieddate { get; set; }
    
    public virtual Scenario Scenario { get; set; }
    public virtual Station Station { get; set; }
}
public partial class Station
{
    public Station()
    {
        Scenariostation = new HashSet<Scenariostation>();
    }
    public int Stationid { get; set; }
    public string Stationname { get; set; }
    public string Address { get; set; }
    public NpgsqlPoint? Stationlocation { get; set; }
    public int? Modifiedbyuserid { get; set; }
    public DateTime? Modifieddate { get; set; }
    public virtual ICollection<Scenariostation> Scenariostation { get; set; }
}
  • 使用EF核心,如果您不想进行2个数据库往返行程,则在断开连接的方案中的数据更新很棘手。

  • 即使两次数据库旅行似乎并不重要,但如果数据表拥有数百万记录,也可能会妨碍性能。

  • 另外,如果只有很少的列要更新,包括嵌套子实体的列,Usual Approach将无法正常工作

通常的方法

public virtual void Update(T entity)
{
    if (entity == null)
        throw new ArgumentNullException("entity");
    var returnEntity = _dbSet.Attach(entity);
    _context.Entry(entity).State = EntityState.Modified;
}

但是,在此问题的问题EF核心更新,如果您使用此DbContext.Entry(entity).EntityState = EntityState.IsModified,则所有列将更新。因此,某些列将更新为其默认值,即null或默认数据类型值。

更重要的是,ScenarioStation的某些记录根本不会更新,因为实体状态将为UnChanged

So Inorder to 仅更新从客户端发送的列,以某种方式 ef Core需要被告知

使用changetracker.trackgraph

最近,我找到了该DbConetxt.ChangeTracker.TrackGraph方法,可用于标记AddedUnChanged的实体状态。

差异是,使用TrackGraph,您可以添加自定义逻辑,因为它通过实体的导航属性进行导航。

使用TrackGraph

我的自定义逻辑
public virtual void UpdateThroughGraph(T entity, Dictionary<string, List<string>> columnsToBeUpdated)
{
    if (entity == null)
        throw new ArgumentNullException("entity");
    _context.ChangeTracker.TrackGraph(entity, e =>
    {
        string navigationPropertyName = e.Entry.Entity.GetType().Name;
        if (e.Entry.IsKeySet)
        {
            e.Entry.State = EntityState.Unchanged;
            e.Entry.Property("Modifieddate").CurrentValue = DateTime.Now;
            if (columnsToBeUpdated.ContainsKey(navigationPropertyName))
            {
                foreach (var property in e.Entry.Properties)
                {
                    if (columnsToBeUpdated[e.Entry.Entity.GetType().Name].Contains(property.Metadata.Name))
                    {
                        property.IsModified = true;
                    }
                }
            }
        }
        else
        {
            e.Entry.State = EntityState.Added;
        }
    });
}

使用这种方法,我可以轻松地处理任何嵌套儿童实体及其列的新的列更新以及新的添加/修改。

您可能会发现有关更新有用的ASP.NET核心文档。ASP.NET Core的示例使用EF Core。

我个人倾向于采用您的第一种方法,即加载实体,对其进行更新并调用Savechanges,原因有两个:

  1. 方法2意味着实体中的所有数据都将被暴露,并且可以更改隐藏值。那是安全风险。
  2. 方法2通常更快,但是如果您的实体中有很多数据,则可能会慢,尤其是当您的Web连接速度慢时。

大多数应用程序都有更多的读写,因此我倾向于关注查询,除非测试表明更新/创建/删除很慢。

最新更新