我有一个问题,我想要在类中更新或添加一行,具体取决于数据库中存在的记录。在那里,我没有“创建/更新”方法,而是使用“保存(实体)”方法。
这个想法是对数据库检查实体,如果它存在,那么它是一个更新,如果它没有,那么它显然是一个创建。
使用EF 4.1的问题是,一旦我通过相同的上下文从数据库中读取行,那么这又会创建该行的内存缓存。当我尝试通过附加/添加例程替换行时,它显然会在已存在的行周围抛出异常(因为它试图强制currentRow和newRow进入同一个表并且在重新调整时失败)。
我的解决方法基本上是在上下文中使用.Remove(),它标记要从内存中删除的行,然后在同一个USING事务中重新添加它。
var ctx = new SecurityContext(this.ConnectionString);
using(ctx)
{
var dbEntry = (ctx.Accounts.Where(a => a.AccountId == entity.AccountId || a.Username == entity.Username)).FirstOrDefault();
ctx.Accounts.Remove(dbEntry);
if (dbEntry != null)
{
ctx.Entry(entity).State = EntityState.Added;
} else
{
ctx.Accounts.Add(entity);
ctx.Entry(entity).State = EntityState.Added;
}
ctx.SaveChanges();
}
我的问题是 - 这是典型的路线吗?还是有更聪明/更清洁的方式?
答案 0 :(得分:0)
我认为此代码应该有效,使用Attach
而不是Add
:
var ctx = new SecurityContext(this.ConnectionString);
using(ctx)
{
ctx.Accounts.Attach(entity);
ctx.Entry(entity).State = ctx.Accounts.Any(
a => a.AccountId == entity.AccountId ||
a.Username == entity.Username) ?
EntityState.Modified : EntityState.Added;
ctx.SaveChanges();
}
原谅奇怪的包装 - 想让它适合页面而不滚动。
答案 1 :(得分:0)
好吧,我认为这是一个让我成为笨蛋的案例..因为我意识到当创建两个记录时可能发生冲突,除了AccountId之外完全相同(假设它们是通过调用者类生成的) 。我已将DAO修改为以下版本,现在它可以正常工作。
如果您愿意,请选择相应的代码:)
public class AccountDAO : BaseDAO<Account>
{
public AccountDAO(string connectionString) : base(connectionString)
{
}
public override Account Save(Account entity)
{
var ctx = new SecurityContext(this.ConnectionString);
using(ctx)
{
//var dbEntry = (ctx.Accounts.Where(a => a.AccountId == entity.AccountId || a.Username == entity.Username)).FirstOrDefault();
var dbEntry = (ctx.Accounts.Any(a => a.AccountId == entity.AccountId || a.Username.Contains(entity.Username)));
//ctx.Accounts.Remove(entity);
if(!dbEntry)
{
ctx.Accounts.Add(entity);
ctx.Entry(entity).State = EntityState.Added;
} else
{
var currEntity = Read(entity);
entity.AccountId = currEntity.AccountId;
ctx.Accounts.Add(entity);
ctx.Entry(entity).State = EntityState.Modified;
}
ctx.SaveChanges();
}
return entity;
}
public override Account Read(Account entity)
{
using (var ctx = new SecurityContext(this.ConnectionString))
{
var newEntity = (ctx.Accounts.Where(a => a.AccountId == entity.AccountId || a.Username.Contains(entity.Username))).FirstOrDefault();
return newEntity;
}
}
public override void Delete(Account entity)
{
using (var ctx = new SecurityContext(this.ConnectionString))
{
var ent = Read(entity);
ctx.Entry(ent).State = EntityState.Deleted;
ctx.Accounts.Remove(ent);
ctx.SaveChanges();
}
}
}
答案 2 :(得分:0)
参见样本
private void Save(Action<Controls.SaveResult> saveResult)
{
if (SavableEntity.EntityState != EntityState.Unmodified)
{
if (SavableEntity.EntityState == EntityState.Detached || SavableEntity.EntityState == EntityState.New)
this.SetIsBusy("Creating new...");
else
this.SetIsBusy("Saving changes...");
DomainContext.SavePartial(SavableEntity, p =>
{
this.ReleaseIsBusy();
if (p.HasError)
{
var res = new Controls.SaveResult() { HasErrors = true };
if (saveResult != null)
saveResult(res);
if (this.EntitySaved != null)
{
EntitySaved(this, new SavedEventArgs() { result = res });
}
p.MarkErrorAsHandled();
}
else
{
Messenger.Default.Send<T>(this.SavableEntity);
var res = new Controls.SaveResult() { HasErrors = false };
if (saveResult != null)
saveResult(res);
if (this.EntitySaved != null)
EntitySaved(this, new SavedEventArgs() { result = res });
if (this.CloseAfterSave)
this.RaiseRequestToClose();
this.RaisePropertyChanged("Title");
}
RaisePropertyChanged("SavableEntity");
}, false);
}
}
答案 3 :(得分:0)
这可能就是你如何表达这个问题,但你似乎有一种奇怪的方法。
如果要获取和更新应用程序的同一层中的实体,则不应在保存时重新创建上下文,只需挂起对提取实体的上下文的引用,它将跟踪对实体的更改,您只需在同一个上下文中调用SaveChanges()。否则你就是在反对它的基本设计。
你应该养成在事务中包装SaveChanges()的习惯。如果SaveChanges()触发更改/插入数据库中的多行,则存在保存部分更改的风险。你应该永远保存所有东西。
使用(TransactionScope ts = new TransactionScope())
{
ctx.SaveChanges();
ts.Complete();
}
如果您正在使用wcf为中间层开发3层应用程序,并因此从客户端序列化实体,您可以简单地添加客户端通过的新属性“IsNew”。如果不是新的,您应该使用Attach()。例如,如果是IsNew,那么ctx.Accounts.Add(实体),否则ctx.Accounts.Attach(实体)
假设上述情况,如果您有一个IsNew实体,但希望确保它不存在作为中间层的最终检查(我假设您的客户端已经尝试让用户编辑现有的实体(如果存在))。您应该首先在数据库上添加唯一性约束,因为这是对重复项的最终防御。其次,您可以采用已经存在的方法,检查实体是否存在于数据库中,然后手动合并实体(如果这是您所需的功能),或抛出异常/并发异常,强制客户端重新加载真正的实体,他们可以修改那个。
第4点相当复杂,有很多方法,对我来说太复杂了,无法尝试描述。但请注意,如果您采用自己的方法检查它是否存在,那么决定添加/附加,确保将其包装在事务中,否则新实体有可能在其间由另一个用户/进程添加检查(使用Where())和SaveChanges()。