Open Closed

Navigation property with backing field doesn't update from console application #1579


User avatar
0
GregB created
  • ABP Framework version: v4.3.0
  • UI type: Angular
  • DB provider: EF Core / MongoDB
  • Tiered (MVC) or Identity Server Separated (Angular): no
  • Exception message and stack trace: N/A
  • Steps to reproduce the issue:"

I've got an entity with a backing field navigation property like so.

   public class Customer : FullAuditedAggregateRoot<Guid>, IHasTimelineTopic, IHasAttachments
    {
        private readonly HashSet<Contact> _contacts = new();
        public IEnumerable<Contact> Contacts => _contacts?.ToList();
        
        public void AddContacts(IEnumerable<Contact> contacts)
        {
            Check.NotNull(contacts, nameof(contacts));

            foreach (var contact in contacts)
            {
                _contacts.Add(contact);
            }
        }

        public void RemoveContacts(IEnumerable<Contact> contacts)
        {
            Check.NotNull(contacts, nameof(contacts));

            foreach (var contact in contacts)
            {
                _contacts.Remove(contact);
            }
        }
    }

it's EF config looks like

            builder.Entity<Customer>(b =>
            {
                b.ToTable(FooConsts.DbTablePrefix + "Customers", FooConsts.DbSchema);
                b.ConfigureByConvention();
                b.HasMany(x => x.Contacts).WithMany(x => x.Customers)
                    .UsingEntity(join => join.ToTable("AppCustomerContacts"));
                b.Navigation(nameof(Customer.Contacts))
                    .UsePropertyAccessMode(PropertyAccessMode.Field);
            });

In my AppService I can update it like so

    [Authorize(FooPermissions.Customers.Edit)]
    public virtual async Task&lt;CustomerDto&gt; UpdateAsync(Guid id, CustomerUpdateDto input)
    {
        var customer = await _customerRepository.GetAsync(id);
        ObjectMapper.Map(input, customer);

        var contacts = await _contactRepository.FindListAsync(input.ContactIds);
        customer.AddContacts(contacts);

        var toRemove = customer.Contacts.Where(x => input.ContactIds.Contains(x.Id) == false).ToList();
        customer.RemoveContacts(toRemove);

        customer = await _customerRepository.UpdateAsync(customer, autoSave: true);
        return ObjectMapper.Map&lt;Customer, CustomerDto&gt;(customer);
    }

this works fine and generates two SQL statements, one to update the AppCustomerContacts xref table and one to update the entity itself.

I'm trying to write a console aplication to load some data from an Excel file. I followed the strucure of the included DbMigratior app and I've got :

  • Program.cs which configures the Logger and creates the HstBuilder, registering a hosted service...
  • DataSeederHostedService whichis an IHostedService and takes an IHostApplicationLifetime as a constructor parameter. It has a StartAsync() method which does just like the DbMigratior. It creates a DataSeederModule from the AbpApplicationfactory, calls UseAutoFac() and AddLogging(). Initializes the application, gets a DataSeedingService from the ServiceProvider and calls my SeedAsync() method.
  • My DataSeedingService is an IDomainService which takes a few repositories, the IGuidGenerator and IUnitOfWork as constructor paramerters.

The DataSeedingService adds Contacts to my Customers in the same way as above, using .AddContacts() but when I call await _customerRepository.UpdateAsync(customer, true); it only generates a single SQL statement to update the entity and doesn't insert anything into AppCustomerContacts.

What am I missing?

Here's how SeedAsync() calls the

contact = await _contactRepository.InsertAsync(contact, true);
customer = await _customerRepository.GetAsync(customerId);
customer.AddContact(contact);

_logger.LogInformation($"\t Adding contact to customer {customer.Name}");

customer = await _customerRepository.UpdateAsync(customer, true);

My DataSeederModule looks like this

    [DependsOn(
        typeof(AbpAutofacModule),
        typeofFooDomainModule),
        typeof(EFooEntityFrameworkCoreModule),
        typeof(FooApplicationContractsModule)
        )]
    public class FooDataSeederModule : AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            Configure<AbpBackgroundJobOptions>(options =>
            {
                options.IsJobExecutionEnabled = false;
            });
        }
    }

Why isn't my console app generating two SQL statements? What am I missing? Do I need to reference another module or configure something at app startup?


4 Answer(s)
  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    May be you need add the [UnitOfWork] to the SeedAsync method

    [UnitOfWork]
    public virtual async Task SeedAsync()
    {
        ......
        contact = await _contactRepository.InsertAsync(contact, true);
        customer = await _customerRepository.GetAsync(customerId);
        customer.AddContact(contact);
    
        _logger.LogInformation($"\t Adding contact to customer {customer.Name}");
    
        customer = await _customerRepository.UpdateAsync(customer, true);
    }
    
  • User Avatar
    0
    GregB created

    Hi @liangshiwei. I've added that and it still only generates the update statement for the Customer.

    Anything else I can try?

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    Can you provide a project to reproduce? shiwei.liang@volosoft.com thanks.

  • User Avatar
    0
    ServiceBot created
    Support Team Automatic process manager

    This question has been automatically marked as stale because it has not had recent activity.

Made with ❤️ on ABP v9.1.0-preview. Updated on December 13, 2024, 06:09