Search code examples
c#.netreflectionentity-framework-coreef-model-builder

How can I dynamically register DbSets into DbContext and add a row to the table?


I have too many entities and registering everyone of them is giving me a headache not to mention the load to instantiate the DbContext. I tried to dynamically register the dbsets.

To dynamically register DbSet s into DbContext I used the article in this link:

how to dynamically register entities dbcontext

I added an extension as a file:

public static class ModelBuilderExtensions
{
    public static void RegisterAllEntities<BaseModel>(this ModelBuilder modelBuilder, params Assembly[] assemblies)
    {
        IEnumerable<Type> types = assemblies.SelectMany(a => a.GetExportedTypes())
                                            .Where(c => c.IsClass && 
                                                        !c.IsAbstract && 
                                                        c.IsPublic &&
                                                        typeof(BaseModel).IsAssignableFrom(c));

        foreach (Type type in types)
            modelBuilder.Entity(type);
    }
}

I made an abstract class called BaseEntity that all my entities implemented.

Inside the ApplicationDbContext, I overrode the OnModelCreating method like this:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    var entitiesAssemblyInt = typeof(BaseEntity<int>).Assembly;
    var entitiesAssemblyGuid = typeof(BaseEntity<Guid>).Assembly;

    modelBuilder.RegisterAllEntities<BaseEntity<int>>(entitiesAssemblyInt);
    modelBuilder.RegisterAllEntities<BaseEntity<Guid>>(entitiesAssemblyGuid);
}

It works perfectly in migrations but when I want to use the DbContext in my CommandHandler to add a row to my table named client I don't know how to call client from context.

context.Client.Add(client);

This doesn't work because I didn't register the DbSets directly into DbContext and there is no client in the context.


Solution

  • In my opinion this is generally a bad idea*, for maintainability, debugging, readability reasons... The list goes on.

    However, if you must, you can use _context.Set<Donation>() to fetch the DbSet for a given entity.

    Example

    The following code with a correctly registered DbSet & Context.

    This is the recommended way

        public async Task<Donation?> GetDonationAsync(Guid donationID)
        {
            return await _context.Donations
                .Where(x => x.ID == donationID)
                .SingleOrDefaultAsync();
        }
    
        public async Task AddDonationAsync(Donation donation)
        {
            await _context.Donations.AddAsync(donation);
        }
    

    Is the same as doing:

        public async Task<Donation?> GetDonationAsync(Guid donationID)
        {
            return await _context.Set<Donation>()
                .Where(x => x.ID == donationID)
                .SingleOrDefaultAsync();
        }
    
        public async Task AddDonationAsync(Donation donation)
        {
            await _context.Set<Donation>().AddAsync(donation);
        }
    

    The above code is contained in my repository services. I would recommend doing it this way and calling your repository, rather than directly calling your db context.

    Example

    Repository example

    public class DonationRepository : IDonationRepository
    {
        private readonly MyDatabaseContext _context;
    
        public DonationRepository(MyDatabaseContext context)
        {
            _context = context ?? throw new ArgumentNullException(nameof(context));
        }
    
        public async Task<Donation?> GetDonationAsync(Guid donationID)
        {
            return await _context.Donations
                .Where(x => x.ID == donationID)
                .SingleOrDefaultAsync();
        }
    
        public async Task AddDonationAsync(Donation donation)
        {
            await _context.Donations.AddAsync(donation);
        }
    
    }
    

    Controller example

    [ApiController]
    [Route("[controller]")]
    public class DonationController : Controller
    {
    
        private readonly IDonationRepository _donationRepository;
    
        private readonly ILogger<DonationController> _logger;
    
        public DonationController(ILogger<DonationController> logger,
                                IDonationRepository donationRepository)
        {
            _logger = logger;
            _donationRepository = donationRepository;
            
        }
    
        [HttpGet]
        [Route("GetDonationValue")]
        public async Task<IActionResult> GetDonationValueAsync(Guid donationID)
        {
    
            var donation = await _donationRepository.GetDonationAsync(donationID);
            
            // Doesn't exist!
            if (donation == null) {
                _logger.LogError($"Couldn't find Donation with ID: '{donationID}'.");
                return NotFound();
            }
            
            _logger.LogInformation($"Found Donation with value of: '{donation.Amount}'.");          
            return Ok(donation.Amount);
    
    
        }
    
    }