Как моделировать абстрактные ассоциации? - PullRequest
0 голосов
/ 12 июня 2018

Предположим, что у меня есть следующие интерфейсы:

//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.Разработчики должны иметь возможность обращаться к Linq и писать о нем по ссылкам типа Customer и ставить условие его связи с Order.Поэтому я положил ассоциацию типа Заказ в Заказчик.Заказ и Заказчик являются абстрактными объектами.

Я хочу знать, является ли хорошей практикой определение двух ассоциаций, одной в Заказчике и другой в OnlineCustomer типа OnlineOrder?Если да, то как я могу отобразить эту модель в реляционную базу данных, используя EF?

Учитывая, что абстрактные классы Customer и Order находятся в общей сборке, и общая сборка проекта не будет проинформирована о унаследованных конкретных классах.TPH, TPC или TPT и куда мы должны поместить отношение?

1 Ответ

0 голосов
/ 12 июня 2018

Классы DbSet представляют таблицы базы данных

Имейте в виду, что классы DbSet, которые вы определяете в DbContext, представляют таблицы в вашей базе данных.Таблицы представляют собой реальные элементы с реальными столбцами.Каждый столбец представлен свойством в классе, которое вы поместите в свой DbSet.Если столбцы не являются реальными и представляют отношение, то свойство определяется как виртуальное.Это можно увидеть в виртуальных объявлениях в связанных классах «один ко многим».

В результате «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 функциональность.

Это гораздо практичнее, чем позволитьпользователи решают, нужно ли им звонить OnlineOrders или Orders

. Поэтому я предлагаю:

// 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, я не хочу, чтобы «ICustomer.Orders».

Class OnlineCustomer реализовывал как IOnlineCustomer, так и ICustomer.Функции IOnlineCustomer реализованы неявно, функции ICustomer реализованы явно.Эта функция, вероятно, будет вызывать неявные функции Order с помощью Cast.

Попробуйте выполнить следующее:

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

Ваш отладчик покажет вам, что вызывается OnlineCustomers.Orders, а не явно реализованный ICustomer.Заказы.Хотя вы можете получить доступ только к функциям IOrder, все возвращаемые элементы фактически являются IOnlineOrders , which of course implement the IOrder`.

См. Также Зачем реализовывать интерфейс явно?

Finalпримечание

Вы решили сделать возвращаемое значение вашей Orders функции a 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 Orders [4] `.

Itдовольно трудно определить правильное значение Order[4], или вам придется выполнить какую-то сортировку или что-то в этом роде, что, вероятно, является пустой тратой вычислительной мощности, поскольку большинству абонентов вашей функции на самом деле не нужна отсортированная коллекция.

Рассмотрите возможность изменения возвращаемого значения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...