2009-11-25 4 views
2

У меня был быстрый вопрос о правильных объектных отношениях, которые я должен был установить для этой ситуации:Каковы правильные отношения с объектами? (C#)

У меня есть объект Customer со связанными параметрами и объект депо со связанными параметрами. Каждое депо обслуживает множество клиентов, и заказчику нужен доступ к определенной информации для своего депо.

Мне интересно, какое правильное отношение я должен настроить так, чтобы набор объектов клиента ссылался на один и тот же экземпляр определенного объекта депо. Я хотел быть уверенным, что это не создает дублирующий объект Depot для каждого клиента. Кроме того, я хотел бы иметь возможность изменять свойства Депо, не пробираясь через самого клиента.

Я знаю, что это, вероятно, довольно простой вопрос, но у C# есть так много разных «функций», которые время от времени путают.

Благодарим за помощь!

  • Чарли
+1

Просто некоторые идеи: 1. TreeView парадигма: каждый Клиент является дочерним узлом Узлы клиентов узла депо. 2. Составной объект с взаимными ссылками: каждый объект депо - объект, который поддерживает список : у каждого клиента есть поле, поддерживающее ссылку на его депо. – BillW

+0

Я бы рекомендовал посмотреть на шаблоны архитектуры предприятия Мартином Фаулером. Это отличная книга и многое покрывает то, о чем вы просите. –

ответ

1

Если я правильно понимаю ваш вопрос, я думаю, что решение вашей проблемы может быть ИЛИ картографа. На данный момент Microsoft предоставляет два OR-карты, LINQ to SQL и Entity Framework. Если вы используете .NET 3.5, я рекомендую использовать LINQ to SQL, но если вы можете поэкспериментировать с .NET 4.0, я настоятельно рекомендую изучить Entity Framework. (Я препятствую использованию Entity Framework в .NET 3.5, так как он был выпущен очень рано и имеет много проблем.)

Оба этих OR-модуля предоставляют инструменты визуального моделирования, которые позволяют создавать концептуальную модель сущности. С LINQ to SQL вы можете создать модель из своей базы данных, которая предоставит вам классы сущностей, а также ассоциации между этими классами (представляющие ваши внешние ключи из вашей схемы БД). Структура LINQ to SQL будет обрабатывать запросы SQL для вас и автоматически сопоставляет результаты запросов базы данных к графам объектов. Отношения, такие как описанные вами, с несколькими клиентами в наборе, ссылающимся на один и тот же отдельный отдел, обрабатываются автоматически для вас, вам не нужно беспокоиться о них вообще. У вас также есть возможность запрашивать вашу базу данных с помощью LINQ, и вы можете избежать необходимости писать значительное количество хранимых процедур и кода сантехники/картографии.

Если вы используете .NET 4.0, Entity Framework буквально LINQ to SQL на стероидах. Он поддерживает все LINQ to SQL, и, черт возьми, намного больше. Он поддерживает модельный дизайн, позволяющий создавать концептуальную модель, из которой генерируются схема кода и базы данных. Он поддерживает гораздо более широкий спектр отображений, обеспечивая более гибкую платформу. Он также предоставляет Entity SQL (eSQL), который представляет собой текстовый язык запросов, который может использоваться для запроса модели в дополнение к LINQ to Entities. Line LINQ to SQL, он решит сценарий, который вы использовали в качестве примера, а также многие другие.

OR mappers могут быть огромными затратами времени, денег и усилий, значительно уменьшая объем усилий, необходимых для взаимодействия с реляционной базой данных. Они обеспечивают как динамические запросы, так и динамические, оптимистичные обновления/вставки/удаления при разрешении конфликтов.

+0

Linq to SQL следует рекомендовать только тогда, когда вы знаете, что SQL используется. Кроме того, он, похоже, не отвечает на фундаментальный вопрос «Как мне отслеживать все это в памяти, так что мне не нужно проходить через объект клиента, чтобы добраться до складов [и наоборот]?» –

+0

@Jason D: L2S был только одним из вариантов. Общий смысл сообщения заключался в использовании блока отображения OR, который полностью решает проблему поставленного вопроса, который отслеживал экземпляры объектов, так что каждый объект клиента не ссылался на свою собственную копию объекта депо, используя общий экземпляр. Существует множество ручных решений проблемы, но они требуют УСТОЙЧИВОГО усилия по внедрению и обслуживанию. ORM - это наиболее эффективный с точки зрения затрат ответ, который не нарушает единой ответственности или разделения проблем. L2S и EF легко доступны, бесплатны и просты в использовании. – jrista

+0

Это не обязательно должно быть L2S или EF. Вероятно, я должен был упомянуть nHibernate, SubSonic, Telerik OpenAccess. Существуют и другие варианты ORM, которые обычно делают то же самое, что и L2S (или EF в будущем), хотя и не так просто и мощно. ORM заботится о сопоставлении, разделении проблем домена и персистентности, ленивой загрузки (или явной загрузки по требованию, если вы предпочитаете), отслеживании состояния и генерации sql для обоих поисков и обновлений. Память эффективно используется без дублирования экземпляров объектов. В случае L2S и EF v4 эффективность SQL очень высока. – jrista

1

Похоже, что у вас есть отношения «многие ко многим». (Клиенты знают о своих депо, и наоборот)

В идеале это лучше всего подходит для приложения базы данных, где вы определяете таблицу слабых сущностей ... Конечно, использование базы данных является излишним, если мы говорим о 10 Клиенты и 10 складов ...

Предполагая, что база данных переполнена, это может быть смоделировано в коде с некоторыми Словарями. Предполагая, что вы используете Int для уникальных идентификаторов для обоих депо и Клиентом, вы можете создать что-то вроде следующего:

// creating a derived class for readability. 
public class DepotIDToListOfCustomerIDs : Dictionary<int,List<int>> {} 
public class CustomerIDToListOfDepotIDs : Dictionary<int,List<int>> {} 
public class DepotIDToDepotObject : Dictionary<int,Depot>{} 
public class CustomerIDToCustomerObject : Dictionary<int, Customer>{} 
//... 
// class scope for a class that manages all these objects... 
DepotIDToListOfCustomerIDs _d2cl = new DepotIDToListOfCustomerIDs(); 
CustomerIDToListOfDepotIDs _c2dl = new CustomerIDToListOfDepotIDs(); 
DepotIDToDepotObject _d2do = new DepotIDToDepotObject(); 
CustomerIDToCustomerObject _c2co = new CustomerIDToCustomerObject(); 
//... 
// Populate all the lists with the cross referenced info. 
//... 
// in a method that needs to build a list of depots for a given customer 
// param: Customer c 
if (_c2dl.ContainsKey(c.ID)) 
{ 
    List<int> dids=_c2dl[c.ID]; 
    List<Depot> ds=new List<Depot>(); 
    foreach(int did in dids) 
    { 
     if (_d2do.ContainsKey(did)) 
      ds.Add(_d2do[did]); 
    } 
} 
// building the list of customers for a Depot would be similar to the above code. 

EDIT 1: обратите внимание, что с выше код, я созданный его, чтобы избежать циклических ссылок. Имея ссылку на клиента, депо, которое также ссылается на того же клиента, будет препятствовать их быстрому сбору мусора. Если эти объекты будут сохраняться для всего срока службы приложений, то, безусловно, можно было бы сделать более простой подход. В этом подходе у вас будет два списка, один из экземпляров Customer, другой - список экземпляров Depot. Клиент и депо будут содержать списки складов и клиентов соответственно. Однако вам все равно потребуется два словаря, чтобы разрешить идентификаторы депо для клиентов и наоборот. Полученный код будет на 99% таким же, как выше.

EDIT 2: Как указано в других ответов вы можете (и должен) иметь брокера объектную модель, которая делает отношения и отвечает на вопросы о взаимоотношениях. Для тех, кто неправильно прочитал мой код; он никоим образом не предназначен для создания абсолютной и полной объектной модели для этой ситуации.Однако он предназначен для иллюстрации того, как объект-брокер будет управлять этими отношениями таким образом, который предотвращает циклические ссылки. У вас есть извинения за путаницу, вызванную первым разом. И я благодарен за иллюстрацию хорошей презентации OO, которая была бы легко использована другими.

+2

Системы OO не имеют проблем с реализацией многих отношений. Ваш ответ хорош до кода. –

+0

Роберт, не могли бы вы быть более точными, пожалуйста? –

+0

Вы можете изменить его так, чтобы клиенты и хранилища были построены только с помощью статических методов, которые могут гарантировать семантику singleton об уникальном идентификаторе. Таким образом, вам не понадобятся странные наборы словарей, что может указывать @Robert. – user7116

1

В ответ на @Jason D и ради @Nitax: я действительно сглаживаю поверхность, потому что, хотя она в основном проста, она также может усложниться. Я не собираюсь переписывать его лучше, чем Мартин Фаулер (конечно, не через 10 минут).

Прежде всего вам нужно разобраться с проблемой только одного объекта в памяти, относящегося к определенному депо. Мы достигнем этого с помощью чего-то, называемого репозиторием. CustomerRepository имеет метод GetCustomer(), а метод DepotRepository имеет метод GetDepot(). Я буду махать руками и притворяться, что это происходит.

Во-вторых, вам нужно будет написать несколько тестов, которые показывают, как вы хотите, чтобы код работал. Я не знаю этого, но все равно несу.

// sample code for how we access customers and depots 
Customer customer = Repositories.CustomerRepository.GetCustomer("Bob"); 
Depot depot = Repositories.DepotRepository.GetDepot("Texas SW 17"); 

Теперь твердая часть здесь: Как вы хотите моделировать отношения? В системах OO вам действительно не нужно ничего делать. В C# I может просто сделать следующее.

Клиенты сохранить список депо они с

class Customer 
{ 
    public IList<Depot> Depots { get { return _depotList; } } 
} 

альтернативно, Депо хранить список клиентов, они с

class Depot 
{ 
    public IList<Customer> Customers { get { return _customerList; } } 
} 
// * code is very brief to illustrate. 

В это самая основная форма, любое количество клиентов может ссылаться на любое количество депо. m: n решена. Ссылки дешевы в OO.

Помните, что проблема, с которой мы столкнулись, заключается в том, что, хотя Клиент может хранить список ссылок на все хранилища, о которых он заботится (первый пример), Депо не может перечислить всех Клиентов.

Чтобы получить список всех клиентов для Depot (первый пример), мы должны написать код, который перебирает всех клиентов и проверяет свойство customer.Depots:

List<Customer> CustomersForDepot(Depot depot) 
{ 
    List<Customer> allCustomers = Repositories.CustomerRepository.AllCustomers(); 
    List<Customer> customersForDepot = new List<Customer>(); 

    foreach(Customer customer in allCustomers) 
    { 
     if(customer.Depots.Contains(depot)) 
     { 
      customersForDepot.Add(customer); 
     } 
    } 
    return customersForDepot; 
} 

Если бы мы использовали Linq, мы мог бы написать его как

var depotQuery = from o in allCustomers 
       where o.Depots.Contains(depot) 
       select o; 

return query.ToList(); 

У вас 10 000 000 клиентов, которые хранятся в базе данных? Ой! Вы действительно не хотите загружать всех 10 000 000 клиентов каждый раз, когда Депо должно определять своих «клиентов». С другой стороны, если у вас есть только 10 хранилищ, запрос, загружающий все хранилища один раз и некоторое время, не имеет большого значения. Вы всегда должны думать о своих данных и о своей стратегии доступа к данным.

Мы могли иметь список в обоих Customer и Depot. Когда мы это делаем, мы должны быть осторожны в реализации. При добавлении или удалении ассоциации мы должны внести изменения в оба списка одновременно. В противном случае у нас есть клиенты, думающие, что они связаны с депо, но депо ничего не знает о клиенте.

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

class CustomerDepotAssociation 
{ 
    public Customer { get; } 
    public Depot { get; } 
} 

class CustomerDepotAssociationRepository 
{ 
    IList<Customer> GetCustomersFor(Depot depot) ... 
    IList<Depot> GetDepotsFor(Customer customer) ... 
    void Associate(Depot depot, Customer customer) ... 
    void DeAssociate(Depot depot, Customer customer) ... 
} 

Это еще одна альтернатива. Репозитарию ассоциации не нужно раскрывать, как он связывает клиентов с хранилищами (и, кстати, из того, что я могу сказать, это то, что пытается сделать код @Jason D)

Я могу предпочесть отдельный объект в этом случае, потому что то, что мы говорим, является ассоциацией Клиента и Депо, является сущностью для себя.

Так идти вперед и читать некоторые Domain Driven Design книги, а также купить Martin Fowlers PoEAA (Узоры Enterprise Application Architecture)

+0

По сути, у вас будет словарь (или хеш-таблица), управляющий отношениями. Но в свете того, что спрашивал ОП, мне казалось, что это скорее вопрос «какие объекты подходят для управления отношениями m: n»; а не «как завуалировать внутренности менеджера отношений, чтобы они были просты в использовании». (Но это хороший вопрос, и вы дали хороший ответ ...) –

+0

Одна нота об использовании linq, как правило, имеет проблемы с производительностью. Многие люди рассказывают о ней. Первоначально я думал опубликовать ответ linq, но быстро понял, что буду рассказывать людям делать то, что в настоящее время кусает нас в заднем конце моей работы ... –

+0

Да Linq (to Sql) может иметь проблемы с производительностью, но я бы не стал освободить его из-под контроля. Мы используем его все время, и да, вы должны быть осторожны, потому что, точно так же, как sql, вы можете написать несколько потрясающих результатов с плохими запросами. Сам Линк хоть и фантастический. –

0

Надежда это понятно.

OO:

depot_model_01

ER:

depot_model_02

Смежные вопросы