EF 4.1 替换一行?
我有一个问题,在类中我想根据数据库中现有的记录更新或添加一行。在这一点上,我没有“创建/更新”方法,而是有“保存(实体)”方法。
这个想法是根据数据库检查实体,如果存在,则它是更新,如果不存在,则显然是创建。
使用 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();
}
我的问题是 - 这是典型的路线吗?还是有更聪明/更清洁的方法?
I have an issue where inside a class I want to update or add a row depending on the record existing inside the DB. In that, rather than having "Create/Update" methods I have a "Save(entity)" method.
The idea is the entity is checked against the database, if it exists then it's an update if it doesn't then its obviously a create.
Using EF 4.1 the problem is that once I read the row from the database via the same context, then this in turn creates a memory cache of that row. When I then try and replace the row via say an attach/add routine it will obviously throw an exception around the row already existing etc (as its trying to force both currentRow and newRow into the same table and fails in reconcillation).
My work-around for this is basically to use the .Remove() on the context which in marks the row for removal from memory and then re-add it in the same USING transaction.
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();
}
My Question is - is this the typical route to take? or is there much smarter / cleaner ways?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
我相信这段代码应该可以工作,使用
Attach
而不是Add
:请原谅奇怪的包装 - 希望使其适合页面而不滚动。
I believe this code should work, using
Attach
rather thanAdd
:Forgive the weird wrapping - wanted to make it fit on the page without scrolling.
好吧,我认为这是我是个傻瓜的情况。因为我意识到当创建完全相同的两条记录时可能会发生冲突,除了 AccountId 不同(假设它们是通过调用者类生成的)。我已将 DAO 修改为以下内容,现在它可以工作了。
如果您愿意,请把代码分开:)
Ok, me thinks it was a case of me being a dumbass.. In that i realised the conflict may have occured when two records were being created the exact same except the AccountId were different (given they were generated via caller class). I've modifed the DAO to be the below and now it works.
Pick the code apart if you like :)
见样本
see sample
这可能是你提出问题的方式,但你的方法似乎很奇怪。
如果您要在应用程序的同一层中获取和更新实体,则在保存时不应重新创建上下文,只需保留对获取实体的上下文的引用即可,它将跟踪对您的实体,您只需在同一上下文上调用 SaveChanges() 即可。否则,您将与它的基本设计作斗争。
您应该养成将 SaveChanges() 包装在事务中的习惯。如果 SaveChanges() 触发对数据库中超过 1 行的更改/插入,您将面临保存部分更改的风险。您应该始终保存所有内容或什么也不保存。
使用 (TransactionScope ts = new TransactionScope())
{
ctx.SaveChanges();
ts.Complete();
}
假设上述情况,如果您有一个 IsNew 实体,但希望确保它不存在作为最终检查在你的中间层(我假设你的客户已经尝试让用户编辑现有实体(如果存在))。您应该首先对数据库设置唯一性约束,因为这是防止重复的最后一道防线。其次,您可以采用已有的方法,检查实体是否存在于数据库中,然后手动合并实体(如果这是您所需的功能),或者抛出异常/并发异常,强制客户端重新加载真实的实体,他们可以修改该实体。
第4点相当复杂,并且有很多方法,对于我来说太复杂了,无法描述。但要注意,如果您采用检查其存在的方法,然后决定添加/附加,请确保将其包装在事务中,否则新实体有可能在两者之间被另一个用户/进程添加你检查(用你的Where())和SaveChanges()。
It might be how you worded the question, but you seem to have a strange approach.
If you are fetching and updating an entity in the same tier of an application, you shouldn't recreate your context when you save, just hang on to a reference to the context that fetches the entity and it will track the changes to your entity, and you just call SaveChanges() on that same context. You are fighting against its fundamental design otherwise.
You should get in the habit of wrapping you SaveChanges() in a transaction. If SaveChanges() triggers changes / inserts to more than 1 row in the database, you run the risk of saving partial changes. You should always save everything or nothing.
using (TransactionScope ts = new TransactionScope())
{
ctx.SaveChanges();
ts.Complete();
}
If you are developing a 3 tier app using perhaps wcf for the middle tier, and therefore serializing the entity from a client, you could simply add a new property "IsNew" which the client passes through. If not new, you should use Attach(). eg If IsNew, then ctx.Accounts.Add(entity), else ctx.Accounts.Attach(entity)
Assuming the above, if you have an IsNew entity, but want to ensure it doesn't exist as a final check in your middle tier (I assume you client would have already attempted to let the user edit the existing entity if it exists). You should first put a uniqueness constraint on the database, as this is your final defence against duplicates. Second, you can take the approach you have already, where you check if the entity exists in the database, and then either merge the entities by hand if that is your required functionality, or throw an exception / concurrency exception that forces the client to reload the real entity and they can modify that one.
Point 4 is fairly complex, and there are a number of approaches, which are too complex for me to bother trying to describe. But beware, if you take your approach where you check it exists, then decide to add/attach, make sure you wrap that in a transaction, as otherwise there is a chance the new entity will be added by another user/process in between when you check (with your Where()) and SaveChanges().