EF Core解决方案,用于处理断开连接的实体

时间:2019-04-25 07:51:22

标签: c# asp.net-core-2.1 entity-framework-core-2.2

我一直希望在断开连接的情况下记录实体上的更改。

原始问题::我在更新后的复杂实体上致电DbContext.Update(Entity),尽管没有任何变化,但ChangeTracker和我的并发 RowVersion 列数递增。

POST用户无更改

"userSubPermissions": [],
"fidAdresseNavigation": {
    "idAdresse": 1,
    "fidPlzOrt": 3,
    "strasse": "Gerold Str.",
    "hausnr": "45",
    "rowVersion": 10,
    "isDeleted": 0,
    "fidPlzOrtNavigation": {
        "idPlzOrt": 3,
        "plz": "52062",
        "ort": "Aachen",
        "rowVersion": 9,
        "isDeleted": 0
    }
},
"idUser": 35,
"fidAnrede": null,
"fidAdresse": 1,
"fidAspnetuser": "a7ab78be-859f-4735-acd1-f06cd832be7e",
"vorname": "Max",
"nachmname": "Leckermann",
"eMail": "kunde@leasing.de",
"rowVersion": 11,
"isDeleted": 0

POST返回的用户

"userSubPermissions": [],
"fidAdresseNavigation": {
    "idAdresse": 1,
    "fidPlzOrt": 3,
    "strasse": "GeroldPenis Str.",
    "hausnr": "45",
    "rowVersion": 11,
    "isDeleted": 0,
    "fidPlzOrtNavigation": {
        "idPlzOrt": 3,
        "plz": "52062",
        "ort": "Aachen",
        "rowVersion": 10,
        "isDeleted": 0
    }
},
"idUser": 35,
"fidAnrede": null,
"fidAdresse": 1,
"fidAspnetuser": "a7ab78be-859f-4735-acd1-f06cd832be7e",
"vorname": "Max",
"nachmname": "Leckermann",
"eMail": "kunde@leasing.de",
"rowVersion": 12,
"isDeleted": 0

RowVersion逻辑位于 DBContext 中,并且仅在修改“实体状态”时更改行版本。

foreach (var entity in ChangeTracker.Entries().Where(e => e.State == EntityState.Modified))
        {
            var saveEntity = entity.Entity as ISavingChanges;
            saveEntity.OnSavingChanges();
        }

根据我过去两天的调查,EF Core似乎有两种选择。

1.--如本文章和许多类似文章所述,您可以使用TrackGraph https://www.mikesdotnetting.com/article/303/entity-framework-core-trackgraph-for-disconnected-data

问题::当存在实体ID时,即使没有任何更改,此解决方案也将整个实体标记为已更改。我得到与上述完全相同的结果, RowVersion 递增计数,这意味着我的实体及其所有属性都被标记为已修改。

2.-如本文所述 https://blog.tonysneed.com/2017/10/01/trackable-entities-for-ef-core/您可以下载一个NuGet软件包并实现 IMergeable ITrackable 我的实体上的界面。

问题::客户端需要跟踪模型中的更改并将其传递给API,我想避免这种情况,因为Angular似乎没有为此提供好的解决方案。

3.- 编程实体框架书中还有另一种解决方案,关于EF 6如何通过记录原始值第4章:记录原始值来处理断开连接的情况。 收到更新时,上下文会从数据库读取实体,然后将传入的实体与数据库实体进行比较,以查看是否对属性进行了任何更改,然后将其标记为“已修改”。

https://www.oreilly.com/library/view/programming-entity-framework/9781449331825/ch04.html

问题:本书中描述的某些代码无法在EF Core中实现,其中一个示例是。

((IObjectContextAdapter)this).ObjectContext
.ObjectMaterialized += (sender, args) =>
{
  var entity = args.Entity as IObjectWithState;
  if (entity != null)
  {
    entity.State = State.Unchanged;

    entity.OriginalValues =
      BuildOriginalValues(this.Entry(entity).OriginalValues);
  }
};

在断开连接的场景中,必须有一种方法可以实现这种巧妙的解决方案,而不是在客户端上处理API上的更改,否则,这可能是EF Core中非常有用的功能,因为大多数开发人员都将其用于断开连接的场景。

1 个答案:

答案 0 :(得分:1)

我的回复是使用 TrackGraph

简介

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

  • 在这里,我正在更新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;
        }

    });

}

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