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.

Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
Do you need assistance from an ABP expert?
Schedule a Meeting
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v9.2.0-preview. Updated on March 17, 2025, 10:38