0

Online shop I am working on has entity Order that has member DeliveryDetails. The purpose of DeliveryDetails is to contain data which is specific to delivery method selected by user (e.g. Shipping or Pick Up From Store), while some details are common for all methods (e.g. Firstname, Lastname, PhoneNumber). I was thinking about structure similar to the following using inheritance:

public class Order {
        // ....other props...
        public DeliveryMethodType DeliveryMethodType { get; set; }
        public DeliveryDetailsBase DeliveryDetails { get; set; }
}

    public class DeliveryDetailsBase
    {
        public int Id { get; set; }
        public string CustomerId { get; set; }
        public Order Order { get; set; }
        public int OrderId { get; set; }

        public string Firstname { get; set; }
        public string Lastname { get; set; }
        public string PhoneNumber { get; set; }
    }

    public class DeliveryDetailsShipping : DeliveryDetailsBase
    {
        public string Street { get; set; }
        public string Building { get; set; }
        public string Appartment { get; set; }
        public string PostalCode { get; set; }
        public string City { get; set; }
        public string Country { get; set; }
    }

    public class DeliveryDetailsPickupFromStore : DeliveryDetailsBase
    {
        public string StoreCode { get; set; }
    }

However, I can't figure out how to make DeliveryDetails prop be assigned to different type of delivery method details depending on what method customer selected and how to fit it in EntityFramework on ASP.Core.

Workarounds I have already tried:

-> (1). Creating "super class" contatining props for ALL delivery methods and populate in db only those that are needed for selected delivery method (selection via setting enum DeliveryMethodType). OUTCOME: works, but with 1 big and ugly table featuring multiple nulls.

-> (2). In Order, creating prop DeliveryDetails which in turn embraces DeliveryDetailsPickupFromStoreDATA & DeliveryDetailsShippingDATA. OUTCOME: works, but with several related tables and quite a lot of ugly code checking selected type from enum, instantiating specific subclass for chosen delivery method and setting to null other unused subclasses.

TO SUM UP: Is there any more elegant and feasible way to organize this?

Lighthouse
  • 83
  • 1
  • 5
  • What do you want your table structure to look like? I'm guessing you want one table `Order` and one table `DeliveryDetails`? And can and order have multiple delivery details or just one? – John-Luke Laue May 07 '19 at 21:58
  • I want 1 DeliveryDetails to be assigned to 1 Order while the **type** of DeliveryDetails varies depending on user choice (DeliveryDetailsShipping/ DeliveryDetailsPickupFromStore etc). – Lighthouse May 07 '19 at 22:23
  • Possible duplicate of [How can you represent inheritance in a database?](https://stackoverflow.com/questions/3579079/how-can-you-represent-inheritance-in-a-database) – philipxy May 07 '19 at 22:47
  • (Obviously) This is a faq. Before considering posting please always google your error message or many clear, concise & precise phrasings of your question/problem/goal, with & without your particular strings/names, & read many answers. If you post a question, use one phrasing as title. See [ask] & the voting arrow mouseover texts. – philipxy May 07 '19 at 22:48

2 Answers2

0

EF Core has only implemented Table Per Hierarchy (TPH) inheritance.

Table Per Type (TPT) is still an open ticket (not implemented).

Table Per Concrete Type (TPC) is also still an open ticket (not implemented).

So, if TPH meets your requirements, you can follow this guide. Basically, one table will be used and an extra column called Discriminator will be used to determine which implementation the record corresponds to.

If you are just getting started with Entity, my recommendation would be to not use inheritance and just use nullable columns for data that may or may not be needed depending on the type.

John-Luke Laue
  • 3,736
  • 3
  • 32
  • 60
  • Yes, I have read about TPH. However, even after creating the table for all delivery methods I desire, I run into another issue (more of a design nature): in all articles I have seen on TPH so far, entities described are **not** a part of any other entity (e.g. they write just about some Person class from which Tutor and Student derive so we can have common table for them). Whereas, in my case, the thing is that I want **OrderDetails** to be a member of another class **Order** and I can't figure out how to link Order and OrderDetails in this case to make it work properly. – Lighthouse May 07 '19 at 22:34
0

Is there any more elegant and feasible way to organize this?

Keep it simple, and inheritance isn't usually simple. :)

As a general rule I opt for composition over inheritance. It's easier to work with. Given an order that needs to be delivered to an address or to a store:

public class Order
{
  public DeliveryMethod DeliveryMethod { get; set; } = DeliveryMethod.None;
  
  public virtual OrderDeliveryAddress { get; set; } // should never be null.
  public virtual OrderDeliveryStore { get; set; } // not null if delivery mode = store.
}

public class Address
{
  public string Street { get; set; }
  public string Building { get; set; }
  public string Appartment { get; set; }
  public string PostalCode { get; set; }
  public string City { get; set; }
  public string Country { get; set; }   
}

public class OrderDeliveryAddress
{
  public virtual Order Order { get; set; }
  public virtual Address Address { get; set; }
}

public class Store
{
  public int StoreId { get; set; }
  public virtual Address { get; set; }
}

public class OrderDeliveryStore
{
  public virtual Order Order { get; set; }
  public virtual Store Store { get; set; }
}

Where DeliveryMethod is an Enum. { None = 0, ToAddress, ToStore }

When an order is placed the operator can choose to deliver it to an address, selecting the address of the customer, or entering a new address record; or they can deliver it to a store which can also set the OrderDeliveryAddress to the address of the store. You can establish checks in the database/system to ensure that the data integrity for the delivery method and referenced OrderDeliveryAddress/OrderDeliveryStore are in sync and raise any mismatches that might appear.

One consideration would be that when it comes to deliveries, you will probably want to clone a new Address record based on the customer address, or store address as applicable at the time of ordering rather than referencing their current address record by ID. The reason would be for historical integrity. An order will have been delivered to the address at that point in time, and if a customer address or store address changes in the future, past orders should still show the address that order was delivered.

Mark Cooper
  • 6,738
  • 5
  • 54
  • 92
Steve Py
  • 26,149
  • 3
  • 25
  • 43