Linq:在TransactionScope中删除并插入相同的主键值

时间:2011-09-15 13:07:26

标签: c# linq-to-sql transactionscope

我想用一个事务中的新记录替换数据库中的现有记录。使用TransactionScope,我有

using ( var scope = new TransactionScope())
{
     db.Tasks.DeleteAllOnSubmit(oldTasks);
     db.Tasks.SubmitChanges();

     db.Tasks.InsertAllOnSubmit(newTasks);
     db.Tasks.SubmitChanges();

     scope.Complete();
}

我的节目扔了

System.InvalidOperationException: Cannot add an entity that already exists.

经过一些反复试验,我发现罪魁祸首在于删除和插入之间没有任何其他执行指令。如果我在第一个SubmitChanges()和InsertAllOnSubmit()之间插入其他代码,一切正常。谁能解释为什么会这样?这非常令人担忧。

我尝试了另一个更新对象:

IEnumerable<Task> tasks = ( ... some long query that involves multi tables )
.AsEnumerable()
.Select( i => 
{
    i.Task.Duration += i.LastLegDuration;
    return i.Task;
}
db.SubmitChanges();

这两项都不起作用。 db没有对任务进行任何更改。

编辑:

此行为似乎与“交易”无关。最后,我采用了效率极低的更新:

newTasks.ForEach( t =>
{
     Task attached = db.Tasks.Single( i => ... use primary id to look up ... );
     attached.Duration = ...;
     ... more updates, Property by Property ...
}
db.SubmitChanges();

2 个答案:

答案 0 :(得分:1)

您可以尝试通过选择要更新的Id列表并检查列表是否包含每个项目来一次更新多行,而不是插入和删除或进行多个查询。

此外,请确保将事务标记为已完成,以向事务管理器指示所有资源中的状态是否一致,并且可以提交事务。

Dictionary<int,int> taskIdsWithDuration = getIdsOfTasksToUpdate(); //fetch a dictionary keyed on id's from your long query and values storing the corresponding *LastLegDuration*
using (var scope = new TransactionScope(TransactionScopeOption.Required))
{
    var tasksToUpdate = db.Tasks.Where(x => taskIdsWithDuration.Keys.Contains(x.id));
    foreach (var task in tasksToUpdate)
    {
        task.duration1 += taskIdsWithDuration[task.id];
    }        

    db.SaveChanges();
    scope.Complete();
}         

根据您的情况,您可以在表格非常大并且要更新的项目数量相当小的情况下反转搜索,以利用索引编制。如果是这种情况,您现有的更新查询应该可以正常工作,所以我怀疑您是否需要反转它。

答案 1 :(得分:0)

我在LinqToSql中遇到了同样的问题,我不认为它与事务有关,而是与会话/上下文如何合并更改有关。我这样说是因为我通过绕过linqtosql删除并使用一些原始sql来解决这个问题。丑陋我知道,但它有效,而且都在交易范围内。