Как да определя идентификатора на сборен корен, добавен към хранилище?

Да кажем, че имам общ интерфейс на хранилище, както следва:

public interface IRepository<T>
{
    Add(T item);
    Delete(int itemId);
    Update(T item);
}

Обикновено новият идентификатор на елемент, добавен чрез IRepository.Add(), ще бъде определен от някаква бек-енд база данни, но само след като цялостната транзакция/единица работа бъде изпратена. Така че съм доста сигурен, че би било погрешно IRepository.Add() да върне новия идентификатор на добавения елемент. Хранилището наистина не трябва да знае нищо за това как се създава ID. Дали това е правилно?

Ако това е случаят, как иначе може да се определи новият идентификатор на елемент, добавен към хранилище, или дори трябва да правя това? Знам, че ORM като NHibernate е в състояние автоматично да замени обекти в паметта с нови обекти с правилния идентификатор, но се опитвам да проектирам моето хранилище, без да имам предвид никаква конкретна реализация на ORM.

Например, кажете, че имам уебсайт, където клиентите могат да правят поръчки. Нов клиент избира да провери и бива изпратен до формуляр, за да попълни своите данни. Тази информация се използва за създаване на обект Customer, който се съхранява в CustomerRepository. Сега информацията за тяхната поръчка трябва да бъде създадена, но поръчката трябва да препраща към клиент чрез неговия идентификатор?

Customer newCustomer = new Customer(first, last, address, phone dateOfBirth);
customerRepository.Add(newCustomer);

//How would I determine customerId??
Order newOrder = new Order(customerId, shippingAddress, billingAddress);
newOrder.AddOrderItem("widget");
newOrder.AddOrderItem("doohicky");
newOrder.AddOrderItem("stuff");

person Eric Anastas    schedule 08.06.2011    source източник


Отговори (6)


В примера, който давате, бих създал Customer и Order в една стъпка и бих предал обекти на домейн към обекти на домейн, вместо да подавам идентификатори:

Customer newCustomer = new Customer(first, last, address, phone dateOfBirth);

// Pass the customer rather than the CustomerId:
Order newOrder = new Order(newCustomer , shippingAddress, billingAddress);
newOrder.AddOrderItem("widget");
newOrder.AddOrderItem("doohicky");
newOrder.AddOrderItem("stuff");

customerRepository.Add(newCustomer);
orderRepository.Add(newOrder);

// SaveChanges()

...когато промените се запазят, рамката автоматично попълва идентификаторите на Customer и Order и попълва Customer.Id, Order.customerId (и т.н.) по силата на това, че обектът Customer е бил присвоен на Order.

person Steve Wilkes    schedule 08.06.2011

Генерирам идентификатор на клиент (a la CombGuid.NewGuid()) и след това го предавам на конструктора. Подходът, когато използвате идентичност на база данни има сериозни недостатъци

person xelibrion    schedule 08.06.2011

Ерик,

В сценария, който споменавате, не виждам да се случва CommitChanges(). Бих увил всичко в транзакционен обхват и след това бих натиснал customerRepository.CommitChanges(), преди да добавите редовете за поръчка. след това трябва да можете да вземете идентификатора от новосъздадения клиентски обект и да го използвате, както следва:

Order newOrder = new Order(newCustomer.Id, shippingAddress, billingAddress);

след това, ако поръчката(ите) се провали, можете да върнете всичко назад и да го запазите атомарно, като не удряте scope.Complete().

Надявам се това да помогне..

person jim tollan    schedule 08.06.2011
comment
В примерите, които видях, хранилището не е мястото, където се извършват промени, както описвате. По-скоро всяко хранилище препраща към обект UnitOfWork, който може да бъде ангажиран или върнат назад. Въпреки това останах с впечатлението, че обектът UnitOfWork е типично настроен така, че да може да връща назад само промени, които все още не са ангажирани. - person Eric Anastas; 09.06.2011

Независимо дали използвате NHibernate или не, смятам, че подходът, който предприема, е правилният. Вашата цел с всеки обект на домейн е да имате само едно копие на този обект в паметта във всеки един момент, т.е. никога не трябва да имате два обекта, които представляват един и същ запис в база данни. От това следва, че ако базата данни е актуализирала записа с нов идентификатор, домейн обектът в паметта също трябва да бъде актуализиран с този идентификатор, тъй като това е „единственото“ вярно представяне на този запис.

След извикване на Add, идентификаторът на обекта е зададен и след това можете да направите допълнителни промени в този обект и да извикате Update, без да се налага да знаете твърде много за вашата реализация.

person Tim Rogers    schedule 08.06.2011
comment
Така че трябва да се приеме, че всяка специфична реализация на IRepository.Add() ще зададе ID на елементите, които се добавят? - person Eric Anastas; 09.06.2011

Вашият интерфейс е повече DAO, отколкото Repository според DDD:

http://codebetter.com/iancooper/2011/04/12/repository-saveupdate-is-a-smell/

Както спомена Стив Уилкс, трябва да поддържате справка за клиента в поръчката, а не за идентификатора на клиента, така че когато работната единица се обработва, тя ще създаде правилна връзка към тези обекти в хранилище за постоянство (SQL DB, уеб услуга и т.н.)

За повече информация относно DAO тук: http://java.sun.com/blueprints/corej2eepatterns/Patterns/DataAccessObject.html

person Tomas Dermisek    schedule 09.06.2011

Обектите на домейна трябва да имат своя собствена стратегия за идентификация, независимо от идентификационните номера на базата данни, така че е за предпочитане да генерирате своя идентификатор в слоя на домейна или ако наистина трябва да генерирате идентификатор в базата данни, след това добавете друг идентификатор на домейн, генериран в слоя на домейна, до автоматично генерирания идентификатор на базата данни.

При дизайн, управляван от домейн, където прилагате модел на хранилище, не трябва да свързвате вашия домейн с база данни, така че разчитането на база данни за създаване на идентификатор не е добра идея.

Друг момент е, че може да искате да направите асоцииран клиент, за да не поставите само идентификатора на клиента, това прави слоя домейн богат и солиден.

person Mohamed Abed    schedule 05.01.2012