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

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

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

  1. Repository Layer/TransactionRepository(EF Core)

应用层/PostTransactionService:

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

var transactionBillClassification = await _billingClassificationQueries.GetBillClassification(command.BillingClassificationId.Value);

var transaction = Transaction.CreateTransactionForChargeOrCredit(
                    account,
                    command.TransactionDate,
                    Enumeration.FromValue<ChargeAction>(command.ChargeActionId),
                    Enumeration.FromValue<ChargeType>(command.ChargeTypeId),
                    new Money(command.AmountPosted),                   
                    transactionBillClassification,
                    parentTransaction);

字符串

应用层/账单分类

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

public async Task<BillClassification> GetBillClassification(int billingClassificationId)
    {   
        using (var connection = new SqlConnection(_connectionString))
        {
            connection.Open();

            var query = $@"SELECT 
                        BillClassificationId
                        , BC.Name
                        , BC.ChargeType
                        , MA.MainAccountNo
                        , MA.Name as MainAccountName
                        , MA.MainAccountTypeId
                        , MA.MainAccountSubTypeId
                        , MA.WriteOffAccountNo
                    FROM [RATES].[BillClassification] BC
                    JOIN [ACCOUNTING].[MainAccount] MA
                        ON MA.MainAccountNo = BC.MainAccount                        
                    WHERE BillClassificationId = @billingClassificationId ";

            var result = await connection.QueryAsync<dynamic, dynamic, dynamic>(
                query
                , (billClassification, mainAccount) =>
                {
                    var mainAccountForBillClass = new MainAccount(mainAccount.MainAccountNo, mainAccount.MainAccountName, Enumeration.FromValue<MainAccountType>(mainAccount.MainAccountTypeId), Enumeration.FromValue<MainAccountSubType>(mainAccount.MainAccountSubTypeId), mainAccount.WriteOffAccountNo);
                    _transactionRepository.Detach(mainAccountForBillClass);

                    var billClass = new BillClassification(
                        billClassification.BillClassificationId,
                        billClassification.Name,
                        Enumeration.FromValue<ChargeType>(billClassification.ChargeType),
                        mainAccountForBillClass
                    );
                    _transactionRepository.Detach(billClass);

                    return billClass;
                }
                , new { billingClassificationId }, splitOn: "MainAccountNo");

            var billClassification = result.FirstOrDefault();

            return billClassification;
        }


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

应用层/PostTransactionService

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

foreach (var item in command.TransactionLineItems)
        {
            var itemBillingCode = await _billingClassificationQueries.GetBillingCode(item.BillingCodeId.Value);
            //_transactionRepository.Detach(itemBillingCode);               

            var lineItem = TransactionLineItem.CreateLineItemForChargeOrCredit(
                transaction,
                account.AccountNumber,
                command.TransactionDate,
                new Money(item.AmountPosted),
                new Money(command.ArBalance),
                "",
                itemBillingCode);

            transaction.AddLineItem(lineItem);
        }


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

应用层/账单分类

var result = await connection.QueryAsync<dynamic, dynamic, dynamic, dynamic, dynamic>(
                 query
                 ,(billingCode, billingCodeMainAccount, billClassification, billClassificationMainAccount) => 
                 {
                     var mainAccountForBillingClass = new MainAccount(
                                 billClassificationMainAccount.BC_MA_MainAccountNo,
                                 billClassificationMainAccount.BC_MA_Name,
                                 Enumeration.FromValue<MainAccountType>(billClassificationMainAccount.BC_MA_MainAccountTypeId),
                                 Enumeration.FromValue<MainAccountSubType>(billClassificationMainAccount.BC_MA_MainAccountSubTypeId),
                                 billClassificationMainAccount.BC_MA_WriteOffAccountNo
                                 );
                     //_transactionRepository.Detach(mainAccountForBillingClass);

                     var mainAccountForBillingCode = new MainAccount(
                                 billingCodeMainAccount.BCD_MA_MainAccountNo,
                                 billingCodeMainAccount.BCD_MA_Name,
                                 Enumeration.FromValue<MainAccountType>(billingCodeMainAccount.BCD_MA_MainAccountTypeId),
                                 Enumeration.FromValue<MainAccountSubType>(billingCodeMainAccount.BCD_MA_MainAccountSubTypeId),
                                 billingCodeMainAccount.BCD_MA_WriteOffAccountNo
                                 );
                    // _transactionRepository.Detach(mainAccountForBillingCode);

                     var result = new BillingCode(
                         billingCode.BCD_BillingCodeId,
                         billingCode.BCD_Name,
                         new BillClassification(
                             billClassification.BC_BillClassificationId,
                             billClassification.BC_Name,
                             Enumeration.FromValue<ChargeType>(billClassification.BC_ChargeTypeId),
                             mainAccountForBillingClass
                             ),
                         mainAccountForBillingCode,
                         new Money(billingCode.BCD_SuggestedAmount)
                         );
                     //_transactionRepository.Detach(result);

                     return result;
                 }
                , new { billingCodeId }, splitOn: "BCD_MA_MainAccountNo, BC_BillClassificationId, BC_MA_MainAccountNo"
                );


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

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


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

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


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

xjreopfe

xjreopfe1#

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

ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;

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

相关问题