Можем ли мы использовать перечисления как идентификаторы типов типов?
Мы работаем с довольно большой моделью в первой настройке кода EF 6.1, и мы используем int для идентификаторов сущностей.
К сожалению, это не так, как хотелось бы, так как мы можем легко смешивать идентификаторы, например, сравнивая идентификаторы объектов разных типов (myblog.Id == somePost.Id) или аналогичные. Или еще хуже: myBlog.Id ++.
Поэтому я придумал идею использования типизированных идентификаторов, поэтому вы не можете смешивать идентификаторы.
Поэтому нам нужен тип BlogId для нашего блога. Теперь очевидным выбором было бы использовать int, завернутый в структуру, но вы не можете использовать structs как ключи. И вы не можете расширить int... - подождите, вы можете! Использование enum!
Итак, я придумал это:
public enum BlogId : int { }
public class Blog
{
public Blog() { Posts = new List<Post>(); }
public BlogId BlogId { get; set; }
public string Name { get; set; }
public virtual List<Post> Posts { get; set; }
}
internal class BlogConfiguration : EntityTypeConfiguration<Blog>
{
internal BlogConfiguration()
{
HasKey(b => b.BlogId);
Property(b=>b.BlogId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
}
Итак, теперь у нас есть типы safeID - сравнение BlogId и PostId - это ошибка времени компиляции.
И мы не можем добавить 3 в BlogId.
Пустые перечисления могут выглядеть немного странно, но это больше детализация реализации.
И мы должны установить параметр DatabaseGeneratedOption.Identity явно в нашем сопоставлении, но это одноразовое усилие.
Прежде чем мы начнем преобразовывать весь наш код в этот шаблон, существуют ли очевидные проблемы?
Изменить:
Вероятно, мне нужно выяснить, почему мы должны работать с идентификаторами вместо полных объектов. Иногда нам нужно сопоставлять объекты в запросах EF Linq - и сравнение объектов там не работает. Например (построив пример блога и предположив несколько более богатую модель домена): найдите комментарии к записи в текущем блоге пользователей. Помните, что мы хотим сделать это в базе данных (у нас много данных), и мы предполагаем, что нет прямых навигационных свойств. И currentUser не подключен. Наивный подход был бы
from c in ctx.Comments where c.ParentPost.Blog.Author == currentUser
Это не работает, поскольку вы не можете сравнивать объекты в EF Linq.
Поэтому мы стараемся
from c in ctx.Comments where c.ParentPost.Blog.Id == currentUser.Id
Это компилируется и выполняется, но не так - должно быть
from c in ctx.Comments where c.ParentPost.Blog.Author.Id == currentUser.Id
Элементы safeafe поймали бы его. И у нас есть гораздо более сложные запросы, чем это. Попробуйте "найти комментарии к записи в текущие пользователи, сделанные конкретным другим пользователем, которые текущий пользователь не прокомментировал позже".
С уважением, Нильс
Ответы
Ответ 1
Это интересный подход, но вопрос в том, стоит ли это и каковы последствия?
Вы все еще можете сделать что-то вроде
if ((int)blog.BlogId == (int)comment.CommentId) { }
Лично я бы потратил больше времени на обучение людей, написание хороших тестов и обзоров кода, вместо того, чтобы пытаться добавить какую-то дополнительную сложность, которая влияет на то, как вы используете и запрашиваете свои сущности.
Подумайте - например:
- Какое влияние оказывает эта литье на производительность в запросах LINQ?
- Как бы вы это соблюдали, если вы раскрываете свои операции через Web API WCF?
- Это работает с навигационными свойствами?
- Кроме того, вы ограничены в типах, которые вы могли бы использовать в качестве первичного ключа; Я не считаю, что это работает с гидами.
Способ дополнительной защиты заключается в том, чтобы ваш доменный уровень обрабатывал такие вещи, принимая экземпляры сущностей вместо идентификаторов.
Ответ 2
Я даже не был знаком с этим использованием, но немного поработал, и даже команда EF говорит, что он способен. Из их начального сообщения в блоге о поддержке перечисления в EF это указано:
Перечисления как клавиши
Кроме того, свойства перечисляемых типов могут участвовать в определении первичных ключей, уникальных ограничений и внешних ключей, а также принимать участие в контрольных проверках concurrency и объявлять значения по умолчанию.
источник: http://blogs.msdn.com/b/efdesign/archive/2011/06/29/enumeration-support-in-entity-framework.aspx
Я никогда не делал этого сам, но эта цитата дает мне уверенность. Так что это возможно, но, как предлагает L-Three: действительно подумайте, хотите ли вы, что вы хотите (плюсы и минусы, но похоже, что вы уже это сделали) и тестовый тест!
Ответ 3
Я действительно не пытаюсь bash вас, но как можно смешивать идентификаторы типа X с идентификаторами типа Z?
Я никогда не встречал никого, кто делал такие вещи, как myBlog.Id ++, либо (или, по крайней мере, не без увольнения).
Во всяком случае, вот решение, которое швы будет меньше работы и лучше обслуживается (особенно для db-админов):
- В TypeConfiguration создайте Id через свободный API (вы поймете, почему позже)
-Создание абстрактного базового класса для всех ваших объектов с помощью:
* свойство: proteced int Id
* метод: public int getIdValue()
* method: public bool isSameRecord (T otherEntity), где T: EntityBaseClass
Я предполагаю, что первый метод не требует пояснений, isSameRecord возьмет другой экземпляр вашего базового класса, сначала проверит проверку типа, и если он пройдет его, он также проведет проверку id.
Это непроверенный подход, есть хорошая вероятность, что вы не можете создавать защищенные идентификаторы.
Если это не сработает, вы можете создать public int _id и просто сообщить своей команде, чтобы он не использовал его напрямую.
Ответ 4
Не уверен, что это будет работать в EF, но одна вещь, которую вы можете сделать, это реализовать ваши объекты IEquatable<T>
:
Например, ваш класс Blog
:
public class Blog : IEquatable<Blog>
{
// other stuff
public bool Equals(Blog other)
{
return this.Id.Equals(other.Id);
}
}
В качестве альтернативы вы можете использовать более гибкую ORM, такую как NHibernate. Если это интересно, сообщите мне, и я расширю свой ответ.
Ответ 5
Я знаю, что я немного опаздываю на эту вечеринку, но я использовал эту технику, и она определенно работает!
Тип безопасности работает точно так же, как вы предлагаете. Компилятор поймает ошибки, такие как
from c in ctx.Comments where c.ParentPost.Blog.Id == currentUser.Id
И это предотвращает глупые математики.
currentUser.Id++;
currentUser.Id * 3;
Свойства навигации по-прежнему работают нормально, если оба конца навигации являются одним и тем же типом перечисления.
И SQL-запросы работают так же, как и с int
.
Это, безусловно, интересная идея!
Может использовать идентификаторы типов типов? - Да!
Должен ли вы? Я не уверен. Не похоже, что именно так был разработан EF и чувствует себя немного взломанным.