Abp vnext EFCore implements dynamic context DbSet

Posted by cx3op on Wed, 19 Jan 2022 16:22:56 +0100

background

When we use the EFCore framework to operate the database, we will encounter a large number of context "DbSet < > to be written in" xxDbContext "; It's acceptable for us to have fewer watches. When there are many watches, we have to write a DbSet for each table. A large number of dbsets is just like a painful thing; And it looks very wordy and unsightly; So far, we started the trip of stepping on the pit below;

How EFCore implements dynamic DbSet

Our online Baidu is the same, probably in this way to achieve dynamic

  1. We usually define entities first
public class UserJob: IEntity
{
    public Guid UserId { get; set; }

    public Guid UserId { get; set; }

    public string JobName { get; set; }

    public bool IsManager { get; set; }
}
  1. Add the following methods to our "XXDbContext", and annotate the "dbset < >
public class CoreDBContext : AbpDbContext<CoreDBContext>
{
    // public DbSet<UserJob> UserJob { get; set; }

    public CoreDBContext(DbContextOptions<CoreDBContext> options) : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        DynamicDbSet(modelBuilder);

        base.OnModelCreating(modelBuilder);
    }

    /// <summary>
    ///Dynamic dbSet
    /// </summary>
    /// <param name="modelBuilder"></param>
    private static void DynamicDbSet(ModelBuilder modelBuilder)
    {
        foreach (var entityType in EntityType())
        {
            modelBuilder.Model.AddEntityType(entityType);
        }
    }

    /// <summary>
    ///Entity derived from IEntity
    /// </summary>
    /// <returns></returns>
    private static List<Type> EntityType()
    {
        return Assembly.GetExecutingAssembly().GetTypes()
            .Where(s => typeof(IEntity).IsAssignableFrom(s))
            .Select(s => s).ToList();
    }
}

So far, we have found that the key sentence of implementing dynamic DbSet in EFCore is {ModelBuilder Model. AddEntityType(entityType); Just do it; The way we want is almost done; But is it really the same in Abp vnext? We look down;

Implementing dynamic DbSet in Abp vnext

  • Copy and paste ready to close

According to the above method, we copied the code into Abp vnext. We found that the code can also run normally, as if there was no problem; At this time, we call the next interface to report an error, and the happiness is gone;

Report an error! What's going on? Let's take a look at the details of the error report:

In the figure above, it is not difficult to see that the constructor cannot obtain the parameter IRepository1[DotNet.EFCore.Entity.UserJob] `. Then we find that the constructor obtains this parameter from the container. It is not difficult to guess whether it is not injected into the container? When we think about this kind of play, we didn't register it. Where can I do this? At this time, Abp is carrying the pot again (he has scolded in his heart...);

But think again, there seems to be a default warehouse configuration when Abp vnext integrates EFCore; It seems to have been seen somewhere. Sure enough, we found the following code

services.AddAbpDbContext<CoreDBContext>(options =>
{
    options.AddDefaultRepositories(includeAllEntities: true);
});

A F12 meal, there's no way. What should I do? Can't I finish it! With an unyielding heart, let's continue to see how the source code operates:

  • Source code analysis to see which step went wrong

We can also see that the default warehouse is added in addabpdbcontext. Let's start with addabpdbcontext and look at the code. We found a key code "new efcorerepository register (options) AddRepositories();:

public static IServiceCollection AddAbpDbContext<TDbContext>(this IServiceCollection services,
     Action<IAbpDbContextRegistrationOptionsBuilder> optionsBuilder = null)
     where TDbContext : AbpDbContext<TDbContext>
{
    var options = new AbpDbContextRegistrationOptions(typeof(TDbContext), services);

    optionsBuilder?.Invoke(options); // Initializes the configuration of the AbpCommonDbContextRegistrationOptions option

    // Other codes

    new EfCoreRepositoryRegistrar(options).AddRepositories(); // Add warehouse

    return services;
}

Follow the footsteps, we continue to look inside, look What has been done in AddRepositories(), we found the following three methods to register the repository:

public virtual void AddRepositories()
{
    RegisterCustomRepositories(); // Register custom warehouse
    RegisterDefaultRepositories(); // Register default warehouse
    RegisterSpecifiedDefaultRepositories(); // Register the specified Entity
}

Here, we only look at the method RegisterDefaultRepositories(), and we won't look at the other two first;

protected virtual void RegisterDefaultRepositories()
{
    if (!Options.RegisterDefaultRepositories) // Is the options of the side configuration item AddDefaultRepositories(includeAllEntities: true);
    {
        return;
    }

    foreach (var entityType in GetEntityTypes(Options.OriginalDbContextType)) // Get all entitytypes
    {
        if (!ShouldRegisterDefaultRepositoryFor(entityType))
        {
            continue;
        }

        RegisterDefaultRepository(entityType); // Register default warehousing service
    }
}

Looking back at the loop, I now traverse the EntityType. It seems that I understand what to write here. Let's continue to see that the GetEntityTypes(Options.OriginalDbContextType) method is an abstract method;

 protected abstract IEnumerable<Type> GetEntityTypes(Type dbContextType);

The EFCore code of the above abstract method implements efcorerepository customerregister as follows:;

protected override IEnumerable<Type> GetEntityTypes(Type dbContextType)
{
    return DbContextHelper.GetEntityTypes(dbContextType);
}

internal static class DbContextHelper
{
    public static IEnumerable<Type> GetEntityTypes(Type dbContextType)
    {
        return
            from property in dbContextType.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance)
            where
                ReflectionHelper.IsAssignableToGenericType(property.PropertyType, typeof(DbSet<>)) &&
                typeof(IEntity).IsAssignableFrom(property.PropertyType.GenericTypeArguments[0])
            select property.PropertyType.GenericTypeArguments[0];
    }
}

At this time, we found that reading the EntityType here reflects reading the "dbset < > attribute from the" xxDbContext "to get all the entitytypes; So far, we are going back to the loop, that is, if we delete the dbset < > attribute in xxDbContext, GetEntityTypes must be empty, and Abp will not go into the loop to help us inject the default warehousing service;

So far, we have found the general problem. The dbset < > attribute in xxDbContext is deleted. Abp cannot obtain the EntityType and cannot implement default warehouse injection;

  • Find and solve problems

To solve the above problem, we focus on obtaining the EntityType and registering the default warehouse implementation;
We won't say much. Let's inherit the efcorerepositorycustomerregister and rewrite the AddRepositories implementation;

using System.Reflection;
using Volo.Abp.Domain.Entities;
using Volo.Abp.EntityFrameworkCore.DependencyInjection;

namespace DotNet.EFCore.EfCore;

public class EfCoreRepositoryCustomerRegistrar : EfCoreRepositoryRegistrar
{
    public EfCoreRepositoryCustomerRegistrar(AbpDbContextRegistrationOptions options) : base(options)
    {
    }

    public override void AddRepositories()
    {
        foreach (var entityType in GetEntityType())
        {
            RegisterDefaultRepository(entityType);
        }
    }

    private IEnumerable<Type> GetEntityType()
    {
        return Assembly.GetExecutingAssembly().GetTypes()
            .Where(s => typeof(IEntity).IsAssignableFrom(s)).ToList();
    }
}

Add an extension of IServiceCollection

using Volo.Abp.EntityFrameworkCore.DependencyInjection;

namespace DotNet.EFCore.EfCore;

public static class ServiceDynamicDbSet
{
    public static void AddDefaultRepositories(this IServiceCollection services)
    {
        // Pass an AbpCommonDbContextRegistrationOptions type to facilitate the property injection of the repository registerabase base class
        var options = new AbpDbContextRegistrationOptions(typeof(CoreDBContext), services);

        // We customized the above to get the EntityType implementation and inject the default warehouse
        new EfCoreRepositoryCustomerRegistrar(options).AddRepositories();
    }
}

Add the following code to EntityFrameWorkCoreModule:

context.Services.AddDefaultRepositories();

At this point, we run the code and find that it seems almost OK. Here we are done;

Summary

As mentioned above, it is not difficult to find that the implementation of dynamic DbSet by EFCore itself is a line of code. It is not possible in Abp vnext because when the framework injects the default warehouse, it obtains the entity type by obtaining the DbSet written in DbCotext, and injects the default implementation of the warehouse through the entity type;

The above is also my own experience in stepping on the pit. Baidu also wants to have white whoring (after all, CV programmers), but there is no corresponding answer, so I have this article. I hope to help other partners to give a reference;

There may be other better solutions, or there are bug s in them. You are welcome to correct them;

Above code case test address

Author: Code post
Address: https://www.cnblogs.com/Jinfeng1213/p/15813900.html
Statement: please keep the original link when reprinting the original blog, or add your blog address at the beginning of the article. If you find any errors, you are welcome to criticize and correct. Any article reprinted in me cannot be set with reward function. If you have special needs, please contact me!

 

Turn https://www.cnblogs.com/Jinfeng1213/p/15813900.html

Topics: .NET