У меня есть проект .net core 2.1. И мои классы репозитория, как показано ниже. Но из-за того, что конструктор MyDbContext имеет параметр, я получаю сообщение об ошибке, как показано ниже. Когда я удаляю параметр JwtHelper, он работает отлично. Но мне нужно добавить JwtHelper в MyDbContext.cs для ведения журналов аудита. Как этого добиться?
'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; }
}
Мидбконтекст.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>();
}
Итак, UnitOfWork хочет бежать DataContext = new TContext();. Откуда он должен получить IJwtHelper (если это можно параметризовать)?
Спасибо @CoolBots. Сейчас я отредактировал свой вопрос, добавив содержимое моего класса UnitOfWork. Вы можете проверить мой класс UnitOfWork? Есть ли другой способ DataContext = new TContext(); ? Если это возможно, то я снимаю ограничение new().
Исходя из вашего кода, вам нужно ограничение new() и конструктор без параметров в TContext. Возможно, IJwtHelper можно передать каким-то другим способом, например, как свойство? Затем его можно передать в конструктор UnitOfWork и установить как свойство на TContext
Спасибо @Damien_The_Unbeliever. Я преобразовал свой конструктор UnitOfWork в public UnitOfWork(IAuditHelper auditHelper) { this.auditHelper = auditHelper; DataContext = new TContext(auditHelper); }. Но я получаю ошибку 'TContext': cannot provide arguments when creating an instance of a variable type. `
Если вы можете передать параметры своему конструктору UnitOfWork, просто передайте ему уже построенный TContext и устраните ограничение new(), и это сработает.
Могу ли я передать свой JwtHelper из файла startup.cs в UnitOfWork или любым другим способом? @Damien_The_Unbeliever
Да, но если вы все еще хотите UnitOfWork создать экземпляр, передав хелпер, вам нужно дать ему Func<IJwtHelper, TContext> хелпер, который может содержать определенный new код, который вы не можете иметь внутри дженерика.
Моя цель - регистрировать аудиты только по userId. Я отредактировал свой вопрос, добавив все свои коды в свой вопрос. Я также добавил свой файл startup.cs. Где я могу добавить Func<IJwtHelper, TContext> в свои классы? @Damien_The_Unbeliever





Для ограничения new() требуется конструктор без параметров; однако, поскольку вам нужно IJwtHelper в вашем DbContext, а это свойство существует только в MyDbContext, вы можете создать свой собственный базовый класс для получения других контекстов вместо DbContext:
public class MyDbContextBase : DbContext
{
public IJwtHelper JwtHelper { get; set; }
}
Удалить свойство IJwtHelper из MyDbContext; удалить конструктор; заставить его наследовать MyDbContextBase вместо DbContext
Измените ограничение U в интерфейсе IUnitOfWork<U> на MyDbContextBase
Измените ограничение TContext с DbContext на MyDbContextBase в классе UnitOfWork<TContext>; добавить IJwtHelper в качестве параметра конструктора
Создав экземпляр TContext в конструкторе класса UnitOfWork<TContext>, назначьте IJwtHelper через общедоступное свойство.
Я добавил весь свой код в качестве ответа. Как вы сказали, я меняю общедоступный IJwtHelper с частного. И я добавил свой startup.cs. Но я не могу изменить TContext. Потому что он динамичен. Например, через 2 недели я могу создать MyRepos2. Если я изменил TContext, то я могу использовать только один контекст. Можете ли вы отредактировать мой ответ @CoolBots?
Вы можете сделать MyDbContext базовым классом — просто DbContext со свойством IJwtHelper. Затем вы можете создавать подклассы по мере необходимости, например class MyDbContext2 : MyDbContext
В порядке. Я изменил свой вопрос и удалил свой ответ. @CoolBots
class MyDbContext2 : MyDbContext может быть неверным. Потому что MyDbContext2 и MyDbContext очень разные по контексту. Таким образом, наследование от MyDbContext может стать проблемой в будущем проекта @CoolBots.
я уточнил свой ответ
Большое спасибо за вашу помощь @CoolBots. Но я не могу добиться этого из-за инъекции JwtHelper. Решение HaraldCoppoolse DbContextFactory работает.
Проблема в конструкторе вашего UnitOfWork:
public UnitOfWork()
{
DataContext = new TContext();
}
Здесь вы создаете новый объект класса MyDbContext, используя конструктор по умолчанию, но MyDbContext не имеет конструктора по умолчанию.
Вы решили сделать свой UnitOfWork очень общим. Это здорово, потому что это позволяет вам использовать наш UnitOfWork со всеми видами DbContexts. Единственное ограничение, о котором вы сказали своему UnitOfWork, заключается в том, что ваш DbContext должен иметь конструктор по умолчанию.
Хорошим методом было бы создать фабрику самостоятельно и передать ее в UnitOfWork.
Если вы не хотите или не можете предоставить MyDbContext конструктор по умолчанию, подумайте о том, чтобы сообщить своему UnitOfWork, как он может его создать: «Эй, единица работы, если вам нужно создать DbContext, который я хочу, чтобы вы использовали, используйте эту функцию. "
На самом деле вы будете использовать шаблон проектирования завода
Шаг 1: Создайте класс с функцией Create(), которая создаст именно тот DbContext, который вы хотите использовать.
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();
}
}
Шаг 2: сообщите вашему UnitOfWork, как он должен создавать 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
}
Отныне всякий раз, когда создается объект UnitOfWork<MyDbContext>,
используя конструктор по умолчанию, этот конструктор прикажет фабрике создать новый MyDbContext.
Вам действительно не нужно реализовывать интерфейс. Все, что нужно знать вашему UnitOfWork, — это как создать DbContext.
Вместо интерфейса вы можете передать ему 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();
Добавлено после комментария
Часть: () => factory.Create(); в последнем утверждении называется лямбда-выражением. Это означает: создать функцию без входных параметров (это часть ()) и двойное возвращаемое значение, равное factory.Create().
Точно так же, если вам нужно создать лямбда-выражение, представляющее функцию с входным параметром Rectangle и выдающую поверхность прямоугольника:
Func<Rectangle, double> myFunc = (rectangle) => rectangle.X * rectangle.Y;
Другими словами: myFunc — это функция, которая имеет Rectangle на входе и double на выходе. Функция такая:
double MyFunc (Rectangle rectangle)
{
return rectangle.X * rectangle.Y;
}
Вы называете это так:
Func<Rectangle, double> calcSurface = (rectangle) => rectangle.X * rectangle.Y;
Rectangle r = ...;
double surface = calcSurface(r);
Точно так же лямбда-выражение, представляющее функцию с двумя входными параметрами и одним выходным параметром:
Func<double, double, Rectangle> createRectangle =
(width, height) => new Rectangle {Width = width, Height = height};
Последний параметр Func<..., ..., ..., x> всегда является возвращаемым значением.
И для полноты: метод с возвратом void называется Action:
Action(Rectangle) displayRectangle = (r) => this.Form.DrawRectangle(r);
Большое спасибо @HaraldCoppoolse. Я сделал ваше сказал. Но когда я добавляю UnitOfWork<MyDbContext>.CreateDbContextFunction = factory.Create(); к моему классу Startup.cs, я получаю ошибку Невозможно неявно преобразовать тип MyDbContext в System.Func<MyDbContext>.
Большое спасибо @HaraldCoppoolse. Это то, что я искал. Вы сэкономили мне много времени. Это сработало отлично. Единственным недостатком этого подхода является то, что мы создаем много ContextFactory. Например, если у нас 8 контекстов, то будет MyDb1ContextFactory, MyDb2ContextFactory,.....,MyDb8ContextFactory. Так что это не универсал. Наконец, добавьте в MyDbContextFactoryDbContext IDbContextFactory<HsysDbContext>.Create() { throw new NotImplementedException(); } для тех, кто увидит этот пост в будущем. Потому что класс требует реализации.
Ну давай, отредактируй вопрос. И, конечно, вы можете создать общую функцию, которая создает экземпляр фабричного объекта, который использует правильную функцию конструктора, но это стоит нового вопроса.
Я отредактировал ваш ответ. Ты прав. Я создам новый вопрос для этого сейчас.
Я создаю новый вопрос и задаю общий вопрос DbContextFactory @HaraldCoppoolse. Но все выступили против этого подхода UnitOfWork. Они сказали, что вам не следует использовать UnitOfWork. Так что никто не отвечает на мой вопрос. stackoverflow.com/questions/54749019/…
Вам нужно ограничение
new()для параметраTContextв классеUnitOfWork<TContext>? Если это так, вам понадобится конструкция без параметров. У вас может быть более одного конструктора, кстати.