英文:
Limit type of generic injected into class
问题
我想知道在.NET6中是否可能实现类似这样的功能。
假设有一个应用程序,其中有3个以上的数据库,每个数据库都使用自己的DbContext,可以创建一个像这样的通用DbContext:
public class DbRepository<TRepository, TEntity> : IDbRepository<TRepository, TEntity> where TRepository : IContext
使用以下实现:
public class FirstDbContext : IFirstDbContext
public class SecondDbContext : ISecondDbContext
现在有一个与TRepository相同的BaseService:
public class BaseService<TRepository> where TRepository : IContext
我是否可以根据BaseService中的TRepository来限制传递给Service的内容?
所以:
public class UserService : BaseService<IFirstDbContext>
{
public UserService(
IDbRepository<IFirstDbContext, User> userDbRepository, /* 允许 */
IDbRepository<ISecondDbContext, Invoice> invoiceDbRepository, /* 不允许 */
) { }
}
如果UserService使用BaseService<IFirstDbContext>
定义,则只允许注入类型为IDbRepository<IFirstDbContext, TEntity>
的内容。
英文:
I'm wondering if something like this is possible in .NET6.
Given an application with 3 more databases, each using its own DbContext, create a generic DbContext over an entity like so:
public class DbRepository<TRepository, TEntity> : IDbRepository<TRepository, TEntity> where TRepository : IContext
services.AddScoped(typeof(IDbRepository<,>), typeof(DbRepository<,>));
With implementations:
public class FirstDbContext : IFirstDbContext
public class SecondDbContext : ISecondDbContext
Now a BaseService that is also of the same TRepository:
public class BaseService<TRepository> where TRepository : IContext
Can I limit what is passed into a Service based on the TRepository in BaseService?
So That:
public class UserService : BaseService<IFirstDbContext>
{
public UserService(
IDbRepository<IFirstDbContext, User> userDbRepository, /* allowed */
IDbRepository<ISecondDbContext, Invoice> invoiceDbRepository, /* not allowed */
) { }
}
If UserService is defined with BaseService<IFirstDbContext>
then only allow injection of types IDbRepository<IFirstDbContext, TEntity>
.
答案1
得分: 2
不,至少不使用语言类型系统。您唯一能做的是编写自定义的 Roslyn 分析器,该分析器将处理您的文件并生成相应的警告/错误。
英文:
No, at least using language type system. The only thing you can do - write custom Roslyn analyzer which will process your files and emit corresponding warnings/errors.
答案2
得分: 2
我可以根据BaseService
中的TRepository
来限制传递给Service的内容吗?
可以,但有一些条件。
一种常见的技巧(至少对我来说如此)是向我的所有EF实体class
类型添加一个标记接口,以便我可以将其添加为泛型类型参数约束,作为我编写的通用方法中的“合理性检查”,例如DbSet<T>
,以确保T
是实际的EF实体类类型,而不仅仅是.NET类型(毕竟,使用DbSet<String>
会编译通过,尽管这只是愚蠢的) - 通常这看起来像这样 interface IEntity {}
。
(关于这个问题的一个相关示例,请参阅我在这个QA的答案中如何使用interface IPart
1)
我想强调的是,这个IEntity
类型并不是绝对可靠的:下游的消费者完全可以自己实现IEntity
并将其类型传递给受约束的通用方法,从而破坏事情 - 但这并不是我们目前关心的事情。
继续,首先,让我们摆脱每个DbContext
的interface
类型(即IFirstDbContext
和ISecondDbContext
):这只是不必要的:不要指定IFirstDbContext
,直接指定FirstDbContext
(尽管如果你真的想保留这些接口并使用它们,你可以这样做 - 但这只是为自己创造了额外的工作;你永远不会需要模拟DbContext
,所以我只是不明白为什么要为DbContext
类型创建一个interface
(除了某种IReadOnlyDbContext
之类的东西,但那是另一个话题)。
考虑到interface IEntity
,所以让我们将其概括为一个通用接口:
interface IEntity<TContext>
where TContext : DbContext
{
}
...然后将该接口添加到您的实体类型:User
、Invoice
和Some2ndDbContextEntity
:
partial class User : IEntity<FirstDbContext>
{
}
partial class Invoice : IEntity<FirstDbContext>
{
}
partial class Some2ndDbContextEntity: IEntity<SecondDbContext>
{
}
我非常反对泛型仓储的概念,所以我只是在这里重用了OP的IDbRepository<TContext,TEntity>
接口,以阐明我所提出的观点 - 而不是作为C#程序应该编写的示例。
因此,让我们修改您的IDbRepository
以添加一些约束:
interface IDbRepository<TContext,TEntity>
where TContext : DbContext
where TEntity : class, IEntity<TContext>
{
}
然后在UsersService
内部,我们现在可以使用IDbRepository<,>
:
class GoodUserService
{
public GoodUserService(
IDbRepository<FirstDbContext,User> usersRepository,
IDbRepository<FirstDbContext,Invoice> invoicesRepository
)
{
}
}
...但是如果我们尝试在Invoice
中使用IDbRepository
与SecondDbContext
- 或者在Some2ndDbContextEntity
中使用FirstDbContext
,那么泛型约束将被违反:
class BadUserService
{
public BadUserService(
IDbRepository<FirstDbContext,User > usersRepository,
IDbRepository<FirstDbContext,Some2ndDbContextEntity> badRepository
)
{
}
}
确实,如果我们这样做,就会出现编译时错误:
英文:
> Can I limit what is passed into a Service based on the TRepository
in BaseService?
Yes - ish.
A common technique (well, at least for me) is to add a marker interface to all of my EF entity class
types so that I can add it as a generic-type-parameter-constraint as a sanity-check in generic methods that I write over, say, DbSet<T>
, to ensure T
is an actual EF entity class type rather than any-ol'-.NET-type (after-all, using DbSet<String>
will compile, even though that's just silly) - usually this just looks like interface IEntity {}
.
(For a somewhat relevant example, see how I use interface IPart
in my answer to this other QA)
I'd like to stress that this IEntity
type is not foolproof: it's entirely possible for downstream consumers to implement IEntity
themselves and pass their types into the constrained generic methods and break things - but that's not something we're worried about at this point.
Moving on, first, let's ditch the interface
types for each DbContext
(i.e. IFirstDbContext
and ISecondDbContext
): that's just unnecessary: instead of specifying IFirstDbContext
just specify FirstDbContext
directly (though if you really want to keep those interfaces and use them, you can - but you're just creating extra legwork for yourself; you're never going to need to mock a DbContext
so I just don't see the point in having an interface
for a DbContext
type (excepting some kind of IReadOnlyDbContext
, but that's another topic).
Considering the interface IEntity
So let's generalize that out to a single generic interface:
interface IEntity<TContext>
where TContext : DbContext
{
}
...then add that interface to your entity types: User
, Invoice
, and Some2ndDbContextEntity
:
partial class User : IEntity<FirstDbContext>
{
}
partial class Invoice : IEntity<FirstDbContext>
{
}
partial class Some2ndDbContextEntity: IEntity<SecondDbContext>
{
}
<sub>I'm very opposed to the concept of generic-repositories, so I'm only re-using the OP's IDbRepository<TContext,TEntity>
interface here to illustrate the point I'm making - and not as an example of how C# programs should be written.</sub>
So let's modify your IDbRepository
to add some constraints:
interface IDbRepository<TContext,TEntity>
where TContext : DbContext
where TEntity : class, IEntity<TContext>
{
}
So then inside UsersService
we can now use IDbRepository<,>
:
class GoodUserService
{
public GoodUserService(
IDbRepository<FirstDbContext,User> usersRepository,
IDbRepository<FirstDbContext,Invoice> invoicesRepository
)
{
}
}
...but if we try to use IDbRepository
for Invoice
with SecondDbContext
- or for Some2ndDbContextEntity
with FirstDbContext
then the generic constraints will be violated:
class BadUserService
{
public BadUserService(
IDbRepository<FirstDbContext,User > usersRepository,
IDbRepository<FirstDbContext,Some2ndDbContextEntity> badRepository
)
{
}
}
Indeed, we see a compile-time error if we do that:
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论