【发布时间】:2021-12-13 10:16:31
【问题描述】:
免责声明:这个问题是关于近 17 年老系统的“现代化”。 17 年前使用的 ORM 要求 PK 不使用 Identity。 我知道这有多糟糕,对此我无法改变任何事情。
所以我在数据库中有(简化的)下表:
CREATE TABLE [dbo].[KlantDocument](
[OID] [bigint] NOT NULL,
[Datum] [datetime] NOT NULL,
[Naam] [varchar](150) NOT NULL,
[Uniek] [varchar](50) NOT NULL,
[DocumentSoort] [varchar](50) NULL,
CONSTRAINT [PK_KlantDocument] PRIMARY KEY CLUSTERED
(
[OID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[KlantDocument] CHECK CONSTRAINT [FK_KlantDocument_Klant]
GO
如您所见,table 没有在 PK 上设置 Identity,所以必须手动插入。
项目正在 Web Api .Net Core 5 中重建,它将执行所有 CRUD 操作。 它使用 EF Core 作为 ORM,并且同意这里将使用 Unit of Work 模式(请继续阅读,UoW在这里不是问题)。
对于那些好奇或想知道它的价值的人,你可以在这里查看它 (https://pastebin.com/58bSDkUZ)(这绝不是完整的 UoW,只是部分没有 cmets)。 p>
更新:调用者控制器操作:
[HttpPost]
[Route("UploadClientDocuments")]
public async Task<IActionResult> UploadClientDocuments([FromForm] ClientDocumentViewModel model)
{
if (!ModelState.IsValid)
return BadRequest(ModelStateExtensions.GetErrorMessage(ModelState));
var dto = new ClientDocumentUploadDto
{
DocumentTypeId = model.DocumentTypeId,
ClientId = model.ClientId,
FileData = await model.File.GetBytes(),
FileName = model.File.FileName, // I need this property as well, since I have only byte array in my "services" project
};
var result = await _documentsService.AddClientDocument(dto);
return result ? Ok() : StatusCode(500);
}
当我插入记录时,我会这样做:
// Called by POST method multiple times at once
public async Task<bool> AddClientDocument(ClientDocumentUploadDto dto)
{
try
{
var doc = new KlantDocument
{
/* All of the logic below to fetch next id will be executed before any of the saves happen, thus we get "Duplicate key" exception. */
// 1st try to fetch next id
// Oid = _uow.Query<KlantDocument>().OrderByDescending(s => s.Oid).First().Oid + 1,
// 2nd try to fetch next id
// Oid = _uow.Query<KlantDocument>().OrderBy(s => s.Oid).Last().Oid + 1,
// 3rd try to fetch next id
// Oid = await _uow.Query<KlantDocument>().OrderBy(s => s.Oid).AsNoTracking().Select(s => s.Oid).LastAsync() + 1,
// 4th try to fetch next id
// Oid = _uow.Query<KlantDocument>().OrderBy(s => s.Oid).AsNoTracking().Select(s => s.Oid).Last() + 1,
// 5th try to fetch next id
// Oid = (_uow.Query<KlantDocument>().OrderBy(s => s.Oid).AsNoTracking().Max(s => s.Oid) + 1),
Naam = dto.FileName,
DocumentSoort = dto.DocumentTypeId,
Datum = DateTime.Now,
Uniek = Guid.NewGuid() + "." + dto.FileName.GetExtension()
};
_uow.Context.Set<KlantDocument>().Add(doc); // Does not work
_uow.Commit();
}
catch (Exception e)
{
_logger.Error(e);
return false;
}
}
我得到“重复键”异常,因为插入时有 2 条记录重叠。
我尝试将其包装到事务中,如下所示:
_uow.ExecuteInTransaction(() => {
var doc = new KlantDocument
{
/* All of the logic below to fetch next id will be executed before any of the saves happen, thus we get "Duplicate key" exception. */
// 1st try to fetch next id
// Oid = _uow.Query<KlantDocument>().OrderByDescending(s => s.Oid).First().Oid + 1,
// 2nd try to fetch next id
// Oid = _uow.Query<KlantDocument>().OrderBy(s => s.Oid).Last().Oid + 1,
// 3rd try to fetch next id
// Oid = await _uow.Query<KlantDocument>().OrderBy(s => s.Oid).AsNoTracking().Select(s => s.Oid).LastAsync() + 1,
// 4th try to fetch next id
// Oid = _uow.Query<KlantDocument>().OrderBy(s => s.Oid).AsNoTracking().Select(s => s.Oid).Last() + 1,
// 5th try to fetch next id
// Oid = (_uow.Query<KlantDocument>().OrderBy(s => s.Oid).AsNoTracking().Max(s => s.Oid) + 1),
Naam = dto.FileName,
DocumentSoort = dto.DocumentTypeId,
Datum = DateTime.Now,
Uniek = Guid.NewGuid() + "." + dto.FileName.GetExtension()
};
_uow.Context.Set<KlantDocument>().Add(doc); // Does not work
_uow.Commit();
});
它不起作用。我仍然收到“重复键”异常。
据我所知,在事务完成之前,默认情况下 EF 不应该锁定数据库吗?
我尝试像这样手动编写插入 SQL:
using (var context = _uow.Context)
{
using (var dbContextTransaction = context.Database.BeginTransaction())
{
try
{
// VALUES ((SELECT MAX(OID) + 1 FROM KlantDocument), -- tried this also, but was not yielding results
var commandText = @"INSERT INTO KlantDocument
(
OID
,Datum
,Naam
,Uniek
,DocumentSoort
)
VALUES (
(SELECT TOP(1) (OID + 1) FROM KlantDocument ORDER BY OID DESC),
@Datum,
@Naam,
@Uniek,
@DocumentSoort
)";
var datum = new SqlParameter("@Datum", DateTime.Now);
var naam = new SqlParameter("@Naam", dto.FileName);
var uniek = new SqlParameter("@Uniek", Guid.NewGuid() + "." + dto.FileName.GetExtension());
var documentSoort = new SqlParameter("@DocumentSoort", dto.DocumentTypeId ?? "OrderContents");
context.Database.ExecuteSqlRaw(commandText, datum, naam, uniek, documentSoort);
dbContextTransaction.Commit();
}
catch (Exception e)
{
dbContextTransaction.Rollback();
return false;
}
}
}
同样的事情。
我做了很多研究来尝试解决这个问题:
- .NET Core - ExecuteSqlRaw - last inserted id? - 解决方案要么不起作用,要么仅适用于标识列
- 我想到了使用 SQL OUTPUT 变量,但问题是它真的很难甚至不可能实现,感觉很笨拙,总体而言,如果管理,不能保证它会起作用。如此处所示:SQL Server Output Clause into a scalar variable 和此处How can I assign inserted output value to a variable in sql server?
如上所述,当时的 ORM 做了一些神奇的事情,它在数据库中顺序插入记录,我希望保持这种状态。
在处理给定场景时,有什么方法可以实现顺序插入?
【问题讨论】:
-
你在使用多线程吗?如果是这样,也许您可以尝试锁定这部分代码。
-
这是从异步 Web Api 控制器操作中使用的。我将用它的调用方式更新我的问题。
-
什么是
_uow?请记住,工作单元模式是内置的EntityFramework...如果您使用的是EntityFramework,那么您正在使用工作单元模式。小心不要过度设计解决方案。 -
// Called by POST method multiple times at once-> 发布的代码没有显示这个 -
@bluedot 我要感谢你,不管你是否扩展了我的观点。我还没有听说过事务隔离级别。尽管这似乎不容易阅读主题,但我一定会研究一下。
标签: c# sql-server entity-framework-core