我有一个 C# .NET 6.0 服务,其中添加了一个 OracleDbContext,Servicelifetime 为“Scoped”(据我所知,每个函数调用本质上都会获取自己的 DbContext 实例)。该服务在后台运行,并带有文件观察器,这样当文件被放入特定位置时,它将启动一些处理。首先,它查询数据库表以查看该文件的记录是否存在...如果不存在,那么它将尝试将记录插入到表中。
这是对存在性的检查,返回 null(文件名是主键):
_dbContext.[table].FirstOrDefault(ext => ext.FileName == fileName);
然后这是将被调用的插入函数,它甚至本身首先检查是否存在:
var existingRecord = _dbContext.[table]
.FirstOrDefault(s => s.FileName == obj.FileName);
if (existingRecord != null)
{
return true;
}
_dbContext.[table].Add(obj);
_dbContext.SaveChanges();
但是此服务运行一段时间后,我们最终会看到问题,它将到达 SaveChanges(),然后返回错误“ORA-00001:违反了唯一约束(EPADMIN.[主键约束])”。然而,记录已成功插入表中。当这种情况开始发生时,我们重新启动服务,并且通常会在一段时间内再次正常。 整个过程,除了服务本身的开始(服务启动时启动的功能)之外,都是同步的。数据库上下文确实配置了执行策略,因此它使用默认的事务隔离级别“已提交读”。有一次,我尝试为事务指定可序列化(在设置连接弹性之前),但这导致了它自己的问题。
我能做的是,当遇到该特定错误时,基本上只是忽略它并继续前进(因为记录在那里)。但这似乎是一种黑客方法——我担心一旦事情变得不正常,后续插入总是会返回该错误。我如何从一开始就防止他们失控,或者如果发生这种情况,基本上重置整个上下文?
我很乐意提供任何其他有帮助的详细信息。
多种方法之一是尝试将存在检查、插入和 SaveChanges 操作包装在事务中。这确保了操作是原子的,并防止使用相同的主键并发插入。
using var transaction = _dbContext.Database.BeginTransaction();
try
{
var existingRecord = _dbContext.[table].FirstOrDefault(s => s.FileName == obj.FileName);
if (existingRecord == null)
{
_dbContext.[table].Add(obj);
_dbContext.SaveChanges();
transaction.Commit();
}
else
{
transaction.Rollback();
}
}
catch (Exception ex)
{
transaction.Rollback();
// Handle exceptions or log the error.
}