Как искать подклассы, используя шаблон репозитория? - PullRequest
4 голосов
/ 03 августа 2011

У меня есть несколько подклассов базового типа платежной транзакции (кредитная карта, чек, наличные, billMeLater и т. Д.). Каждый подкласс имеет свой собственный репозиторий, поскольку у каждого есть свои свойства и способ выборки. Мне нужно иметь возможность искать платежные транзакции, и я пошел по пути, который привел к увеличению головной боли. Хитрость заключается в том, что иногда клиент должен искать по общим свойствам, таким как сумма или имя клиента, а иногда клиенту нужно искать по свойствам, относящимся к типу платежа, таким как номер кредитной карты или номер банковского маршрута ... но метод поиска на уровне домена должен иметь возможность возвращать все типы базы транзакций.

У меня есть следующие уровни абстракции:

  • Слой WCF с методом SearchTransactions ().

  • Доменный слой с методом SearchTransactions ().

  • Уровень данных с несколькими репозиториями, каждый из которых имеет метод Search () (зависит от типа платежа).

  • База данных (через EF) с типичным неразборчивым беспорядком, необходимым для DBA

Как бы вы это сделали?

EDIT:

Для дополнительного контекста, вот несколько примеров возможных типов оплаты и их базы:

public abstract class TransactionBase
{
    public int TransactionId { get; set; }
    public decimal Amount { get; set; }
}

public class CreditCardTransaction : TransactionBase
{
    public string CardNumber { get; set; }
    public int ExpirationMonth { get; set; }
    public int ExpirationYear { get; set; }
}

public class CheckTransaction : TransactionBase
{
    public string BankAccountNumber { get; set; }
    public string RoutingNumber { get; set; }
}

Таким образом, клиент должен иметь возможность выполнять поиск по CardNumber, RoutingNumber, Amount и т. Д. Одним способом. Если клиент выполняет поиск по сумме (параметр на базе), метод должен вернуть оба значения: CreditCardTransaction и CheckTransaction. Если клиент выполняет поиск по BankAccountNumber, он должен возвращать только CheckTransactions.

ТРЕБОВАНИЯ КЛИЕНТА И РАННЕЕ РЕШЕНИЕ:

Клиент требует, чтобы был один вызов для поиска нескольких типов транзакций. Клиенту не важно, что они передают в качестве аргументов, если ему не требуется более одного вызова для покрытия всех типов платежей. Одна из идей, которые у меня возникли ранее, состояла в том, чтобы использовать классы, которые соответствовали критериям поиска. Тогда у меня могли быть подклассы классов критериев поиска, которые искали более специфические свойства типа оплаты. Как это:

public class TransactionSearchCriteriaBase
{
    public int TransactionId { get; set; }
    public decimal Amount { get; set; }
}

public class CreditCardTransactionSearchCriteria : TransactionSearchCriteriaBase
{
    public string CardNumber { get; set; }
    public int ExpirationMonth { get; set; }
    public int ExpirationYear { get; set; }
}

Итак, если клиент хочет выполнить поиск по общим свойствам, таким как Amount, они передают TransactionSearchCriteriaBase. Если они передают в CreditCardTransactionSearchCriteria, они заканчивают поиск транзакций по кредитным картам. Например:

var listOfTransactions = _transactionService.Search(new CreditCardTransactionSearchCriteria{ Amount = 10m, CardNumber = "1111" });

Я заменил почти неизбежный блок switch / if на фабрику хранилищ, которая возвращала список применимых хранилищ на основе типа объекта критериев, переданного фабрике.

Кроличья нора углубляется. Я хотел бы меньше кроличьей норы.

ДРУГАЯ ЧАСТЬ ИНФОРМАЦИИ:

Поскольку мы делаем это в EF 3.5, у нас нет поддержки POCO. Итак, мы не рассматриваем объекты, которые EF генерирует как объекты домена. Наши репозитории сопоставляют различные отключенные объекты EF с объектами домена и возвращают их доменным службам, которые их вызывают.

Ответы [ 2 ]

2 голосов
/ 03 августа 2011

Я бы переосмыслил вашу модель Entity Framework.

Эта модель домена, которую вы предоставили, выглядит идеально для Наследование таблиц для типов .

Тогда вы можете использоватьМетод LINQ .OfType<T>() для фильтрации различных типов транзакций на основе параметра универсального типа в вашем репозитории:

public class TransactionRepository : IRepository<Transaction>
{
   public TTransaction Find<TTransaction>(int transactionId) 
      where TTransaction  : TransactionBase, new()
   {
      return _ctx.Transactions.OfType<TTransaction>().SingleOrDefault(tran => tran.TransactionId == transactionId);
   }
}

Тогда вы можете сделать это:

var ccTran = _repository.Find<CreditCardTransaction>(x => x.TransactionId == 1);
var cTran = _repository.Find<CheckTransaction>(x => x.TransactionId == 2);

Что касается ваших вопросов:

Таким образом, клиент должен иметь возможность выполнять поиск по CardNumber, RoutingNumber, Amount и т. Д. Одним способом

Я не думаю, что это возможно с однимметод, конечно же, не без некоторого оператора if / switch.

Кикер - это фильтрация - все сводится к сигнатуре метода репозитория - что предоставляется, какие общие ограничения и т. Д.

Если вы говорите, что каждый подтип имеет свой собственныйхранилище, тогда имеет ли смысл иметь один метод, который обслуживает все три хранилища?Где должен жить этот магический метод?

Итак, в целом, я думаю, вы достигли точки, которой многие достигли, когда ваш домен борется с Entity Framework.

В основном, если вы работаете наднабор объектов типа AbstractA, вы не можете "понижать" до набора объектов типа DerivedA для выполнения фильтрации.

Все зависит от того, какая часть вашей доменной модели вы готовы пойти на компромисс.У меня сама была похожая проблема, и я в конечном итоге использовал наследование TPT (затем переключился на TPH, потому что производительность была выше).

Поэтому, не зная слишком много о вашем домене, кроме того, что вы упомянули, я думаю,вам нужно переосмыслить утверждение: «У каждого подкласса есть свой собственный репозиторий, поскольку у каждого есть свои свойства и способ выборки».

Похоже, у вас должен быть один репозиторий, TPT / TPH вваша модель EF и метод "поиска" в вашем репозитории с ограничением общего типа для типа транзакции.

Если у вас нет единого волшебного метода, вам понадобится неприятный оператор switch / ifи делегируйте фильтрацию определенному методу.

1 голос
/ 03 августа 2011

В DDD главная цель Агрегата - поддерживать и управлять согласованностью. Если я правильно последую вашему примеру, у вас есть два типа агрегатов, каждый из которых представлен совокупными корнями CreditCardTransaction и CheckTransaction .

Однако описанный вами сценарий не имеет ничего общего с поддержанием согласованности, поскольку он не меняет никаких данных. То, что вы хотите достичь, это предоставить пользователю своего рода отчет. Поэтому вместо того, чтобы пытаться сгибать агрегаты, я бы представил другой репозиторий - TransactionRepository с единственным методом FindTransaction (TransactionQuery) . Это хранилище будет существовать только по одной причине - запросить в вашей базе данных данные, которые вы хотите показать пользователю (да, это будет хранилище только для чтения).

Другими словами, я бы предложил использовать ваши Агрегаты и сущности домена, когда вы выполняете некоторые операции, которые фактически изменяют данные, но не для запросов, которые показывают данные только пользователям - вместо этого используйте более простые объекты (плюс вы можете агрегировать данные без путаницы с вашей доменной структурой, как в вашем примере).

...