【问题标题】:Effective approach to update in disconnected EF Core在断开连接的 EF Core 中更新的有效方法
【发布时间】:2018-05-05 01:06:17
【问题描述】:

我有一个 ASP Dotnet Core Web 服务。这与使用 Entity Framework Core 1.1 的 Postgres 数据库对话。当服务需要更新数据库记录以响应客户端请求时,有两种方法。

方法 1

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

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

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

方法 2。

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

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

这两种方法的行为非常不同。

方法 1

专业版。仅更新那些已从数据库中的值更改的值。

骗局。执行两次数据库往返。 (检索实体然后更新)。

方法 2。

专业版。只需执行一次数据库往返即可。

骗局。更新传入的整个对象图,即使实体中只有一个值发生了变化。

Entity Framework Core 文档中关于使用断开连接的实体的页面尚未编写。

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

我们没有一个完整的生产系统和足够的数据来有效地测试这一点,在这个开发阶段,我们正试图解决我们应用程序中风险最大的部分,基于经验的优化不是足够高的优先级让我们现在投资这个时间。

鉴于网络服务器和数据库服务器将位于同一个数据中心内,我正在寻找的是一种有点知情的“10 人入门”方法,如果需要,我们可能会在以后花时间进行优化。

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

我非常欢迎任何信息或指导。

【问题讨论】:

  • 文档已经更新。

标签: c# database postgresql entity-framework entity-framework-core


【解决方案1】:

我的回应是方法2

概要

我想只修改实体的几列以及添加\修改嵌套的子实体

  • 在这里,我正在更新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 Core,如果您不想进行 2 次数据库往返,那么在断开连接的情况下更新数据会很棘手。

  • 尽管 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 Core 更新,如果你使用这个DbContext.Entry(entity).EntityState = EntityState.IsModified,所有的列都会更新。 因此某些列将更新为其默认值,即 null 或默认数据类型值。

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

因此,为了仅更新从客户端发送的列,需要以某种方式告知 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;
        }

    });

}

通过这种方法,我可以轻松地处理任何嵌套子实体及其列的所需列更新以及新的添加/修改。

【讨论】:

    【解决方案2】:

    您可能会发现ASP.NET Core documentation on updating 很有用。 ASP.NET Core 的示例使用 EF Core。

    我个人倾向于使用您的第一种方法,即加载实体、更新实体并调用 SaveChanges,原因有两个:

    1. 方法 2 意味着实体中的所有数据都将被公开,并且隐藏的值可以被更改。这是一个安全风险。
    2. 方法 2 通常更快,但如果实体中有大量数据,则可能会更慢,尤其是当您的网络连接速度很慢时。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-06-20
      • 1970-01-01
      • 1970-01-01
      • 2015-10-24
      • 2012-10-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多