Класс, связывающий лучшие практики в С#
Во-первых, EF не является вариантом для нашей среды разработки, поэтому, пожалуйста, не используйте ответы только "EF"...
Я думаю, что это довольно стандартная дилемма, поэтому я уверен, что должен быть такой способ, чтобы большинство профессионалов делало это так, что я просто не споткнулся... так что я здесь, надеюсь, что я могу показать мне, что это.
Скажем, у вас есть следующие таблицы базы данных:
tblCompanies
ID
NAME
tblDepartments
ID
COMPANY_ID
NAME
tblEmployees
ID
DEPARTMENT_ID
FIRSTNAME
LASTNAME
... какой лучший способ представить это в классах внутри вашего кода?
Я предполагаю, что лучший способ таков:
public class Company
{
public int ID { get; set; }
public string Name { get; set; }
public List<Department> Departments { get; set; }
}
public class Department
{
public int ID { get; set; }
public string Name { get; set; }
public List<Employee> Employees { get; set; }
}
public class Employee
{
public int ID { get; set; }
public string FirstName { get; set;}
public string LastName { get; set; }
}
Я считаю, что для этого должен быть "подход ООП". Однако то, что, кажется, всегда происходит, выглядит примерно так:
public class Department
{
public int ID { get; set; }
public string Name { get; set; }
public int CompanyID { get; set; }
public List<Employee> Employees { get; set; }
}
... главным образом потому, что, когда вы вытаскиваете только департамент из базы данных, у вас будет только идентификатор компании, а не все другие атрибуты, необходимые для полного заполнения экземпляра класса компании.
(Я использовал симпатичный пример ванили, но тот, который я на самом деле занимаюсь в моем текущем проекте, имеет 3 поля, которые он использует для связывания данных, поэтому мысль о том, что у них одинаковые 3 поля в нескольких классах, кажется неправильной для меня)
Есть ли наилучшая практика для этих сценариев? Насколько мне не нравится мысль хранить одни и те же данные в нескольких классах только из лени, мне также не нравится возвращать экземпляр класса с одним из его полей, заполненным, потому что все, что у меня было в то время,
Ответы
Ответ 1
Я не думаю, что для этого есть руководство по лучшим методам, и, конечно же, это зависит от того, как будут использоваться ваши классы. Но в моем личном опыте я пришел к следующему подходу:
public class Company
{
public int ID { get; set; }
public string Name { get; set; }
public IEnumerable<Department> GetDepartments()
{
// Get departments here
}
}
public class Department
{
public int ID { get; set; }
public string Name { get; set; }
protected int CompanyID { get; set; }
private Company _Company;
public Company Company
{
get
{
// Get company here
}
}
public IEnumberable<Employee> GetEmployees()
{
// Get employees here
}
}
public class Employee
{
public int ID { get; set; }
public string Name { get; set; }
protected int DepartmentID { get; set; }
private Department _Department;
public Department Department
{
get
{
// Get department here
}
}
public IEnumberable<Employee> GetEmployees()
{
// Get employees here
}
}
В некоторых случаях я раскрыл некоторые свойства "навигации" моих классов как public
(например, CompanyID и DepartmentID), чтобы предотвратить создание нового класса для получения уже загруженного значения.
Как отмечали другие, вы также можете моделировать "ленивую загрузку", но для этого вам потребуются дополнительные усилия с вашей стороны.
Ответ 2
Это обычная проблема, и ОРМ пытается решить. Конечно, это нелегко в зависимости от ваших потребностей и ваших ограничений.
Есть только два основных варианта для хранения одной копии информации. Ленько загружайте данные по запросу или загружайте все, чтобы начать (Жадная нагрузка). В противном случае вам придется дублировать данные.
При ленивой загрузке вы в основном устанавливаете такие вещи, чтобы при навигации в собственность вы вызывали базу данных и захватывали информацию, необходимую для загрузки объекта, представляющего свойство, к которому вы обращаетесь. Трудная часть, чтобы смотреть с этим - проблема SELECT N + 1. Эта проблема возникает, когда вы заканчиваете повторять набор родительских объектов и запускаете ленивые нагрузки на каждый дочерний объект, что приводит к N + 1 вызовам базы данных для загрузки набора объектов (1) и их дочерних элементов (N).
Жадная загрузка в основном говорит, что загружает все, что вам нужно для начала. ORM (где они работают) хороши, потому что они заботятся о многих деталях через LINQ и создают решения, которые могут быть эффективными и поддерживаемыми обычно вместе с возможностью позволить вам манипулировать использованием Greedy и Lazy Loading.
Другим важным достижением является много-много отношений. Вы должны убедиться, что у вас нет круговой инициализации, и получите весь багаж круговых зависимостей. Есть, конечно, гораздо больше, чем я пропустил.
По моему скромному мнению, я не уверен, что есть лучшая практика, так как есть практика с некоторыми из них плохо - ничто не идеально. Вы можете:
-
Начните развертывание собственного реляционного картографического объекта, позволяющего избавиться от дубликата ID
-
Используйте более легкую структуру ORM для обработки некоторых из них, что позволяет избавиться от дубликата ID
-
Создавайте специализированные запросы для загрузки агрегатов данных, позволяющих избавиться от дубликата ID (* cough * DDD)
-
Просто сохраните дублирование идентификатора, как вы упомянули выше, и не беспокойтесь о создании явной реляционной модели в вашем домене.
Это один из вас, чтобы выбрать, что лучше всего на основе ваших ограничений. Это глубокая тема, и мой опыт ограничен... так что принимайте то, что я говорю, с солью.
Ответ 3
Я думаю, это зависит от требований. Вам нужно пройти вверх (получить компанию из отдела, отдела от сотрудника и т.д.). Если вы это сделаете, тогда лучше всего предоставить вам средства для этого. В идеале это будет нечто вроде имущества компании или отдела, конечно, вы не захотите получать данные, которые вам действительно не нужны, поэтому вы, вероятно, будете хранить идентификатор частной компании и иметь общедоступную функцию getCompany, которая запрашивает данные.
Ответ 4
Я считаю, что это не вопрос ООП, в вашем случае у вас просто есть модель базы данных (представление базы данных в классах), которая не содержит никакой логики, и все классы используются как структуры, и это правильный путь для сопоставления базы данных с классами - структурами. Поэтому в следующем модуле, который будет представлять логику вашей программы, вы должны сопоставить свой модуль базы данных с реальными классами, которые будут содержать логику (я имею в виду методы, которые будут ее реализовывать), конечно, если вы действительно в них нуждаетесь. Поэтому, на мой взгляд, вопрос ОО должен быть в логической части вашего приложения. С другой стороны, вы можете взглянуть на nhibernate и на то, как сделанное отображение там даст вам подсказку для реализации модели базы данных bes.
Ответ 5
Я считаю, что именно так выглядят ваши классы в NHibernate:
public class Company
{
public int ID { get; set; }
public string Name { get; set; }
public IList<Department> Departments { get; set; }
}
public class Department
{
public int ID { get; set; }
public string Name { get; set; }
public Company Company { get; set; }
public IList<Employee> Employees { get; set; }
}
public class Employee
{
public int ID { get; set; }
public string FirstName { get; set;}
public string LastName { get; set; }
public Department Department { get; set; }
}
Обратите внимание, что существует способ перехода от Employee to Department и от отдела к компании (в дополнение к тому, что вы уже указали).
В NHibernate есть всевозможные функции, чтобы это просто работало. И это работает очень, очень хорошо. Основной трюк - это прокси-объекты во время выполнения, чтобы обеспечить ленивую загрузку. Кроме того, NHibernate поддерживает множество различных способов для нетерпеливой и ленивой загрузки именно так, как вы хотите это сделать.
Конечно, вы можете получить эти же функции без NHibernate или аналогичного ORM, но почему бы не использовать просто использовать богатую функциональными возможностями технологию, а не вручную кодировать собственную функцию, нестандартную ORM?
Ответ 6
Есть еще один вариант. Создайте класс DataController, который обрабатывает загрузку и "напоминание" ваших объектов. DataController поддерживает словарь [CompanyIDs, объекты компании] и [DepartmentIDs, объекты отдела]. Когда вы загружаете новый отдел или компанию, вы сохраняете запись в этом словаре DataController. Затем, когда вы создаете экземпляр нового отдела или сотрудника, вы можете либо напрямую установить ссылки на родительские объекты, либо вы можете использовать объект Lazy [Company/Department] и установить его с помощью лямбда (в конструкторе), который будет поддерживать область действия DataController, без ссылки на него непосредственно внутри объектов. Одна вещь, о которой я забыл упомянуть, вы также можете поместить логику в метод getter/get для словарей, которые запрашивают базу данных, если конкретный идентификатор не найден. Использование всего этого вместе позволяет вашим классам (моделям) быть очень чистым, при этом все еще достаточно гибким, когда/как их данные загружаются.