【问题标题】:Auto detection of changes with disconnected entities自动检测断开实体的更改
【发布时间】:2021-01-09 21:22:28
【问题描述】:

我正在 Web 服务器上制作一个简单的编辑器,允许用户将数据更改/添加到存储在 MS SQL 服务器上的单个表中。

我正在使用 Entity Framework 6 来执行此操作,我想知道应该如何跟踪对实体模型所做的更改。

我希望我可以在上下文中加载新数据,并让上下文自动与数据库中的内容进行比较,然后调用 SaveChanges()。

但从我在网上阅读的内容来看,我似乎需要遍历所有数据,并检查自己发生了什么变化,以便我可以调用Context.Entry(myEntry).State = AddedContext.Entry(myEntry).State = Modified

EF 是否无法自动检测新增内容、修改内容和未更改内容?

【问题讨论】:

    标签: entity-framework entity-framework-6


    【解决方案1】:

    我建议将 ViewModel 或 DTO 传递给视图,然后在提交时将它们映射回重新加载的实体。 EF 只会自动更新设置值时更改的值。在不更改值的情况下设置值不会触发更新。 (附加实体并设置它的修改状态将更新 所有 列)传递实体虽然方便,但比 UI 可能呈现的更多地暴露了您的数据结构,并且可以在被发回之前被篡改.永远不要相信客户返回的任何东西。当序列化到客户端时,数据不再是实体,而是 JSON 数据块。当发送回服务器时,它不是一个被跟踪的实体,它是一个带有实体签名的 POCO。 EF 实体可以提供的任何更改跟踪都不会应用于客户端或在序列化/反序列化过程中继续存在。

    例如:

    给定一个有名字和出生日期的孩子。我们选择一个 DTO 传递给视图。视图更改名称,我们取回 DTO 并复制 所有 值,修改或以其他方式返回实体并调用 SaveChanges()

    // For example, loading the child in the controller to pass to the view...
    ChildDTO childDto = null;
    using (var context = new TestDbContext())
    {
        childDto = context.Children
           .Select(x => new ChildDto
           {
               ChildId = x.ChildId,
               Name = x.Name,
               BirthDte = x.BirthDate
           }).Single(x => x.ChildId == 1);
    }
    
    // View updates just the name...
    childDto.Name = "Luke";
    
    
    // Example if the view passed DTO back to controller to update...
    using (var context = new TestDbContext())
    {
        var child = context.Children.Single(x => x.ChildId == 1);
        child.Name = childDto.Name;
        child.BirthDate = childDto.BirthDate;
        context.SaveChanges();
    }
    

    如果姓名发生更改而出生日期没有更改,则 EF 生成的更新语句只会更新姓名。如果实体名称已经是“Luke”,则不会发出更新语句。您可以使用 SQL 分析器验证此行为,以查看 SQL EF 是否/何时/向数据库发送什么。

    Automapper 可以帮助简化此操作,以便将 DTO 重新放入实体:

    var mappingConfig = new MapperConfiguration(cfg =>
    {
        cfg.CreateMap<Child, ChildDTO>();
        cfg.CreateMap<ChildDTO, Child>();
    });
    

    然后在阅读时,利用ProjectTo 而不是Select

    using (var context = new TestDbContext())
    {
        childDto = context.Children
            .ProjectTo<ChildDTO>(mappingConfig)
            .Single(x => x.ChildId == 1);
    }
    

    ...更新实体时:

    using (var context = new TestDbContext())
    {
        var child = context.Children.Single(x => x.ChildId == 1);
        var mapper = mappingConfig.CreateMapper();
        mapper.Map(childDto, child); // copies values from DTO to the entity instance.
        context.SaveChanges();
    }
    

    在将值复制到实体之前验证 DTO 非常重要,无论是手动还是使用 Automapper。 Automapper 配置也可以设置为仅复制预期/允许更改的值。

    【讨论】:

    • 感谢详细而出色的回答!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-05
    • 2017-03-16
    • 2013-07-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多