Inherit from generic class

末鹿安然 提交于 2019-12-04 06:41:35

问题


I have .net core 2.1 project. And my repository classes like below. But because of MyDbContext constructor has parameter, I'm getting error like below. When I remove JwtHelper parametrer, it is working perfectly. But, I need adding JwtHelper in MyDbContext.cs for logging auditings. How can I achieve this?

'MyDbContext' must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'TContext' in the generic type or method 'UnitOfWork'

UnitOfWork.cs

public class UnitOfWork<TContext> : IUnitOfWork<TContext> where TContext : DbContext, new()
{ 
    protected readonly DbContext DataContext;

    public UnitOfWork()
    {
        DataContext = new TContext();
    }

    public virtual async Task<int> CompleteAsync()
    {
        return await DataContext.SaveChangesAsync();
    }

    public void Dispose()
    {
        DataContext?.Dispose();
    }
}

IUnitOfWork.cs

public interface IUnitOfWork<U> where U : DbContext
{ 
    Task<int> CompleteAsync();
}

MyRepos.cs

public class MyRepos : UnitOfWork<MyDbContext>, IMyRepos
{
    private IUserRepository userRepo;
    public IUserRepository UserRepo { get { return userRepo ?? (userRepo = new UserRepository(DataContext)); } }
}

IMyRepos.cs

public interface IMyRepos : IUnitOfWork<MyDbContext>
{
  IUserRepository UserRepo { get; }
}

MyDbContext.cs

public class MyDbContext : DbContext
{
    private readonly IJwtHelper jwtHelper;

    public MyDbContext(IJwtHelper jwtHelper) : base()
    {
        this.jwtHelper= jwtHelper;
    }

    public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
    {
        var userId=jwtHelper.GetUserId();
        SaveAudits(userId,base.ChangeTracker);
        return (await base.SaveChangesAsync(true, cancellationToken));
    }
}

UserRepository.cs

public class UserRepository : Repository<User>, IUserRepository
{
    private readonly MyDbContext_context;

    public UserRepository(DbContext context) : base(context)
    {
        _context = _context ?? (MyDbContext)context;
    }
}

IUserRepository.cs

public interface IUserRepository : IRepository<User>
{ }

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IJwtHelper, JwtHelper>();
    services.AddScoped<DbContext, MyDbContext>();
    services.AddTransient<IMyRepos, MyRepos>();
}

回答1:


The problem is in the constructor of your UnitOfWork:

public UnitOfWork()
{
    DataContext = new TContext();
}

Here you construct a new object of class MyDbContext using a default constructor, but MyDbContext does not have a default constructor.

You decided to make your UnitOfWork very generic. That is great, because this enables you to use our UnitOfWork with all kinds of DbContexts. The only limitation you told your UnitOfWork is that your DbContext should have a default constructor.

A good method would be to create the factory yourself and pass it to the UnitOfWork.

If you don't want, or can't give MyDbContext a default constructor, consider telling your UnitOfWork how it can create one: "Hey unit of work, if you need to create the DbContext that I want you to use, use this function"

In fact, you will be using the factory design pattern

Old fashioned interface method

Step 1: Create a class, with a function Create(), that will create exactly the DbContext that you want to use.

interface IDbContextFactory<TContext>
   where TContext : DbContext
{
    DbContext Create();
}

// The MyDbContextFactory is a factory that upon request will create one MyDbcontext object
// passing the JwtHelper in the constructor of MyDbContext
class MyDbContextFactory : IDbContextFactory<MyDbContext>
{
      public IJwthHelper JwtHelper {get; set;}

      public MyDbContext Create()
      {
           return new MyDbContext(this.JwtHelper);
      }


      DbContext IDbContextFactory<HsysDbContext>.Create()
      {
           throw new NotImplementedException();
      }
  }

Step 2: tell your UnitOfWork how it should create the DbContext.

public class UnitOfWork<TContext> : IUnitOfWork<TContext> where TContext : DbContext 
{ 
    public static IDbContextFactory<TContext> DbContextFactory {get; set;}
    protected readonly DbContext DataContext;

    public UnitOfWork()
    {
        this.DataContext = dbContextFactory.Create();
    }
    ...
}

public void ConfigureServices(IServiceCollection services)
{
    // create a factory that creates MyDbContexts, with a specific JwtHelper
    IJwtHelper jwtHelper = ...
    var factory = new MyDbContextFactory
    {
         JwtHelper = jwtHelper,
    }

    // Tell the UnitOfWork class that whenever it has to create a MyDbContext
    // it should use this factory
    UnitOfWork<MyDbContext>.DbContextFactory = factory;

    ... // etc
}

From now on, whenever a UnitOfWork<MyDbContext> object is constructed, using the default constructor, this constructor will order the factory to create a new MyDbContext.

Lambda expression

You don't really have to implement an interface. All that your UnitOfWork needs to know is how to create a DbContext.

Instead of an interface, you could pass it a Func:

public class UnitOfWork<TContext> : IUnitOfWork<TContext> where TContext : DbContext 
{ 
    // use this function to create a DbContext:
    public static Func<TContext> CreateDbContextFunction {get; set;}
    protected readonly DbContext DataContext;

    public UnitOfWork()
    {
        // call the CreateDbContextFunction. It will create a fresh DbContext for you:
        this.DataContext = CreateDbContextFunction();
    }
}

public void ConfigureServices(IServiceCollection services)
{
    // create a factory that creates MyDbContexts, with a specific JwtHelper
    IJwtHelper jwtHelper = ...
    var factory = new MyDbContextFactory
    {
         JwtHelper = jwtHelper,
    }

    // Tell the UnitOfWork class that whenever it has to create a MyDbContext
    // it should use this factory


    UnitOfWork<MyDbContext>.CreateDbContextFunction = () => factory.Create();

Added after comment

The part: () => factory.Create(); in the last statement is called a lambda expression. It means: create a function without input parameters (that is the () part) and a double return value equal to factory.Create().

Bit off topic: explanation of Lambda expression

Similarly if you need to create a lambda expression that represents a function with input parameter a Rectangle and as output the surface of the rectangle:

Func<Rectangle, double> myFunc = (rectangle) => rectangle.X * rectangle.Y;

In words: myFunc is a function that has a Rectangle as input, and a double as output. The function is like:

double MyFunc (Rectangle rectangle)
{
    return rectangle.X * rectangle.Y;
}

You call it like:

Func<Rectangle, double> calcSurface = (rectangle) => rectangle.X * rectangle.Y;
Rectangle r = ...;
double surface = calcSurface(r);

Similarly, a lambda expression that represents a function with two input parameters and one output parameter:

Func<double, double, Rectangle> createRectangle = 
    (width, height) => new Rectangle {Width = width, Height = height};

The last parameter of Func<..., ..., ..., x> is always the return value

And for completeness: a method with a void return is called an Action:

Action(Rectangle) displayRectangle = (r) => this.Form.DrawRectangle(r);



回答2:


The new() constraint requires a parameterless constructor; however, since you need IJwtHelper in your DbContext, and that property only exists in MyDbContext, you can make your own base class to derive other contexts from instead of DbContext:

public class MyDbContextBase : DbContext
{
    public IJwtHelper JwtHelper { get; set; } 
} 
  • Remove IJwtHelper property from MyDbContext; remove the constructor; make it inherit MyDbContextBase instead of DbContext

  • Change the U constraint on the IUnitOfWork<U> interface to MyDbContextBase

  • Change TContext constraint from DbContext to MyDbContextBase on UnitOfWork<TContext> class; add IJwtHelper as a constructor parameter

  • Once you instantiate a TContext in the constructor of UnitOfWork<TContext> class, assign IJwtHelper through the public property.



来源:https://stackoverflow.com/questions/54741937/inherit-from-generic-class

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!