EF4.1 + MARS - 在UPDATE操作中不允许新的事务。

3

当我尝试更新数据库中的一个实体时,出现了以下异常:

由于会话中有其他线程正在运行,因此不允许新事务。

我使用相同的代码块来处理所有传入的数据(从Excel工作簿中抓取)。工作正常的INSERT和无法正常工作的UPDATE之间唯一的区别如下:

public void UploadWorkbook(Workbook workbook, string uploadedBy, string comments)
{
    workbook.UploadedBy = uploadedBy;
    workbook.Comments = comments;

    var existing = _repository.Get(x => x.FileName == workbook.FileName);
    if (existing == null)
        _repository.Insert(workbook); // this works on the commit
    else
        _repository.Update(workbook); // this causes commit to fail

    _unitOfWork.Commit(); // fails when repository runs update method
}

此外,这是更新方法:

public override void Update(Workbook entity)
{
    var existing = Get(x => x.FileName == entity.FileName);

    if (existing == null)
    {
        var message = string.Format(@"Error :: Cannot update Workbook '{0}' 
                                    because it does not exist in the database.", 
                                    entity.FileName);

        throw new Exception(message);
    }

    existing.Version = entity.Version;
    existing.DateModified = DateTime.Now;
    existing.Comments = entity.Comments;
    existing.FilePath = entity.FilePath;

    if (existing.Data != null)
        existing.Data.Clear();

    existing.Data = entity.Data;
}

这是Get方法的实现:

public virtual T Get(Func<T, bool> where)
{
    return _dbSet.Where(where).FirstOrDefault();
}

我看了一些其他关于类似问题的帖子,但并没有像我遇到的这样。我真的被卡住了,因为我无法理解为什么INSERT是有效的,但UPDATE失败了...如果有另一个事务在进行,那么不会导致任何一种操作失败吗?


你如何处理上下文/存储库/集合的生命周期?你同时运行多少个操作? - Ladislav Mrnka
@LadislavMrnka 我的上下文和存储库的生命周期是基于HTTP请求,并且我没有进行任何并发操作。我启用MARS的唯一原因是为了惰性加载和执行嵌套查询,以避免“已经有一个打开的数据读取器”异常。 - shuniar
1个回答

3

我的第一个怀疑是你的Get请求

Get(x => x.FileName == entity.FileName);

隐式地创建了一个新的线程/事务,在提交工作单元之前不会关闭。然后,您的工作单元试图在调用提交时创建一个全新的事务,这与已经打开的Get()事务冲突。

如果是这种情况,那么您可能希望找出如何使Get调用在与commit调用相同的事务中运行。

编辑:

我认为您可以通过简单更改来解决问题

public virtual T Get(Func<T, bool> where)
{
    return _dbSet.Where(where).FirstOrDefault();
}

to

public virtual T Get(Func<T, bool> where)
{
    return _dbSet.Where(where).SingleOrDefault();
}

SingleOrDefault()应该强制序列完成“读取”并释放连接以进行提交事务。(它可能更安全,因为如果查询返回多个结果,则无法确定实际获取的记录是哪一个,因为没有指定排序。使用SingleOrDefault()将在返回多行时引发异常)

或者您可以尝试使用明确的事务作用域:

public void UploadWorkbook(Workbook workbook, string uploadedBy, string comments)
{
    workbook.UploadedBy = uploadedBy;
    workbook.Comments = comments;

    using (var transaction = new TransactionScope())
    {
        var existing = _repository.Get(x => x.FileName == workbook.FileName);
        if (existing == null)
            _repository.Insert(workbook); // this works on the commit
        else
            _repository.Update(workbook); // this causes commit to fail

        _unitOfWork.Commit(); // fails when repository runs update method
        transaction.Complete();
    }
}

(另请参阅有关此错误消息的Microsoft Connect页面

谢谢您的回复。我已经在上面添加了Get()方法的实现。 - shuniar
我看到了你的Get()实现,但我认为需要更多的细节:"_dbSet"的类型是什么,它是如何初始化的? - Nathan
_dbSet的类型是IDbSet<T>,在Repository构造函数中初始化为_dbSet = DbContext.Set<T>();。我已经重写了它,以返回一个IDbSet<T>而不是DbSet<T>。此外,该存储库需要一个DatabaseFactory来创建DbContext。 - shuniar
根据您的评论添加了额外的细节。 - Nathan
我已经更新了我的 Get() 方法,使用了 SingleOrDefault(),但仍然遇到相同的错误。此外,我希望避免使用 TransactionScope(),因为它需要启用 DTC。你有其他建议吗?我还应该注意到,我有一个 OLEDB DataReader 用于抓取 Excel 文件,但无论我是在 _unitOfWork.Commit() 之前还是之后处理它,我仍然会得到相同的异常。 - shuniar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接