Search code examples
c#dependency-injectionentity-framework-core

Overload Constructor on partial class not called, leading to null reference on DbContext Factory


I have a Entity Framework generated class for one of my context items. (EF8 /.net8 Blazor Server) This class looks like this:

// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
namespace ChargeModels
{
    public partial class Individual
    {
        public Individual()
        {
            BatchLines = new HashSet<BatchLine>();
        }

        public int id { get; set; }
        public int name { get; set; }
        public int? reference { get; set; }
    }
}

I want to "extend" this class to have some methods I can call directly from an "individual" object. I have created this class to do this:

namespace ChargeModels
{
    public partial class Individual
    {
        private readonly IDbContextFactory<ChargingContext> _factory;

        public Individual(IDbContextFactory<ChargingContext> factory)
        {
            _factory = factory;
        }
        public bool HasBills()
        {
            using (var context = _factory.CreateDbContext())
            {
               return context.ExistingBills.Where(x => x.IndividualId==id).Any(); 
            }
        }
    }
}

In my Program.cs I do this:

 builder.Services.AddDbContextFactory<ChargingContext>(options => options.UseSqlServer(config.GetConnectionString("ChargingConnection")));

With this pattern I was hoping to be able to perform this kind of operation:

    public Individual BuildModel(int id)
    {
        var individual = _service.GetIndividual(id);
        var hasBills = individual.HasBills();
    }

Some of this is working as I might expect. I can get an "Individual" object from the database and it has .HasBills() available as a method I can call. The part I cannot figure out is why my _factory is always null. The overload constructor is never called leading to _factory never getting enumerated from my DI factory being passed in.

I am using the same factory in all my "normal" services just fine so I must be using this partial incorrectly.

Can anyone offer me any advice on what I am doing wrong?


Solution

  • If you want to be able to lazy-load related data like that, enable Lazy Loading and make sure all of your relationships are mapped out before generating. Be prepared for the Select N+1 performance issue, regardless of whether you use lazy loading or write effectively your own lazy loading like your example is trying.

    With lazy loading enabled, and with your individual entity would have the navigation property to access it's bills:

    public virtual ICollection<Bill> Bills { get; protected set; } = [];
    

    Then your helper method would be:

    [NotMapped}
    public bool HasBills => Bills.Any();
    

    There are a few limitations with this approach. Firstly, accessing the bills after loading the individual if you haven't eager loaded them will hit the database, just like your example. This isn't a problem when you're dealing with a single individual and don't always check/use the Bills collection, but lazy loading can be an issue if you're iterating over a collection of results where each individual triggers one or more additional round trips to the database. The other issue with using helper methods like this is that they cannot be used in Linq-to-EF expressions. For instance, whether you use lazy loading or a separate call to the DbContext manually, you cannot write something like:

    var individualsWithBills = _context.Individuals.Where(x => x.HasBills);
    

    It won't work. HasBills isn't/cannot be a mapped property on Individual, and EF cannot translate that down to SQL. You have to go through a Bills navigation property:

    var individualsWithBills = _context.Individuals.Where(x => x.Bills.Any());
    

    My recommendation is to keep entities mappings closely related to their actual data domain and leverage projections, view-models, to serve the application domain. For instance if you have a view where you want individual details as well as some summary information for their bills, design the View Model with the properties (and any relations) that you want, and then have EF project that from the entities.

    For instance if we want to load a summary for individuals which includes a flag for whether they have bills or not the IndividualSummaryViewModel can contain a Boolean flag which is projected on load. For instance:

    var individuals = await _context.Individuals
        .Where(x => /* conditions */)
        .Select(x => new IndividualSumaryViewModel
        {
            Id = x.Id,
            Name = x.Name,
            // ...
            HasBills = x.Bills.Any()
        }).ToListAsync();
    

    This builds an efficient query to get the summarized data without tripping up with any Select N+1 performance snags. If you want to fetch the bills instead there can be a BillSummaryViewModel to populate from the bills when loading the individual

            Bills = x.Bills.Select(b => new BillSummaryViewModel {/* populate fields */}).ToList(),
    

    ... and use a helper property on the Individual view-model: public bool HasBills => Bills.Any();