.net 无法跟踪实体示例,因为已在跟踪具有相同键值的另一个实体示例,DDD + CQRS + EF核心

a0zr77ik  于 2023-11-20  发布在  .NET
关注(0)|答案(1)|浏览(326)

我或多或少遵循微软的DDD和CQRS模式示例(eshopOnContainers应用程序)来构建我的应用程序。
我有以下主要组成部分:
1.应用层/PostTransactionService
1.应用层/账单分类
1.域模型层/模型类

  1. Repository Layer/TransactionRepository(EF Core)

应用层/PostTransactionService:

在这里,我构建我的域模型对象,这些对象通常需要来自“BillingClassificationList”的域模型示例,例如,为了构建“Transaction”示例,我需要一个像这样的“transactionBillClassification”示例:

  1. var transactionBillClassification = await _billingClassificationQueries.GetBillClassification(command.BillingClassificationId.Value);
  2. var transaction = Transaction.CreateTransactionForChargeOrCredit(
  3. account,
  4. command.TransactionDate,
  5. Enumeration.FromValue<ChargeAction>(command.ChargeActionId),
  6. Enumeration.FromValue<ChargeType>(command.ChargeTypeId),
  7. new Money(command.AmountPosted),
  8. transactionBillClassification,
  9. parentTransaction);

字符串

应用层/账单分类

现在,在BillingClassificationModel类中,我使用Dapper只从数据库中阅读数据,并将结果直接Map到域模型(我知道,我可以使用View Models,但我觉得我会遇到同样的问题),如下所示:

  1. public async Task<BillClassification> GetBillClassification(int billingClassificationId)
  2. {
  3. using (var connection = new SqlConnection(_connectionString))
  4. {
  5. connection.Open();
  6. var query = $@"SELECT
  7. BillClassificationId
  8. , BC.Name
  9. , BC.ChargeType
  10. , MA.MainAccountNo
  11. , MA.Name as MainAccountName
  12. , MA.MainAccountTypeId
  13. , MA.MainAccountSubTypeId
  14. , MA.WriteOffAccountNo
  15. FROM [RATES].[BillClassification] BC
  16. JOIN [ACCOUNTING].[MainAccount] MA
  17. ON MA.MainAccountNo = BC.MainAccount
  18. WHERE BillClassificationId = @billingClassificationId ";
  19. var result = await connection.QueryAsync<dynamic, dynamic, dynamic>(
  20. query
  21. , (billClassification, mainAccount) =>
  22. {
  23. var mainAccountForBillClass = new MainAccount(mainAccount.MainAccountNo, mainAccount.MainAccountName, Enumeration.FromValue<MainAccountType>(mainAccount.MainAccountTypeId), Enumeration.FromValue<MainAccountSubType>(mainAccount.MainAccountSubTypeId), mainAccount.WriteOffAccountNo);
  24. _transactionRepository.Detach(mainAccountForBillClass);
  25. var billClass = new BillClassification(
  26. billClassification.BillClassificationId,
  27. billClassification.Name,
  28. Enumeration.FromValue<ChargeType>(billClassification.ChargeType),
  29. mainAccountForBillClass
  30. );
  31. _transactionRepository.Detach(billClass);
  32. return billClass;
  33. }
  34. , new { billingClassificationId }, splitOn: "MainAccountNo");
  35. var billClassification = result.FirstOrDefault();
  36. return billClassification;
  37. }


请注意,我必须手动创建一个“MainAccount”示例来组成“BillClassification”示例。

应用层/PostTransactionService

类似地,稍后我需要将“LineItem”示例添加到我的“Transaction”示例,并且对于这些行项目中的每一个,我需要使用由“BillingClassificationList”提供的数据。

  1. foreach (var item in command.TransactionLineItems)
  2. {
  3. var itemBillingCode = await _billingClassificationQueries.GetBillingCode(item.BillingCodeId.Value);
  4. //_transactionRepository.Detach(itemBillingCode);
  5. var lineItem = TransactionLineItem.CreateLineItemForChargeOrCredit(
  6. transaction,
  7. account.AccountNumber,
  8. command.TransactionDate,
  9. new Money(item.AmountPosted),
  10. new Money(command.ArBalance),
  11. "",
  12. itemBillingCode);
  13. transaction.AddLineItem(lineItem);
  14. }


请注意,使用了“itemBillingCode”,它来自BillingClassificationList,我像这样检索它的数据:

应用层/账单分类

  1. var result = await connection.QueryAsync<dynamic, dynamic, dynamic, dynamic, dynamic>(
  2. query
  3. ,(billingCode, billingCodeMainAccount, billClassification, billClassificationMainAccount) =>
  4. {
  5. var mainAccountForBillingClass = new MainAccount(
  6. billClassificationMainAccount.BC_MA_MainAccountNo,
  7. billClassificationMainAccount.BC_MA_Name,
  8. Enumeration.FromValue<MainAccountType>(billClassificationMainAccount.BC_MA_MainAccountTypeId),
  9. Enumeration.FromValue<MainAccountSubType>(billClassificationMainAccount.BC_MA_MainAccountSubTypeId),
  10. billClassificationMainAccount.BC_MA_WriteOffAccountNo
  11. );
  12. //_transactionRepository.Detach(mainAccountForBillingClass);
  13. var mainAccountForBillingCode = new MainAccount(
  14. billingCodeMainAccount.BCD_MA_MainAccountNo,
  15. billingCodeMainAccount.BCD_MA_Name,
  16. Enumeration.FromValue<MainAccountType>(billingCodeMainAccount.BCD_MA_MainAccountTypeId),
  17. Enumeration.FromValue<MainAccountSubType>(billingCodeMainAccount.BCD_MA_MainAccountSubTypeId),
  18. billingCodeMainAccount.BCD_MA_WriteOffAccountNo
  19. );
  20. // _transactionRepository.Detach(mainAccountForBillingCode);
  21. var result = new BillingCode(
  22. billingCode.BCD_BillingCodeId,
  23. billingCode.BCD_Name,
  24. new BillClassification(
  25. billClassification.BC_BillClassificationId,
  26. billClassification.BC_Name,
  27. Enumeration.FromValue<ChargeType>(billClassification.BC_ChargeTypeId),
  28. mainAccountForBillingClass
  29. ),
  30. mainAccountForBillingCode,
  31. new Money(billingCode.BCD_SuggestedAmount)
  32. );
  33. //_transactionRepository.Detach(result);
  34. return result;
  35. }
  36. , new { billingCodeId }, splitOn: "BCD_MA_MainAccountNo, BC_BillClassificationId, BC_MA_MainAccountNo"
  37. );


同样,请注意MainAccount示例“mainAccountForBillingClass”的存在,它将具有与前面讨论的MainAccount示例相同的ID(MainAccountNo)。
当我调用_context.Add(transaction).Entity;时,我得到以下错误:
无法跟踪实体类型“MainAccount”的示例,因为已在跟踪具有与"MainAccountNo“相同键值的另一个示例。附加现有实体时,请确保仅附加一个具有给定键值的实体示例。请考虑使用”DbContextOptionsBuilder.EnableSensitiveDataLogging“查看冲突键值
我猜测问题是两个MainAccount示例“mainAccountForBillClass”从第一个查询和“mainAccountForBillingClass”从第二个查询.正如你可能会看到在一些注解行,我试图从DBContext分离这些示例在下面的方式,但仍然有同样的问题:

  1. public void Detach(Entity entity)
  2. {
  3. _context.Entry(entity).State = EntityState.Detached;
  4. }


最后,我的Repo和DBContext都注册为Scoped(默认的生活风格)

  1. builder.Services.AddScoped<ITransactionRepository, TransactionRepository>();
  2. builder.Services.AddScoped<IBillingClassificationQueries>(sp => new BillingClassificationQueries(builder.Configuration["ConnectionString"].ToString(), sp.GetRequiredService<ITransactionRepository>()));


对如何处理这个问题有什么建议吗?

xjreopfe

xjreopfe1#

查询端不应该有任何对Repository接口的引用。为查询端创建一个新的QueryDbContext,并在其构造函数中将其设置为只读:

  1. ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;

字符串
CQRS已经开始分离查询和命令的整个过程。在其理想的实现中,您的读取和写入模型必须不同,不能相互引用,并且必须存储在不同的存储中。因此,如果您使用的是CQRS的弱版本,至少,您应该为它们中的每一个创建新的DbContext,并将查询DbContext设置为只读。这样,您可以放心,任何修改都不会通过QueryDbContext影响系统。

相关问题