我可以按规定顺序 运行 IDataSeedContributor 实现吗?

Can I run IDataSeedContributor implementations in a prescribed order?

我正在尝试在我的数据库中创建一些简单的示例数据。我编写了几个 IDataSeedContributor 实现。在实现中,我注入并调用多个存储库来检索从我的其他实现中播种的数据。我想使用该种子数据来填充当前实现中实体的依赖属性。

这是一个例子:

public ContactsDataSeedContributor(
    IDepartmentRepository departmentRepository,
    IContactRepository contactRepository,
    IAddressRepository addressRepository,
    IEmailAddressRepository emailAddressRepository,
    IPhoneNumberRepository phoneNumberRepository,
    IGuidGenerator guidGenerator,
    ICurrentTenant currentTenant)
{
    _departmentRepository = departmentRepository;
    _contactRepository = contactRepository;
    _addressRepository = addressRepository;
    _emailAddressRepository = emailAddressRepository;
    _phoneNumberRepository = phoneNumberRepository;
    _guidGenerator = guidGenerator;
    _currentTenant = currentTenant;
}

public async Task SeedAsync(DataSeedContext context)
{
    using (_currentTenant.Change(context?.TenantId))
    {

        if (!(await _contactRepository.AnyAsync(x => x.TenantId.ToString() == _currentTenant.Id.ToString())))
        {
            var department = (await _departmentRepository.GetListAsync()).FirstOrDefault();
            var contact = new Contact
            (
                id: _guidGenerator.Create(),
                name: "",
                surname: "Sample Contact",
                title: "Sample Title",
                lastContactDate: DateTime.UtcNow,
                note: "This is a sample note.",
                departmentId: department.Id,
                clientId: null,
                primary: true,
                active: true
            );
            (await _addressRepository.GetListAsync()).ForEach(x => contact.Addresses.Add(x));
            (await _emailAddressRepository.GetListAsync()).ForEach(x => contact.EmailAddresses.Add(x));
            (await _phoneNumberRepository.GetListAsync()).ForEach(x => contact.PhoneNumbers.Add(x));
            await _contactRepository.InsertAsync(contact);
        }

    }
}

我在预料之中,但我得到了几个表明依赖数据尚未初始化的异常。这似乎表明没有一种智能机制来确定实现应该 运行.

的顺序

有没有办法给框架提示一下?

我可以运行 IDataSeedContributor 以规定的顺序实现吗?

我试图避免一个整体的自上而下的实现,所有的东西都运行按照我规定的顺序排列。

这是我要初始化的 class 图:

我相信你可以通过注入 IDataSeeder 而不是直接从 IDataSeedContributor 继承它来做到这一点。因为现在您拥有完全的控制权,所以您可以随时随地调用 SeedAsync 方法。

希望这些信息对您有所帮助,您将在短时间内实现您想要的目标。

参考资料

虽然我很感激@berkansasmaz 让我走上了正确的道路,但这并不是一个完整的解决方案。我想我会在这里分享我的一些解决方案,希望能帮助到其他人。

ABP 文档中给出的所有示例都用于初始化不依赖于其他实体的实体。我想创建示例数据,这样当我创建一个新租户时,该租户已经有一些东西可以使用了。

我的解决方案是修改提供的 SaasDataSeedContributor 以包含我自己的 ITenantDataSeeder 实现。

public interface ITenantDataSeeder
{
  Task CreateTenantAsync();
}

public class TenantDataSeeder : ITenantDataSeeder, ITransientDependency
{
  private readonly ICurrentTenant _currentTenant;
  private readonly ILogger<TenantDataSeeder> _logger;
  private readonly ICountryDataSeeder _countryDataSeeder;
  // other private fields here

  public TenantDataSeeder(
    ICountryDataSeeder countryDataSeeder,
    IStateDataSeeder stateDataSeeder,
    IAddressDataSeeder addressDataSeeder,
    IEmailAddressDataSeeder emailAddressDataSeeder,
    IPhoneNumberDataSeeder phoneNumberDataSeeder,
    IDepartmentDataSeeder departmentDataSeeder,
    IContactDataSeeder contactDataSeeder,
    ICampaignDataSeeder campaignDataSeeder,
    ILeadSourceDataSeeder leadSourceDataSeeder,
    IProductGroupDataSeeder productGroupDataSeeder,
    IProductDataSeeder productDataSeeder,
    IClientNeedDataSeeder clientNeedDataSeeder,
    IRevenueRepeatFreqDataSeeder repeatFreqDataSeeder,
    IOpportunityStatusDataSeeder opportunityStatusDataSeeder,
    IUserDataSeeder userDataSeeder,
    IClientDataSeeder clientDataSeeder,
    IOpportunityDataSeeder opportunityDataSeeder,
    IOpportunityLineItemDataSeeder opportunityLineItemDataSeeder,
    ISalesNoteDataSeeder salesNoteDataSeeder,
    ICurrentTenant currentTenant,
    ILogger<TenantDataSeeder> logger)
  {
    // private fields initialized here
  }

  public async Task CreateTenantAsync()
  {
    // Host Seeding
    await _countryDataSeeder.CreateCountriesAsync();

    if (!_currentTenant.IsAvailable)
      return;

    // Tenant Seeding
    try
    {
      await _stateDataSeeder.CreateStatesAsync();
      await _addressDataSeeder.CreateAddressesAsync();
      await _emailAddressDataSeeder.CreateEmailAddressesAsync();
      await _phoneNumberDataSeeder.CreatePhoneNumbersAsync();
      await _departmentDataSeeder.CreateDepartmentsAsync();
      await _contactDataSeeder.CreateContactsAsync();
      await _campaignDataSeeder.CreateCampaignsAsync();
      await _leadSourceDataSeeder.CreateLeadSourcesAsync();
      await _productGroupDataSeeder.CreateProductGroupsAsync();
      await _productDataSeeder.CreateProductsAsync();
      await _clientNeedDataSeeder.CreateClientNeedsAsync();
      await _repeatFreqDataSeeder.CreateRevenueRepeatFreqsAsync();
      await _opportunityStatusDataSeeder.CreateOpportunityStatusesAsync();
      //await _userDataSeeder.CreateUsersAsync();
      await _clientDataSeeder.CreateClientsAsync();
      await _opportunityDataSeeder.CreateOpportunitiesAsync();
      await _opportunityLineItemDataSeeder.CreateOpportunityLineItemsAsync();
      await _salesNoteDataSeeder.CreateSalesNotesAsync();
    }
    catch (ApplicationException e)
    {
      _logger.LogException(e, LogLevel.Warning);
    }
  }
}

然后我写了几个<Entity>DataSeeder接口和实现。

请注意,这些 classes 实现 IDataSeeder 接口,也不实现 IDataSeedContributor 接口。我可能应该将它们重构为 <Entity>DataSeederFragment 或类似的名称以避免混淆。

下面是接口之一及其实现的示例:

public interface IStateDataSeeder
{
  Task<IEnumerable<State>> CreateStatesAsync();
}

public class StateDataSeeder : IStateDataSeeder, ITransientDependency
{
  protected static IList<State> States = new List<State>();

  private readonly ICurrentTenant _currentTenant;
  private readonly IGuidGenerator _guidGenerator;
  private readonly ICountryDataSeeder _countryDataSeeder;
  private readonly IStateRepository _stateRepository;

  public StateDataSeeder(
    IStateRepository stateRepository,
    ICountryDataSeeder countryDataSeeder,
    IGuidGenerator guidGenerator,
    ICurrentTenant currentTenant)
  {
    _stateRepository = stateRepository;
    _countryDataSeeder = countryDataSeeder;
    _guidGenerator = guidGenerator;
    _currentTenant = currentTenant;
  }

  public async Task<IEnumerable<State>> CreateStatesAsync()
  {
    if (!_currentTenant.IsAvailable)
      return Enumerable.Empty<State>();

    try
    {
      var states = _stateRepository.Where(a => a.TenantId == _currentTenant.Id);
      if (states.Any()) return states;
    }
    catch (NullReferenceException e)
    {
      throw new ApplicationException($"Separated tenant database not yet initialized. Run the DbMigrator."); 
    }

    if (States.Any()) return States;

    var usId = Guid.Parse(CountryConsts.IdUnitedStates);

    var countries = await _countryDataSeeder.CreateCountriesAsync();
    var usCountry = countries.FirstOrDefault(c => c.Id == usId);
    if (usCountry == null)
      throw new ApplicationException("Seeded Country United States was null.");

    States.Add(await _stateRepository.InsertAsync(new State
    (
        id: _guidGenerator.Create(),
        name: "Alabama",
        code: "AL",
        primary: false,
        active: true,
        countryId: usId
    ), autoSave: true));
    
    // other states here

    States.Add(await _stateRepository.InsertAsync(new State
    (
        _guidGenerator.Create(),
        name: "Wyoming",
        code: "WY",
        primary: false,
        active: true,
        countryId: usId
    ), autoSave: true));

    foreach (var state in States) state.TenantId = _currentTenant.Id;
    return States;
  }
}

注意:所有这些接口和实现都放在我的ProjectName.Domain项目下。

我很确定其中一些可以做得更好。我争论是否使用静态 class 字段作为缓存,但我的下游“数据播种器”需要能够访问状态,而存储库不能 return 它们,直到它们被提交[UnitOfWork].

我邀请你的建设性批评。然而,我发布这篇文章的目的是帮助像我这样的人。我花了太长时间试图解决这个问题。