在对我的域类进行建模时,我发现Entity Framework允许您对继承关系建模,但不支持将基类型实例提升为其派生类型,即将数据库中现有的Person行更改为Employee,派生来自人。
显然,我并不是第一个对此感到疑惑的人,因为这个问题已在Stack Overflow上多次被询问和回答(例如见here和here)。
正如这些答案所示,Entity Framework不支持这一点,因为在面向对象编程中不允许这样做:创建实例后,您无法更改其运行时类型。
很公平,从技术角度来看,我可以理解,一旦为一个对象分配了一个单独的内存块,之后添加一些额外的字节来保存派生类型的字段可能需要重新分配整个内存块,并且结果,意味着对象的指针现在已经改变,这反过来又引入了更多的问题。所以我觉得这很难实现,因此在C#中不受支持。
但是,除了技术组件之外,答案(以及here也是如此,请参见第1页的最后一段)似乎也暗示当您想要时它被认为是糟糕的设计 更改对象的运行时类型,表示你不应该需要这种类型更改,而应该使用组合来代替这些情况。
坦率地说,我不明白为什么 - 我说完全有效的想要以与使用Person实例相同的方式工作Employee实例(即通过从Person继承Employee),即使在某些时候及时雇用一名员工,或雇员退出并再次成为一名人员 从概念上讲,我认为这没有任何问题?
任何人都可以向我解释这个吗?
- 编辑,澄清,为什么这被认为是糟糕的设计:
public class Person
{
public string Name { get; set; }
}
public class Employee: Person
{
public int EmployeeNr { get; set; }
public decimal Salary { get; set; }
}
......但这不是?
public class Person
{
public string Name { get; set; }
public EmployeeInfo EmployeeInfo { get; set; }
}
public class EmployeeInfo
{
public int EmployeeNr { get; set; }
public decimal Salary { get; set; }
}
答案 0 :(得分:2)
考虑以下代码:
Person person = new Person { Name = "Foo" };
Person employee = new Employee { Name = "Bar", EmployeeNr = 42 };
但是employee
被声明为Person
,它引用的实际实例是Employee
类型。这意味着您可以将其投射到:
Employee employee2 = (Employee)employee;
使用person
尝试时:
Employee employee3 = (Employee)person;
您将在运行时获得异常:
无法将“Person”类型的对象强制转换为“Employee”。
所以你是对的,因为C#是实现的,这是技术上的不可能性。您可以通过在Employee
中创建构造函数来解决它:
public Employee(Person person, int employeeNr, decimal salary)
{
this.Name = person.Name;
EmployeeNr = employeeNr;
Salary = salary;
}
如您所见,您必须自己实例化Person
Employee
部分。这是一个单调且容易出错的代码,但现在我们来看你的实际问题:为什么这会被认为是劣质设计?
答案在于支持composition over inheritance:
面向对象编程中的继承(或复合重用原则)组合是一种技术,通过包含实现所需功能而不是通过继承的其他类,类可以实现多态行为和代码重用
本质:
支持组合优于继承是一种设计原则,它为设计提供了更高的灵活性,从长远来看,提供了业务领域类和更稳定的业务领域。换句话说, HAS-A 可能比 IS-A 关系更好。
要遵守您的真实示例:将来,我们可能必须使用Android
,它们绝对不会从Person
继承,但可以使用EmployeeInfo
1}}记录。
至于您的实体框架特定问题:
我认为完全有效的想要以与使用Person实例相同的方式工作Employee实例(即通过从Person继承Employee),即使在某个时间点Person将是雇佣员工,或员工退出并再次成为人员。
如果,现在,你知道你只会雇用人类(不要忘记YAGNI),我无法想象这个问题会出现的例子。当您想要显示所有人时,您将迭代该存储库:
using (var context = new MyContext())
{
foreach (var person in context.Persons)
{
PrintPerson(person);
}
}
当您想要列出所有员工时,您完全相同,但使用Employee存储库:
using (var context = new MyContext())
{
foreach (var Employee in context.Employees)
{
PrintEmployee(Employee);
}
}
如果在某些情况下您想知道某人是否是员工,您必须再次访问数据库:
public Empoyee FindEmployeeByName(string name)
{
using (var context = new MyContext())
{
return context.Employees.FirstOrDefault(e => e.Name == name);
}
}
然后您可以使用Person
实例调用:
Empoyee employee = FindEmployeeByName(person.Name);
答案 1 :(得分:2)
你过度分析了这一点。多态性在.NET中得到了很好的支持,并且从来没有任何方法可以不安全地进行上传,友好的InvalidCastException始终会提醒您错误。它甚至可以用C ++(dynamic_cast<>)安全地完成,而不是一种众所周知的语言,因为缺乏射击方法。
对它的运行时支持是关键。 Dbase引擎没有。
这是世纪之交的风险投资的游乐场。由于点com热潮,已经自由流动。面向对象的数据库,承诺为每个人提供喷气式飞机包装。但那也被破坏了,他们都没有接近占主导地位。有没有听说过Gemstone,db4o,Caché,odbpp?与现有引擎竞争是一项艰巨的任务,性能和可靠性是该市场领域的所有,并且非常很难赶上20年的微调。因此,在数据访问层中添加对它的支持毫无意义。
答案 2 :(得分:1)
坦率地说,我不明白为什么 - 我说它完全有效,想要工作 员工实例的方式与使用Person实例的方式相同 (即通过从人员继承员工),即使在某些时候 时间一个人将被雇用为员工,或员工退出和 再次成为一个人。从概念上讲,我没有看到任何错误 此?
我理解为Person -> Employee -> Person
? IE仍然是一个人,但他/她/它被归为“就业”,然后“降级”为一个人?
当object
在运行时没有更改类型时,继承非常有用,例如,当您创建equity
- 对象时,您可以确定不希望将其转换为{{ 1}},即使两者都是证券的子类。但是,只要某些事情在运行时可能发生变化(例如行为),请尝试使用组合。在我之前的示例中,安全子类可能从顶级资产类继承,但用于在资产上设置市场价值的方法应该由行为模式(例如策略模式)放在那里,因为您可能希望在一个运行时期间使用不同的方法。
对于您描述的情况,您不应该使用继承。尽可能始终支持组合而不是继承。如果在运行时期间发生了某些变化,那么绝对不应该通过继承来放置它!
你可以将它与生命进行比较,一个人生来就是一个人而死一个人,一个猫生下一只猫而死一只猫,某种类型的对象应该被创造为一种类型而垃圾被收集为同样的类型。不要通过行为构成来改变某些东西,改变它的行为方式或它所知道的东西。
我建议你看看Design Patterns: Elements of Reusable Object-Oriented Software,它完全涵盖了一些非常有用的设计模式。