简介
我正在使用Enity Framework创建一个ASP.NET WebAPI应用程序。我需要做的是为一个URI返回相同资源的不同表示,具体取决于用户角色。例如,api/employees/1
将为管理员和标准用户返回两个不同的对象:
标准用户
public class EmployeeBasic {
public string FirstName { get; set; }
public string LastName { get; set; }
}
管理的
public class EmployeeExtended : EmployeeBasic {
public decimal Salary { get; set; }
}
想法和尝试
对于每个资源表示,我需要提供一些相关的类,比如说排序模型。我想知道是否可以使用泛型类型和继承来为相关表示创建通用存储库方法。我想到了以下这样做的方法:
1)为Sort Models创建一些基本界面:
public interface ISortModel<out TBusinessEntity> {
}
2)创建通用SortModel作为所有排序模型的基本类型
public abstract class SortModel<TDBEntity, TBusinessEntity> : ISortModel<TBusinessEntity>
{
// Database sorting
public abstract IQueryable<TDBEntity> ApplyToQuery(IQueryable<TDBEntity> query);
// Local sorting
public abstract IEnumerable<TBusinessEntity> ApplyToLocal(IEnumberable<TBusinessEntity> localList);
// ...
// Some private logic (expression mappers, etc.)
}
3)为基本资源创建排序模型
public class EmployeeBasicSortModel : SortModel<DBModel.Employee, EmployeeBasic>
{
public int FullName { get; set; }
public override IQueryable<DBModel.Employee> ApplyToQuery(IQueryable<DBModel.Employee> query) {
// implementation
}
public override IEnumerable<EmployeeBasic> ApplyToLocal(IEnumberable<EmployeeBasic> localList) {
// implementation
}
}
4)扩展基本排序模型并为扩展资源属性添加排序
public class EmployeeExtendedSortModel : EmployeeBasicSortModel //, ... Is it possible to somehow do that?
{
public override IEnumerable<EmployeeExtended> ApplyToLocal(IEnumberable<EmployeeExtended> localList) {
var partiallyOrderedList = base.ApplyToLocal(localList);
// Add extended sorting
}
// ... ?
}
5)使用上述类创建通用服务:
class EmployeesService() {
public IList<TEmployee> GetAll<TEmployee>(ISortModel<TEmployee> sortModel)
where TEmployee : BasicEmployee
{
// implementation
}
}
问题
当我第一次想到它时,它似乎很简单。但是当我开始实现这个时,我无法弄清楚实现第4步的方法。要么我在C#知识中遗漏了一些东西(这很可能),或者这是我想要的方式不可能做到的这个。所以问题是:我可以创建一个泛型类型的基类,使用基本资源作为类型派生它,并使用扩展类派生一次吗?
答案 0 :(得分:4)
圣洁的善良这是一个复杂的问题。仿制药是一种巨大的红鲱鱼。忽略泛型;问题更为根本。让我们大大简化它。
class Animal {}
class Mammal : Animal {}
class Tiger : Mammal {}
class Shape {}
class Square : Shape {}
class GreenSquare : Square {}
class B
{
public virtual Mammal Frob(Square s) { ... }
}
class D : B
{
public override SomeReturnType Frob(SomeArgumentType m) { ... }
}
问题是:此虚拟覆盖的合法返回和参数类型是什么?
答案是:在C#中,唯一合法的类型是与被覆盖的方法类型完全匹配的类型。 Frob的覆盖必须返回Mammal并占据Square。
现在,我们可以在理论上使D.Frob的类型安全返回Tiger 。你明白为什么吗?如果我们将D转换为B,那么它将返回一个Tiger,但Tiger是一个Animal,所以我们可以。
此功能称为返回类型协方差,现在已经为C#建议了大约15年,并且从未实现过。 CLR不支持它,它不是设计团队的高优先级,它创造了脆弱的基类问题的新口味,所有这些都是这样的反对,它不太可能很快满足酒吧
C ++确实支持此功能,包括在CLR上,因此可能在CLR上执行。你最终必须生成一堆辅助方法。
当然我们不能让D.Frob回归Animal。它可能会返回一只海龟,但B.Frob承诺只返回哺乳动物。
参数类型怎么样? 可以是类型安全的,让D.Frob变形。同样,相同的推理:如果我们将D转换为B,那么我们将只得到正方形。但是让D.Frob选择GreenSquare是不安全的,因为B.Frob承诺能够采取任何方格,而不仅仅是绿色方块。
此功能称为形式参数类型逆变,很少有语言实现它。
现在,您需要返回类型协方差和形式参数类型协方差,它既不支持也不支持类型安全。有趣的是,埃菲尔支持这种协方差。
想要返回类型协方差的C#开发人员通常最终会做类似的事情:class D : B {
private Tiger FrobPrivate(Square s) { ... }
public override Mammal Frob(Square s)
{
return this.FrobPrivate(s);
}
public new Tiger Frob(Square s)
{
return this.FrobPrivate(s);
}
}
基本上,C#编译器必须代表您执行该功能。