如何更新(替换)已附加到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)。我怎么能这样做(没有为第二次更新创建新的上下文)?
答案 0 :(得分: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; }
}
让我明确指出这些变化,以及我制作它们的原因。
首先 - 有2个try/catch
块。第一个确保如果对象已经附加,我们不会出错。你实际上并不需要做任何错误 - 它是信息性的,而不是关键的。所以 - 如果它发生就忽略它 - 继续。
第二个try/catch
块优雅地处理任何其他类型的保存错误。例如,如果您的更改违反了外键约束,则此块将捕获失败。
第三件事是我添加了Detach<T>()
方法。这是为了防止.Attach()
的碰撞。从本质上讲,我的假设是,如果你正在编写一个像这样的通用方法,它首先需要.Attach()
个对象 - 那是因为你在没有变更跟踪的情况下使用POCO。在这种情况下 - 在你完成保存/更新之后 - 你应该再次分离是有道理的。
为了完整性 - 我还应该指出,如果你真的要明确地捕获“对象已经附加”错误 - 你应该在第一个InvalidOperationException
块中捕获catch
。
更好的方法
如果您做能够添加基类或接口(如果您使用.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 */
然后我还将.tt
的{{1}}函数更新为如下所示:
EntityClassOpening()
结果是我的所有尚未继承的POCO对象现在都来自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)
);
}
- 这允许我将EntityBase
签名更改为:
Save<T>
...现在我的代码在编译时是类型安全的。
那么 - 你如何有效地使用它?
请记住public T Save<T>(T entity) where T : EntityBase {
方法如果失败则返回.Save<T>()
,如果成功则返回实体的更新版本。我通常称之为:
null
答案 1 :(得分:2)
您可以使用以下内容:
public void Update<TEntity>(TEntity entity) where TEntity : IEntityWithId {
DbSet<TEntity> set = _context.Set<TEntity>();
TEntity original = set.Local.SingleOrDefault(e => e.Id == entity.Id);
if (original != null) {
_context.Entry<TEntity>(original).CurrentValues.SetValues(entity);
} else {
set.Attach(entity);
_context.Entry<TEntity>(entity).State = EntityState.Modified;
}
}
您只需要在实体上实现和接口以支持此操作:
public interface IEntityWithId {
int Id { get; }
}