问题
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 fromMyDbContext
; remove the constructor; make it inheritMyDbContextBase
instead ofDbContext
Change the
U
constraint on theIUnitOfWork<U>
interface toMyDbContextBase
Change
TContext
constraint fromDbContext
toMyDbContextBase
onUnitOfWork<TContext>
class; addIJwtHelper
as a constructor parameterOnce you instantiate a
TContext
in the constructor ofUnitOfWork<TContext>
class, assignIJwtHelper
through the public property.
来源:https://stackoverflow.com/questions/54741937/inherit-from-generic-class