This is my perfect idea of day: Strong typed ids in Entity Framework.
Motivation:
Ok here is my implementation. I hope its pretty self declarative what I mean.
//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;
}
}
When creating migration its jus complain that there is no key. When trying to set key on fluent api it give more precious error: The property 'ID' cannot be used as a key property on the entity 'MyNs.Models.TestModel1' because the property type is not a valid key type. Only scalar types, string and byte[] are supported key types.
Ok understood key cannot be any type but data of my type is just one int witch even has implicit conversions. Inheriting int in this situation is very tempting but like we know its impossible.
Primary question: How to finish this and tell to EF that converting my DbId to int and back is not rocket science.
Secondary question: Is this good idea? Why? Do you suggest feature request if this is not currently possible?
I believe I understand your goal: your aim is to encapsulate the primary key of each model such that primary keys of two different models cannot be compared directly. So, for example, you would want to avoid the comparison Customer.ID == Order.ID at compile time.
However, in your code example, the implicit operator for int <-> DbId<T> works against your goal because this code compiles:
var model1 = new TestModel1() {ID = 1};
var model2 = new TestModel2() {ID = 2};
Console.WriteLine(model1.ID == model2.ID);
So, if I follow your reasoning, it would not work even if EF6+ allowed [Key] on a class (other than string.)
Getting back to basics, if you believe the name ID is too ambiguous, why not follow the Entity Framework Primary Key Convention of class name followed by "ID"?
Example:
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; }
}
This naming convention (along with plain ID) is the convention I see the most in Entity Framework code. So, it has the major benefit of allowing other people to step in and seamlessly acclimate and maintain your code (a benefit that we tinkerers are all guilty of overlooking sometimes!)
Looking at your motivations...
Comparing ModelTypeA.ID and ModelTypeB.ID is (at least almost) always an error.
Are you solving a problem that isn't actually a problem? How often do programmers really screw up Order.CustomerID == Customer.CustomerID?
Code will be more self declarative.
Up to debate? If I spot DbId<Customer> id = Customer.ID in someone's code, is it really more declarative than int id = Customer.CustomerID?
That said, I applaud your effort! Solving problems is what we programmers love to do. Good luck!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With