c# - EF Core 中的断开连接的实体

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

我一直在寻找在断开连接的情况下记录实体的更改。

原始问题:我调用 DbContext.Update(Entity)在更新的复杂实体上,即使没有任何变化,所有内容都被 ChangeTracker 和我的并发标记为已更改 行版本 列数。

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 逻辑位于 数据库上下文 并且仅在修改实体状态时更改行版本。
foreach (var entity in ChangeTracker.Entries().Where(e => e.State == EntityState.Modified))
        {
            var saveEntity = entity.Entity as ISavingChanges;
            saveEntity.OnSavingChanges();
        }

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

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

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

2.- 如本文所述
https://blog.tonysneed.com/2017/10/01/trackable-entities-for-ef-core/您可以下载 NuGet 包并实现 可合并 可追踪
我的实体上的接口(interface)。

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

3.- 书中有另一种解决方案编程 Entity Framework 关于 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 中非常有用的功能,因为大多数开发人员都将它用于断开连接的场景。

最佳答案

我的回复是使用 轨迹图
概要
我要只修改几列实体以及 添加\修改嵌套子实体

  • 在这里,我正在更新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可用于标记Added的方法, UnChanged实体的状态。

    difference is that with TrackGraph, you can add custom logic, as it iteratively navigates through Navigation properties of the entity.


    我使用 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;
            }
    
        });
    
    }
    
    使用这种方法,我能够轻松处理任何嵌套子实体及其列的仅必需的列更新以及新的添加/修改。

    关于c# - EF Core 中的断开连接的实体,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55844343/

    相关文章:

    c# - 如何限制从互联网访问网络资源但在内网公开

    c# - 在 ASP.Net Core App 上调试期间未注入(inject) appSetting.Development.json

    c# - ASP.NET 核心 2.1 : Navigating back to a page after an HTTP POST fails validation displays a browser error

    c# - EF 核心 : Update object graph duplicates child entities

    c# - Windows 8.1 Bing map 控件

    c# - 在winform中创建一个本地数据库并在后台从服务器获取数据

    asp.net-core-2.1 - 如何将 Controller 重定向到区域部分 asp Core 2.1 中的 Razor 页面

    .net - 实体从数据库中具体化后的 EF Core 事件

    c# - 如果另一个按钮被选中,则自动检查按钮的网络表单