如何建模抽象关联?

时间:2018-06-12 05:23:21

标签: c# entity-framework oop design-patterns orm

假设我有以下接口:

//abstract entity
public class Order
{
    int Amount { get; set; }
}

//concrete entity
public class OnlineOrder : Order
{
    string Website { get; set; }
}

//concrete entity
class PhoneOrder : Order
{
    string CallCenter { get; set; }
}

//abstract entity
abstract class Customer
{
    string City { get; set; }
    List<Order> Orders { get; set; }
}
//concrete entity
class OnlineCustomer : Customer
{
    IEnumerable<OnlineOrder> OnlineOrders { get; set; }
}
//concrete entity
class PhoneCustomer : Customer
{
    IEnumerable<PhoneOrder> PhoneOrders { get; set; }
}

问题在于与客户订单的关联。实际上,Order是OnlineOrder和PhoneOrder的基类。开发人员应该能够在类型为Customer的引用上访问和编写Linq,并对其与Order的关联设置条件。所以我在客户中放置了Order类型的关联。订单和客户都是抽象的实体。

我想知道定义两个关联是一个好习惯,一个在Customer中,另一个在OnlineOustder类型的OnlineCustomer中?如果是这样,我如何使用EF将此模型映射到关系数据库?

鉴于抽象的Customer和Order类位于共享程序集中,并且共享项目程序集不会通知继承的具体类。 TPH,TPC或TPT我们应该把关系放在哪里?

1 个答案:

答案 0 :(得分:0)

DbSet类表示数据库表

请记住,您在DbSet中定义的DbContext类代表数据库中的表。这些表是带有真实列的真实项目。每个列都由类中的属性表示,您将放在DbSet中。如果列不是真实的,并且表示关系,则属性被定义为虚拟。您可以在一对多相关类的虚拟声明中看到这一点。

  

效果是,&#39; DbSet classes in your DbContext`不是   接口,但真正的类。

另一方面,这些类可能会实现您的接口。

派生接口

您的OnlineCustomers只有OnlineOrders。如果您向OnlineCustomer询问Orders他们获得相同的对象,那么您希望他们向OnlineOrders提出相同的对象。那么为什么要使用不同的函数名呢?

但我想要一个不同的回报值!

如果在接口中有派生,派生接口中的函数应该与基函数相同,除了返回值之外,更常见的是使用相同的函数名称并使用显式接口实现。

您也会在IEnumerable.GetEnumerator()IEnumerable<T>.GetEnumerator()中看到这两个函数使用相同的方法名称,它们的返回值不同。如果你有一个IEnumerable<T>并且你问GetEnumerator(),那么就知道你得到的IEnumerator<T>来自IEnumerator

另一个例子:List<T>实现IList<T>,它派生自IList。接口IList<T>正常实施,IList明确实施。

这与您的OnlineUsers非常相似。你想要的是,如果你问OnlineUser他们的Orders你只期望OnlineOrders。毕竟,OnlineUsers只有OnlineOrders。您还希望返回的订单都具有IOnlineOrders接口。此界面还会实现IOrders,因此,如果您向IOnlineUser询问Orders,则会获得IOnlineOrders功能以及IOrder功能。

这样更实用,然后让用户决定是否需要致电OnlineOrdersOrders

所以我的建议是:

// unchanged:
interface IOrder {...}
interface IOnlineOrder : IOrder {...}
interface IPhoneOrder : IOrder {...}
interface ICustomer {...}

// similar to IEnumerator<T> and IEnumerator
interface IOnlineCustomer : ICustomer
{
     new List<IOnlineOrder> Orders { get; set; }
}
interface IPhoneCustomer : ICustomer
{
     new List<IPhoneOrder> Orders { get; set; }
} 

请注意,我使用new关键字。每当我向IOnlineCustomer询问他Orders我不想要&#39; ICustomer.Orders`时。

班级OnlineCustomer同时实施IOnlineCustomerICustomer。 IOnlineCustomer函数是隐式实现的,ICustomer函数是显式实现的。此函数可能会使用Cast调用隐式Order函数。

尝试以下方法:

ICustomer customer = new OnlineCustomer(...);
List<IOrders> orders = customer.Orders();

您的调试器将向您显示调用OnlineCustomers.Orders,而不是显式实现的ICustomer.Orders。虽然您只能访问IOrder函数,但所有返回的元素实际上都是IOnlineOrders , which of course implement the IOrder`。

另见Why implement interface explicitly?

最后的评论

您决定将Orders函数的返回值设为List<IOrder>。您确定Orders[4]在您的上下文中有明确的含义吗?

最好不要返回ICollection<IOrder>', or maybe even an IReadOnlyCollection ? After all the possibility to enumerate and to know the number of elements are key features for your callers. They probably don't want to call订单[4]。

定义Order[4]的正确含义相当困难,或者你必须做一些排序或其他事情,这可能会浪费处理能力,因为你职能的大多数来电者都不是真的需要一个有序的集合。

考虑更改返回值。