1

我正在使用自定义方法来跟踪 n 层断开连接的实体类的各个修改属性。我从

实体框架编程:Julia Lerman 和 Rowan Miller (O'Reilly) 的 DbContext。版权所有 2012 Julia Lerman 和 Rowan Miller,978-1-449-31296-1。

代码是:

public void ApplyChanges<TEntity>(TEntity root) where TEntity : class, IObjectWithState {
        // bind the entity back into the context
        dbContext.Set<TEntity>().Add(root);
        // throw exception if entity does not implement IObjectWithState
        CheckForEntitiesWithoutStateInterface(dbContext);

        foreach (var entry in dbContext.ChangeTracker.Entries<IObjectWithState>()) {
            IObjectWithState stateInfo = entry.Entity;
            if (stateInfo.State == RecordState.Modified) {
                // revert the Modified state of the entity
                entry.State = EntityState.Unchanged;
                foreach (var property in stateInfo.ModifiedProperties) {
                    // mark only the desired fields as modified
                    entry.Property(property).IsModified = true;
                }
            } else {
                entry.State = ConvertState(stateInfo.State);
            }
        }
        dbContext.SaveChanges();
    }

此方法的目的是让 EF 知道只有一组预定义的实体字段已准备好在下次调用 SaveChanges() 时进行更新。这是为了解决实体在 ASP.NET MVC 3 中工作的问题,如下所示:

  • 在初始页面加载时:控制器的 Get 操作正在加载实体对象并将其作为参数传递给视图。

  • 视图生成用于编辑实体的 2 个字段的控件,并将记录的 ID 保存在隐藏字段中。

  • 当点击 [save] 并将实体发布回控制器时,除了视图中保留的 3 个字段之外,所有字段都带有一个空值。这是 MVC 绑定管理器的默认行为。

如果我将更改保存回数据库,更新查询当然会用如下语句覆盖非映射字段:

UPDATE non_mapped_field_1 = NULL, ..., mapped_field_1 = 'mapped_value_1', mapped_field_2 = 'mapped_value_2', ... non_mapped_field_n = NULL WHERE ID = mapped_field_3

这就是我尝试单独跟踪字段并仅更新我感兴趣的那些字段的原因。在使用 ApplyChanges() 调用自定义方法之前,我正在添加要包含在更新中的字段列表IObjectWithState.ModifiedProperties 列表,以获取 SQL 语句如下:

UPDATE mapped_field_1 = 'mapped_value_1', mapped_field_2 = 'mapped_value_2' WHERE id = mapped_value_3

问题是,在 ApplyChanges 中将其中一个字段标记为已修改时,即:

entry.Property(property).IsModified = true;

系统抛出以下异常

{System.InvalidOperationException: Member 'IsModified' cannot be called for property 'NotifyCEDeadline' on entity of type 'User' because the property is not part of the Entity Data Model.

at System.Data.Entity.Internal.InternalPropertyEntry.ValidateNotDetachedAndInModel(String method)

at System.Data.Entity.Internal.InternalPropertyEntry.set_IsModified(Boolean value)

at System.Data.Entity.Infrastructure.DbPropertyEntry.set_IsModified(Boolean value)

...

所以问题是。有一种方法可以绕过这个 EF 验证,或者让上下文知道我正在尝试更改的这个系统属性 (IsModified) 的存在吗?

架构总结:

  • EF 代码优先(注解 + Fluent API)
  • Oracle .NET EF 数据提供程序 (ODAC)
  • 使用 nInject.MVC 将上下文注入到自定义业务上下文 => 这就是我自定义 ApplyChanges() 方法的原因

    使用 (var context = new BreakAwayContext()){ context.Set().Add(root);

    对已经初始化的 dbcontext 的简单调用

    dbContext.Set().Add(root);

  • Oracle 数据库是手动创建的,即没有 EF 的帮助,因此不使用 EF 元数据表。

谢谢,伊万。

4

1 回答 1

0

很好的描述,但是我找不到任何关于为什么您需要在对象中使用名为“IsModified”的瞬态属性和/或为什么需要告诉 EF 它正在被修改的任何信息(EF 无论如何都无法持久保存它)。

如果属性包含在视图中,则 IsModified 属性的值应由模型绑定器设置。

您可以在 ApplyChanges 方法中添加代码以跳过名为“IsModified”的属性,或者更好的是,使用 entry.CurrentValues.PropertyNames 仅过滤已知属性,例如:

foreach (var property in stateInfo.ModifiedProperties) { 
    // mark only the desired fields as modified 
    if (entry.CurrentValues.PropertyNames.Contains(property)) {
        entry.Property(property).IsModified = true; 
    }
} 

更新:伊万,很抱歉,当您几个月前发布该问题时,我没有更好地理解该问题,并且在您添加这些澄清评论后我没有跟进。我想我现在理解得更好了。也就是说,我认为我提供的代码片段可以成为解决方案的一部分。通过查看您再次遇到的异常,我现在了解到 EF 检测到的问题是 NotifyCEDDealine 不是持久属性(即它没有在 Code First 模型中映射到数据库中的列)。IsModified 只能用于映射属性,因此您有两个选择:更改实体中 IObjectWithState 的实现代码,以便未在 ModifiedProperties 中记录非映射属性,或者使用我的代码片段来防止调用 IsModified那些。

顺便说一句,执行所有这些操作的替代方法是使用 Controller.TryUpdateModel API 仅设置实体中修改后的属性。

希望这会有所帮助(尽管我知道已经很晚了)。

于 2012-04-27T19:52:03.597 回答