【问题标题】:Can I run IDataSeedContributor implementations in a prescribed order?我可以按规定的顺序运行 IDataSeedContributor 实现吗?
【发布时间】:2021-10-20 03:33:49
【问题描述】:

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

这是一个例子:

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 实现吗?

我试图避免一个单一的自上而下的实现,一切都按我规定的顺序运行。

这是我尝试初始化的类图:

【问题讨论】:

    标签: entity-framework-core abp


    【解决方案1】:

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

    我希望这些信息对您有用,并且您将在短时间内实现您想要的。

    参考文献

    【讨论】:

    • 您好,感谢您的指点。我已阅读该文档,但没有示例。我试图避免长时间的单片实现。你知道这些事情的任何常见问题解答风格/食谱风格的例子吗?文档很棒,但确实可以使用更多示例。
    • 您的要求很合理。您可以为此使用 ABP 模块模板。每个模块都对自己负责(即它自己的数据播种,或者如果有的话,它自己的 UI 应用程序),您可以将此模块添加到您想要的应用程序中。简而言之,模块模板可用于创建微服务或优雅的单体。您可以参考这个链接作为一个简短的研究点 => docs.abp.io/en/abp/latest/Startup-Templates/Module
    【解决方案2】:

    虽然我很欣赏 @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);
        }
      }
    }
    

    然后我写了几个&lt;Entity&gt;DataSeeder接口和实现。

    请注意,这些类不要实现IDataSeeder 接口,也不实现IDataSeedContributor 接口。我可能应该将它们重新命名为 &lt;Entity&gt;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 项目下。

    我很确定其中一些可以做得更好。我争论过是否将静态类字段用作缓存,但我的下游“数据播种机”需要能够访问状态,并且存储库在[UnitOfWork] 提交它们之前无法返回它们。

    我邀请您提出建设性的批评。但是,我发布此内容的目的是帮助像我这样的其他人。我花了太长时间试图弄清楚这一点。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-11-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-15
      • 2013-01-05
      • 2012-01-19
      • 1970-01-01
      相关资源
      最近更新 更多