2015-10-30 3 views
2

Это моя идеальная идея дня: сильные типизированные идентификаторы в Entity Framework.Тип безопасных ключей с моделью Entity Framework

Мотивация:

  1. Сравнение ModelTypeA.ID и ModelTypeB.ID (по крайней мере, почти) всегда ошибка. Почему не скомпилировать это?
  2. Если вы используете пример для запроса DbContext, его легко реализовать, получите непосредственно модель от id. "id.Value.ProductNumber"
  3. Код будет более само декларативным.
  4. Они просто естественно набраны так, почему бы и нет?

Хорошо, вот моя реализация. Я надеюсь, что его довольно само декларативный, что я имею в виду.

//Optional interface may be handy on some scenarios 
public interface Identifiable<T> where T : class, Identifiable<T> 
{ 
    DbId<T> ID { get; set; } 
} 

public class TestModel1 : Identifiable<TestModel1> 
{ 
    [Key] 
    public DbId<TestModel1> ID { get; set; } 
    public string Data1 { get; set; } 
} 

public class TestModel2 : Identifiable<TestModel2> 
{ 
    [Key] 
    public DbId<TestModel2> ID { get; set; } 
    public string Data2 { get; set; } 
    public DbId<TestModel1> TestModel1ID { get; set; } 
    public virtual TestModel1 TestModel1 { get; set; } 
} 

[Serializable] 
public class DbId<T> where T : class 
{ 
    public int ID { get; set; } 

    public static implicit operator DbId<T>(int id) 
    { 
     var c = new DbId<T>() { ID = id }; 
     return c; 
    } 

    public static implicit operator int (DbId<T> id) 
    { 
     return id.ID; 
    }  
} 

При создании миграции его jus жалуются, что нет ключа. При попытке установить ключ на беглой api он дает более ценную ошибку: Идентификатор свойства не может быть использован как свойство ключа в сущности MyNs.Models.TestModel1, потому что тип свойства не является допустимым типом ключа. Поддерживаются только типы скалярных, строковых и байтовых [].

Хорошо понимаемый ключ не может быть ни одного типа, но данные моего типа - это всего лишь одна int witch, даже имеющая неявные преобразования. Наследование int в этой ситуации очень заманчиво, но, как мы знаем, это невозможно.

Основной вопрос: как закончить это и сказать EF, что преобразование моего DbId в int и обратно не является наукой о ракете.

Второй вопрос: Это хорошая идея? Зачем? Вы предлагаете запрос функции, если это невозможно в настоящее время?

+1

Я понимаю, что вы пытаетесь сделать, но это ужасная работа, чтобы попытаться предотвратить то, что очень редко произойдет при аварии. Я полагался бы на тестирование уровня абстракции выше, а не на это. – viggity

+0

Почему это ужасная работа? После того, как реализовано одно (и базовое решение уже есть), решение является переносимым/разделяемым, и только различие заключается в замене «int» на «DbId » при написании нового кода. – user766304

ответ

1

Я считаю, что понимаю вашу цель: ваша цель состоит в том, чтобы инкапсулировать первичный ключ каждой модели, чтобы первичные ключи двух разных моделей не могли сравниваться напрямую. Так, например, вы бы хотели избежать сравнения Customer.ID == Order.ID во время компиляции.

Однако, в вашем примере кода implicit operator для int <-> DbId<T> работает против своей цели, потому что этот код compiles:

var model1 = new TestModel1() {ID = 1}; 
var model2 = new TestModel2() {ID = 2}; 
Console.WriteLine(model1.ID == model2.ID); 

Так что, если я следую за ваши рассуждения, это не будет работать, даже если EF6 + разрешено [Key] на класса (кроме string.)

Возвращаясь к основам, если вы считаете, что имя ID слишком неоднозначно, то почему бы не следовать за именем Primary Key Convention из класса Entity Framework с последующим «I D "?

Пример:

public class Customer 
{ 
    // [Key] is implicit by convention 
    public int CustomerID { get; set; } 
    public string Name { get; set; } 
} 

public class Order 
{ 
    // [Key] is implicit by convention 
    public int OrderID { get; set; } 
    public DateTime SubmittedDate { get; set; } 
    // [ForeignKey("Customer")] is implicit by convention 
    public int CustomerID{ get; set; } 
    public virtual Customer Customer { get; set; } 
} 

Это соглашение об именовании (наряду с обычной ID) является соглашением, я вижу больше всего в Entity Framework кода. Таким образом, он имеет большое преимущество, позволяя другим людям входить и плавно акклиматизировать и поддерживать ваш код (преимущество, которое мы, воины, все виноваты в том, чтобы иногда пропускать!)

Глядя на ваши мотивы ...

Comparing ModelTypeA.ID and ModelTypeB.ID is (at least almost) always an error.

Вы решаете проблему, которая на самом деле не проблема? Как часто программисты действительно испортили Order.CustomerID == Customer.CustomerID?

Code will be more self declarative.

До обсуждения? Если я нахожу DbId<Customer> id = Customer.ID в чей-то код, он действительно более декларативный, чем int id = Customer.CustomerID?

Это, я приветствую ваши усилия! Решение проблем - это то, что мы программисты любим делать. Удачи!

+0

Спасибо за ваш ответ. Да, эти неявные операторы только отчаянно пытаются заставить его работать хотя бы как-то с EF. В большой картине наше текущее использование EF испорчено, и я пытаюсь переосмыслить эту область орм-запросов. В этом продукте мы фактически используем одно и то же соглашение об именах, о котором вы упомянули, но я планирую сделать некоторые вспомогательные ресурсы интерфейса (и т. Д.) Для управления запросами. Это требует большей абстракции, поэтому различное имя ID для каждого типа dosen't хорошо подходит для моего идентификационного интерфейса. Но незавершенная работа ... – user766304

+0

«Как часто программисты действительно испортили Order.CustomerID == Customer.CustomerID?» - Ты прав, не так много раз. Существует немалая проблема с текущим стилем использования. Но если мы обновим эти фиктивные идентификационные номера для чего-то более умного, я верю, что мы можем разработать лучшие решения. Точно так же, как адреса памяти - это всего лишь цифры, но текущие ссылки могут автоматически обрабатывать память, и мы не угрожаем им как цифры на уровне языка. – user766304

+0

'Как часто программисты действительно прикручивают Order.CustomerID == Customer.CustomerID' - более вероятно, что они вот-вот испортились:' ProductRepository.ById (order.PersonId); '(авто-полная ошибка, потому что' PersonId 'был вершиной списка, когда я набрал' .P'), что очень затрудняет диагностику ошибок. Если метод ById принимал только 'ProductId', этого не произошло бы. В условиях риска это вряд ли произойдет, но в конечном итоге это может быть дорогостоящим. Оценивая риск, вам нужно смотреть за пределы частоты, поскольку это слишком упрощенное представление. – Fenton

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