我或多或少遵循微软的DDD和CQRS模式示例(eshopOnContainers应用程序)来构建我的应用程序。
我有以下主要组成部分:
1.应用层/PostTransactionService
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>()));
型
对如何处理这个问题有什么建议吗?
1条答案
按热度按时间xjreopfe1#
查询端不应该有任何对Repository接口的引用。为查询端创建一个新的QueryDbContext,并在其构造函数中将其设置为只读:
字符串
CQRS已经开始分离查询和命令的整个过程。在其理想的实现中,您的读取和写入模型必须不同,不能相互引用,并且必须存储在不同的存储中。因此,如果您使用的是CQRS的弱版本,至少,您应该为它们中的每一个创建新的DbContext,并将查询DbContext设置为只读。这样,您可以放心,任何修改都不会通过QueryDbContext影响系统。