在断开连接的EF Core

时间:2017-11-21 14:16:28

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

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

方法1

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

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

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

  4. 方法2。

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

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

    3. 这两种方法的表现非常不同。

      方法1

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

      精读。需要执行两次数据库往返。 (检索实体然后更新)。

      方法2。

      Pro中。只执行一次数据库往返。

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

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

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

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

      鉴于网络服务器和数据库服务器将存在于同一数据中心内,我所寻找的是10'如果需要,我们可能会花时间进行优化。

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

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

2 个答案:

答案 0 :(得分:2)

我的答复是针对方法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;
}

但是,如果您使用此DbContext.Entry(entity).EntityState = EntityState.IsModified,则会断开EF Core更新的连接,这将更新所有列。 因此某些列将更新为默认值,即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;
        }

    });

}

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

答案 1 :(得分:0)

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

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

  1. 方法2表示实体中的所有数据都将被公开,隐藏值可能会被更改。这是一种安全风险。
  2. 方法2通常更快,但如果您在实体中有大量数据,那么它可能会更慢,尤其是如果您的网络连接速度很慢。
  3. 大多数应用程序都有更多的写入读取,因此我倾向于关注查询,除非测试显示更新/创建/删除速度很慢。