.net - EF5 CF - 更新附加对象

标签 .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;
}

在某些时候,它可以接收 2 个或更多具有相同键值的 不同 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(); 

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

最佳答案

只是想我会把它添加到组合中,因为我看到了这篇文章 - 并使用了 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; }
}

让我明确指出这些更改,以及我做出这些更改的原因。

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

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

第三件事是我加了一个Detach<T>()方法。这是为了防止 .Attach() 的碰撞以及。本质上,我的假设是,如果您正在编写一个像这样的通用方法,它需要 .Attach()首先是对象 - 这是因为您使用的是 POCO 而没有更改跟踪。既然如此——在你完成保存/更新之后——你应该再次分离是有道理的。

为了完整起见——我还应该指出,如果你真的想明确捕获“对象已附加”错误——你应该捕获 InvalidOperationException在第一个catch block 。

更好的方法

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

public void Save<T>(T entity) where T : BaseClass {

在我的应用程序的 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 */

然后我还更新了 .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)
    );
}

结果是我所有没有继承的 POCO 对象现在都派生自 EntityBase - 这让我可以改变我的 Save<T>签名:

public T Save<T>(T entity) where T : EntityBase {

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

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

请记住 .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);

关于.net - EF5 CF - 更新附加对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12299679/

相关文章:

c# - 流畅的 NHibernate 和延迟加载

c# - 如果一项操作失败则回滚

c# - 如何解码 JWT Token?

c# - 如何在c#中使用kubernetes-client创建k8s部署?

c# - 使用 Windows 身份验证和匿名身份验证获取 UserPrincipal

c# - .NET 或 COM HID iCLASS 智能卡读卡器

c# - 如何首先手动添加导航属性模型

c# - 使用 DbContext Set<T>() 而不是在上下文中公开

c# - 在 MVC3 中处理创建和更新复杂数据类型的最佳方法是什么?

c# - EF vs SQL 奇怪的慢