0

Suppose that I have following interfaces:

//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; }
}

The question is about the association to Order in Customer. In fact, the Order is the base class for OnlineOrder and PhoneOrder. Developers should be able to access and write Linq on the references of type Customer and put condition on it's association to Order. So I put an association of type Order in Customer. Order and Customer are both abstract entities.

I want to know is it a good practice to define two association, one in the Customer and the other one in OnlineCustomer of type OnlineOrder? If so, how can I map this model into relational database using EF?

Given the abstract Customer and Order classes are in a shared assembly, and the shared project assembly will not informed of the inherited concrete classes. TPH, TPC, or TPT and where should we put the relation?

Polymorphic
  • 420
  • 3
  • 14

1 Answers1

0

DbSet classes represent database tables

Keep in mind that the DbSet classes you are defining in your DbContext represent the tables in your database. The tables are real items with real columns. Every column is represented by a property in the class that you will put in your DbSet. If columns are not real, and represent a relation, than the property is defined as virtual. You can see this in the virtual declarations in the one-to-many related classes.

The effect is, that the 'DbSetclasses in yourDbContext` are not interfaces, but real classes.

On the other hand, these classes might implement your interfaces.

Derived interfaces

Your OnlineCustomers only have OnlineOrders. You expect that if your ask an OnlineCustomer for their Orders that you get the same objects than if you ask them for their OnlineOrders. So why use a different function name?

But I want a different return value!

If you have derivation in interfaces and a function in your derived interface should do the same as the base function, except for the return value it is more common to use the same function name and use explicit interface implementations.

You see this also in IEnumerable.GetEnumerator() and IEnumerable<T>.GetEnumerator() Both functions use the same method name, their return value is different. If you have an IEnumerable<T> and you ask GetEnumerator() you know you get an IEnumerator<T>, which is derived from IEnumerator.

Another example: a List<T> implements IList<T> which derives from IList. Interface IList<T> is implemented normally, while IList is implemented explicitly.

This is very similar to your OnlineUsers. What you want is, that if you ask an OnlineUser for their Orders you expect only OnlineOrders. After all, OnlineUsers only have OnlineOrders. You also want that the returned Orders all have the IOnlineOrders interface. This interface also implements IOrders, so if you ask an IOnlineUser for his Orders you get his IOnlineOrders functionality as well as his IOrder functionality.

This is way more practical then let users decide whether they need to call OnlineOrders or Orders

So my suggestion would be:

// 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; }
} 

Note that I use the new keyword. Whenever I ask an IOnlineCustomer for his Orders I don't want the 'ICustomer.Orders`.

Class OnlineCustomer implements both IOnlineCustomer and ICustomer. The IOnlineCustomer functions are implements implicitly, the ICustomer functions are implemented explicitly. This function probably will call the implicit Order functions with a Cast.

Try the following:

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

Your debugger will show you that OnlineCustomers.Orders is called, not the explicitly implemented ICustomer.Orders. Although you only can access the IOrder functions, all returned elements are in fact IOnlineOrders, which of course implement theIOrder`.

See also Why implement interface explicitly?

Final remark

You decided to make the return value of your Orders function a List<IOrder>. Are your sure that Orders[4] has a defined meaning in your context?

Wouldn't it be better to return an ICollection<IOrder>', or maybe even anIReadOnlyCollection? 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 callOrders[4]`.

It is fairly difficult to define a proper meaning of Order[4], or you would have to do some sorting or something, which is probably a waste of processing power as most callers of your function don't really need a sorted collection.

Consider changing the return value.

Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116
  • I edited the question so that reflect the root question. I turned the interfaces into classes so that clarify that I do not insist on them to be interface. – Polymorphic Jun 12 '18 at 10:18
  • I've found the role of new keyword interesting. How the new desgin should get mapped, given the abstract Customer and Order classes are in a shared assembly, and the shared project assembly will not informed of the inherited concrete classes. TPH, TPC, or TPT and where should we put the relation? – Polymorphic Jun 12 '18 at 10:28
  • Once again, your classes represent the Tables. An OnlineCustomer has different Orders than a PhoneCustomer. So the Orders properties of the OnlineCustomer ought to be in the OnlineCustomer table, the Orders properties of the PhoneCustomer in the PhoneCustomer table, even if you use TPH TPC or TPT (I'll have to look up which one is the one with a separate Customers table) – Harald Coppoolse Jun 12 '18 at 11:01
  • What if I model Order class and subclasses TPT? – Polymorphic Jun 12 '18 at 11:07
  • Hm! Removing the interfaces makes my answer quite useless, as it focusses mainly on the interfaces. When deciding about how to model inheritance in your database you should ask yourself: what do I query more: give me all Orders that... or Give me all PhoneOrders that.. Depending on your inheritance model only one table needs to be accessed or two tables need to be joined. Choose the inheritance model that accesses do not need a join when querying your most used query – Harald Coppoolse Jun 12 '18 at 11:14