【问题标题】:Inherit from generic class从泛型类继承
【发布时间】:2019-07-11 12:31:27
【问题描述】:

我有 .net core 2.1 项目。我的存储库类如下所示。但由于MyDbContext 构造函数有参数,我收到如下错误。当我删除 JwtHelper 参数时,它运行良好。 但是,我需要在 MyDbContext.cs 中添加 JwtHelper 以进行日志审核。我怎样才能做到这一点?

“MyDbContext”必须是具有公共无参数构造函数的非抽象类型,才能将其用作泛型类型或方法“UnitOfWork”中的参数“TContext”

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>();
}

【问题讨论】:

  • 您是否需要UnitOfWork&lt;TContext&gt; 类中TContext 参数的new() 约束?如果是这样,您将需要一个无参数构造。顺便说一句,您可以拥有多个构造函数。
  • 所以,UnitOfWork 想要运行 DataContext = new TContext();。从哪里获取IJwtHelper(如果允许对其进行参数化)?
  • 感谢@CoolBots。我现在通过添加我的 UnitOfWork 类的内容来编辑我的问题。你能检查我的 UnitOfWork 课程吗? DataContext = new TContext(); 有什么不同的方式吗? 如果可能,那么我删除new() 约束。
  • 如果您可以将参数传递给您的UnitOfWork 构造函数,只需将已构造的TContext 传递给它并消除new() 约束即可。
  • 是的,但是如果您仍然希望 UnitOfWork 创建实例,传递帮助程序,您需要给它一个 Func&lt;IJwtHelper, TContext&gt; 帮助程序,其中可以包含您指定的特定 new 代码不能在泛型内部。

标签: c# entity-framework asp.net-core-2.1


【解决方案1】:

new() 约束需要一个无参数的构造函数;但是,由于您的DbContext 中需要IJwtHelper,并且该属性仅存在于MyDbContext 中,因此您可以创建自己的基类来从DbContext 派生其他上下文:

public class MyDbContextBase : DbContext
{
    public IJwtHelper JwtHelper { get; set; } 
} 
  • MyDbContext 中删除IJwtHelper 属性;删除构造函数;让它继承MyDbContextBase而不是DbContext

  • IUnitOfWork&lt;U&gt;接口上的U约束改为MyDbContextBase

  • UnitOfWork&lt;TContext&gt; 类上将TContext 约束从DbContext 更改为MyDbContextBase;添加IJwtHelper作为构造函数参数

  • UnitOfWork&lt;TContext&gt; 类的构造函数中实例化TContext 后,通过公共属性分配IJwtHelper

【讨论】:

  • 我添加了所有代码作为答案。正如你所说,我将我的 IJwtHelper public 从私有更改。我添加了我的startup.cs. 但是,我无法更改 TContext。因为它是动态的。例如,2 周后,我可以创建 MyRepos2。如果我更改了 TContext,那么我只能使用一个上下文。你能编辑我的答案@CoolBots 吗?
  • 您可以使MyDbContext 成为一个基类- 只是一个带有IJwtHelper 属性的DbContext。然后可以根据需要创建子类,如class MyDbContext2 : MyDbContext
  • 好的。我改变了我的问题,我删除了我的答案。 @CoolBots
  • class MyDbContext2 : MyDbContext 可能不正确。因为,MyDbContext2 和MyDbContext 的上下文非常不同。因此,从 MyDbContext 继承可能是项目@CoolBots 未来的问题
  • 我澄清了我的答案
【解决方案2】:

问题出在UnitOfWork的构造函数中:

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

在这里,您使用默认构造函数构造了类MyDbContext 的新对象,但MyDbContext 没有默认构造函数。

您决定让您的UnitOfWork 非常通用。太好了,因为这使您可以将我们的UnitOfWork 与各种DbContexts 一起使用。您告诉UnitOfWork 的唯一限制是您的DbContext 应该有一个默认构造函数。

一个好的方法是自己创建工厂并将其传递给 UnitOfWork。

如果你不想要,或者不能给MyDbContext一个默认构造函数,考虑告诉你的UnitOfWork它如何创建一个:“嘿工作单元,如果你需要创建我想要的DbContext你用,用这个功能”

事实上,您将使用factory design pattern

老式接口方法

第 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&lt;MyDbContext&gt; 对象时, 使用默认构造函数,该构造函数将命令工厂创建一个新的 MyDbContext。

Lambda 表达式

您实际上不必实现接口。您的 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();

评论后添加

最后一条语句中的 () =&gt; factory.Create(); 部分称为 lambda 表达式。这意味着:创建一个没有输入参数(即() 部分)和等于factory.Create() 的双精度返回值的函数。

题外话:Lambda表达式解释

类似地,如果您需要创建一个 lambda 表达式来表示一个函数,其输入参数为 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);

类似地,一个 lambda 表达式,表示具有两个输入参数和一个输出参数的函数:

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&lt;MyDbContext&gt;.CreateDbContextFunction = factory.Create(); 添加到我的Startup.cs 类时,我收到错误无法将类型'MyDbContext'隐式转换为'System.Func
  • 非常感谢@HaraldCoppoolse。这就是我一直在寻找的。你为我节省了很多时间。它工作得很好。这种方法的唯一缺点是我们创建了很多 ContextFactory。例如,如果我们有 8 个上下文,那么将是 MyDb1ContextFactory, MyDb2ContextFactory,.....,MyDb8ContextFactory。所以,这不是通用的。 最后,请添加到 MyDbContextFactory DbContext IDbContextFactory&lt;HsysDbContext&gt;.Create() { throw new NotImplementedException(); } 以供将来看到此帖子的人使用。因为类需要实现。
  • 好吧,编辑问题。当然,您可以创建一个通用函数来实例化使用正确构造函数的工厂对象,但这值得提出一个新问题
  • 我编辑了你的答案。你说的对。我现在将为此创建新问题。
  • 我创建了一个新问题去通用 DbContextFactory @HaraldCoppoolse 。但是,每个人都反对这种 UnitOfWork 方法。他们说,你不应该使用 UnitOfWork。所以,没有人回答我的问题。 stackoverflow.com/questions/54749019/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-27
  • 2015-09-15
  • 1970-01-01
  • 1970-01-01
  • 2014-12-19
  • 1970-01-01
相关资源
最近更新 更多