EF5 CF - 更新附加对象

And*_*rey 2 .net entity-framework

如何更新(替换)已附加到EF DbContext的对象?

DAL中有Update()方法:

public int Update<TEntity>(TEntity entity)
{
    this._context.Set<TEntity>().Attach(entity);
    this._context.Entry<TEntity>(entity).State = EntityState.Modified;
}
Run Code Online (Sandbox Code Playgroud)

在某些时候它可以接收两个或更多不同的具有相同键值的TEntity实例(简单的例子,而不是从实际项目):

var e1 = new SomeEntity() { Id = 1; }
dal.Update(e1);
...    
var e2 = new SomeEntity() { Id = 1; }
dal.Update(e2); 
// Exception: An object with the same key already exists in the ObjectStateManager. 
...
dal.Commit(); 
Run Code Online (Sandbox Code Playgroud)

我需要保存旧的最后一个值(e2).我该怎么做(没有为第二次更新创建新的上下文)?

Tro*_*ord 5

我想我会把它添加到混合中,因为我遇到了这篇文章 - 并使用了Ladislav建议的解决方案,但有一些变化:

public T Save<T>(T entity) where T : class {
    try {
        this.Set<T>().Attach(entity);
    } catch { 
      // You may wish to add logging here, instead of throwing away the exception
    }

    try {
        this.Entry<T>(entity).State = EntityState.Modified;
        this.SaveChanges();
        this.Entry<T>(entity).Reload(); // Update any server-generated fields
        this.Detach<T>(entity); // Detach the object again, to avoid collisions
    } catch { return null; }

    return entity; // Return the updated version of the object.
}
internal void Detach<T>(T entity) where T : class {
    ObjectContext.Detach(entity);
}

public ObjectContext ObjectContext {
    get { return ((IObjectContextAdapter)this).ObjectContext; }
}
Run Code Online (Sandbox Code Playgroud)

让我明确指出这些变化,以及我为什么要这些变化.

首先 - 有2 try/catch个街区.第一个确保如果对象已经附加,我们不会出错.您实际上不需要对错误任何事情 - 它是信息性的,而不是关键的.所以 - 只要它发生就忽略它 - 然后继续.

第二个try/catch块优雅地处理任何其他类型的保存错误.例如,如果您的更改违反了外键约束,则此块将捕获失败.

第三件事是我添加了一个Detach<T>()方法.这也是为了防止碰撞.Attach().从本质上讲,我的假设是,如果你正在编写一个像这样的通用方法,它首先需要.Attach()对象 - 这是因为你在没有变更跟踪的情况下使用POCO.在这种情况下 - 在你完成保存/更新之后,你应该再次分离是有意义的.

为了完整性 - 我还应该指出,如果你真的想明确地捕获"对象已经附加"错误 - 你应该InvalidOperationException在第一个catch块中捕获.

一个更好的方法

如果您确实能够添加基类或接口(如果使用.tt生成的对象,则应该这样做),可以通过替换第一行来提高此解决方案的类型安全性,如下所示:

public void Save<T>(T entity) where T : BaseClass {
Run Code Online (Sandbox Code Playgroud)

在我的应用程序的EF5 POCO.tt文件中,我已将其更改为为每个对象输出基类,并添加了一些序列化支持以及其他一些调整.它的肉来自这些线:

/* THIS SECTION ADDED IN ORDER TO CREATE A GLOBAL BASE CLASS */

fileManager.StartNewFile("EntityBase.cs");
#>
using System.Runtime.Serialization;

<# BeginNamespace(code); #>
<# foreach (var entity in typeMapper.GetItemsToGenerate<EntityType>(itemCollection)) { #>
[KnownType(typeof(<#=code.Escape(entity)#>))]
<# } #>
public abstract class EntityBase { }
<#
EndNamespace(code);

/* END INJECTED BASE CLASS */
Run Code Online (Sandbox Code Playgroud)

然后我还将更新.ttEntityClassOpening()函数看起来如下所示:

public string EntityClassOpening(EntityType entity) {
    String baseType = _typeMapper.GetTypeName(entity.BaseType);
    return string.Format(
        CultureInfo.InvariantCulture,
        "{0} {1}partial class {2}{3}",
        Accessibility.ForType(entity),
        _code.SpaceAfter(_code.AbstractOption(entity)),
        _code.Escape(entity),
        _code.StringBefore(" : ", String.IsNullOrWhiteSpace(baseType) ? "EntityBase" : baseType)
    );
}
Run Code Online (Sandbox Code Playgroud)

结果是我的所有POCO对象现在都没有继承EntityBase- 这允许我将我的Save<T>签名更改为:

public T Save<T>(T entity) where T : EntityBase {
Run Code Online (Sandbox Code Playgroud)

...现在我的代码在编译时是类型安全的.

那么 - 你如何有效地使用它?

请记住,如果.Save<T>()方法null失败,则返回该方法,如果成功,则返回实体的更新版本.我通常称之为:

if (null == (entity = db.Save(entity))
    throw new Exception( ... );

// If we got this far, it's because the save succeeded.
DoSomething().With(entity);
Run Code Online (Sandbox Code Playgroud)