模块化单体中的 UnitOfWork 模式 - 同时工作几个 DbContext

问题描述 投票:0回答:1

我正在开发围绕模块化整体构建的项目。我已将 DbContext 分为模块、UnitOfWork 模式(希望如此)。我在保存到数据库时遇到问题。系统仅在最后注册的 DbContext 上工作,看不到其他的。我可以创建一个客户实体,但它不会保存到数据库。当我登录到某个客户时,我可以创建一个购物篮并向其中添加产品(因为最后注册的 DbContext 是 BasketsDbContext)。

我提供代码 - 所有模块看起来都一样,所以我只粘贴一次。

共享.基础设施.SqlServerUnitOfWork

public abstract class SqlServerUnitOfWork<T> : IUnitOfWork where T : DbContext
{
    private readonly T _dbContext;
    private readonly IDomainEventDispatcher _domainEventDispatcher;

    protected SqlServerUnitOfWork(T dbContext, IDomainEventDispatcher domainEventDispatcher)
    {
        _dbContext = dbContext;
        _domainEventDispatcher = domainEventDispatcher;
    }

    public async Task CommitAndDispatchDomainEventsAsync<TEntity>(TEntity entity) where TEntity : Entity
    {
        Log.Information("dbcontext: {@a}", _dbContext.GetType().Name);
        await _domainEventDispatcher.DispatchDomainEvents(entity);
        await _dbContext.SaveChangesAsync();
    }

    public async Task<int> CommitChangesAsync()
    {
        return await _dbContext.SaveChangesAsync();
    }
}

共享.抽象.UnitOfWork

public interface IUnitOfWork
{
    Task<int> CommitChangesAsync();
    Task CommitAndDispatchDomainEventsAsync<TEntity>(TEntity entity) 
        where TEntity : Entity;
}

共享.基础设施.扩展

public static IServiceCollection AddUnitOfWork<T>(this IServiceCollection services) where T : class, IUnitOfWork
    {
        services.AddScoped<IUnitOfWork, T>();
        services.AddScoped<T>();
        using var serviceProvider = services.BuildServiceProvider();
        serviceProvider.GetRequiredService<UnitOfWorkTypeRegistry>().Register<T>();

        return services;
    }

模块UoW注册

public static IServiceCollection AddCustomersInfrastructure(this IServiceCollection services, IConfiguration configuration)
    {
        services.AddDbContext<CustomersDbContext>(options =>
        {
            options.UseSqlServer(configuration.GetConnectionString("DefaultConnection"));
        });

        
        services.AddScoped<ICustomerRepository, CustomerRepository>();
        services.AddUnitOfWork<CustomersUnitOfWork>();

        return services;
    }

客户工作单元

internal class CustomersUnitOfWork : SqlServerUnitOfWork<CustomersDbContext>
{
    public CustomersUnitOfWork(CustomersDbContext dbContext,
                               IDomainEventDispatcher domainEventDispatcher) 
        : base(dbContext, domainEventDispatcher)
    {
    }
}

在处理程序中,我注入 IUnitOfWork 并尝试使用它,我可以轻松发布和处理事件,但它不会保存任何不是 BasketsDbContext (最后注册的)的内容

注册客户命令处理程序

public async Task<AuthenticationResult> Handle(SignUpCustomerCommand command, CancellationToken cancellationToken)
    {
        var emailCheck = await _customerRepository.GetCustomerByEmail(command.Email);
        if (emailCheck != null)
        {
            throw new BadRequestException("Email in use");
        }

        var validator = new SignUpCustomerValidator();
        validator.ValidateAndThrow(command);

        //await CheckIfEmailIsFreeToUse(command.Email);

        var passwordHash = _hashingService.GenerateHashPassword(command.Password);
        var address = Address.CreateAddress(command.Country, command.City, command.Street, command.PostalCode);

        var customer = Customer.Create(command.Email,
                                       passwordHash,
                                       command.Name,
                                       command.LastName,
                                       address,
                                       command.TelephoneNumber);

        await _customerRepository.Add(customer);

        await _unitOfWork.CommitAndDispatchDomainEventsAsync(customer);

        var token = _tokenManager.GenerateToken(customer.Id, customer.Email, customer.Role);

        return new AuthenticationResult(customer.Id, token);
    }

日志

[16:26:37 INF] Starting request: SignUpCustomerCommand, 07/20/2023 16:26:37 +02:00    
[16:26:39 INF] dbcontext: BasketsDbContext    
[16:26:39 INF] Domain event: {"Customer": {"Id": {"Value": "847e33eb-08e6-440b-94eb-cc3786a56944", "$type": "CustomerId"}, "Email": {"Value": "[email protected]", "$type": "Email"}, "PasswordHash": {"Value": "$2a$08$fiOMM8UZXcg/m1IF6afVvuhU37BghzvfseTYH0.r8jXQE.heAqTOS", "$type": "PasswordHash"}, "Name": {"Value": "string", "$type": "Name"}, "LastName": {"Value": "string", "$type": "LastName"}, "Address": {"Country": "string", "City": "string", "Street": "string", "PostalCode": "string", "$type": "Address"}, "TelephoneNumber": {"Value": "1", "$type": "TelephoneNumber"}, "Role": "customer", "DomainEvents": [], "$type": "Customer"}, "$type": "CustomerCreatedDomainEvent"}    
[16:26:39 INF] Customer created at: 07/20/2023 14:26:39    
[16:26:39 INF] Customer created at: 07/20/2023 16:26:39 +02:00    
[16:26:40 WRN] Long running request: SignUpCustomerCommand, (2588 milliseconds) - 07/20/2023 
16:26:40 +02:00, {"Email": "[email protected]", "Password": "string", "Name": "string", 
"LastName": "string", "TelephoneNumber": "1", "Country": "string", "City": "string", "Street": 
"string", "PostalCode": "string", "$type": "SignUpCustomerCommand"}    
[16:26:40 INF] Completed request: SignUpCustomerCommand, 07/20/2023 16:26:40 +02:00    
[16:26:40 INF] HTTP POST /api/Customers/SignUp responded 200 in 2765.2058 ms

我当然可以明确地写在处理程序中

_domainEventDispatcher.DispatchEvents(customer);
_customerRepository.Commit();

它工作得很好,但对于一些事件的复杂处理程序来说,它可能会很痛苦。

c# .net entity-framework clean-architecture modular-monolith
1个回答
0
投票

我通过添加 DbContext 的 IEnumerable 并使用跟踪更改的 IEnumerable 来完成这项工作。

public class UnitOfWork : IUnitOfWork
{
    private readonly IEnumerable<DbContext> _dbContexts;
    private readonly IDomainEventsDispatcher _domainEventsDispatcher;

    public UnitOfWork(IEnumerable<DbContext> dbContexts, 
      IDomainEventsDispatcher domainEventsDispatcher)
    {
        _dbContexts = dbContexts;
        _domainEventsDispatcher = domainEventsDispatcher;
    }
    public async Task<int> CommitAsync(CancellationToken cancellationToken = default)
    {

        await _domainEventsDispatcher.DispatchEventsAsync();

        var dbContext = _dbContexts.Where(x => x.ChangeTracker.HasChanges()).FirstOrDefault();

        if (dbContext is not null)
        {
            return await dbContext.SaveChangesAsync(cancellationToken);
        }

        return 0;
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.