2
votes

CLARIFICATION: I am not attempting to store a Class/Type in a database field, or use the Class/Type itself as the key value. I am trying to use a single property of the Class/Type (in this instance, a Guid) as the key.

Although I don't think it's strictly relevant to this particular issue, it may be useful for context: I am trying to implement Semantic Types in my latest project. I have had some success throughout my code, and have in fact identified a couple of bugs that would probably not have been identified quickly as a result.

All is well with integrating my Semantic Types with EF6 in non-key fields, with the help of some Fluent API code.

However, when attempting to use a Semantic Type for a key field, EF generates a runtime exception, stating that there is no key defined - when clearly there is.

With a simple Guid as key

Here is an example of an EF entity I used which does not use Semantic Types:

public partial class MyEntity
{
    [Key]
    public Guid id { get; set; }

    public string SomeData { get; set; }
}

The above works fine.

With a Type property as key - Attempt #1

Now, I define a new Semantic Type, as follows:

public class MyEntityId: SemanticType<Guid>
{
    public MyEntityId()
        : base(IsValid, Guid.Empty)
    { }

    public MyEntityId(Guid value)
        : base(IsValid, value)
    { }

    public static bool IsValid(Guid value)
    {
        return true;
    }
}

... and update the EF model accordingly.

public partial class MyEntity
{
    [Key]
    public MyEntityId id { get; set; }

    public string SomeData { get; set; }
}

I then add some code to the DbContext OnModelCreating method to map the MyEntityId.Value to the required column name:

modelBuilder
  .Entity<MyEntity>()
    .Property(x => x.MyEntityId.Value)
    .HasColumnName("id");

Now, all of the above compiles fine. But as soon as there is an interaction with the table, I get the following exception:

An exception of type 'System.Data.Entity.ModelConfiguration.ModelValidationException'
occurred in EntityFramework.dll but was not handled in user code

EntityType 'MyEntity' has no key defined. Define the key for this MyEntity.

Clearly, the entity does have a key defined.

With a Type property as key - Attempt #2

So, I tried removing the [key] attribute from the model, and modified the Fluent API code, as follows:

modelBuilder
  .Entity<MyEntity>()
    .HasKey(x => x.MyEntityId.Value);
    .Property(x => x.MyEntityId.Value)
    .HasColumnName("id")

But this code then generates the following exception:

An exception of type 'System.InvalidOperationException' occurred in
EntityFramework.dll but was not handled in user code

Additional information: The properties expression 'x => x.MyEntityId.Value'
is not valid. The expression should represent a property: 
C#: 't => t.MyProperty'  VB.Net: 'Function(t) t.MyProperty'. 
When specifying multiple properties use an anonymous type: 
C#: 't => new { t.MyProperty1, t.MyProperty2 }'
VB.Net: 'Function(t) New With { t.MyProperty1, t.MyProperty2 }'.

With a Type property as key - Attempt #3

So, after digging around on SO, it looks like EF requires a property, not just a public field. So I modified the Semantic Type to introduce a new "EFValue" property, as follows:

public class MyEntityId: SemanticType<Guid>
{
    public MyEntityId()
        : base(IsValid, Guid.Empty)
    { }

    public MyEntityId(Guid value)
        : base(IsValid, value)
    { }

    public static bool IsValid(Guid value)
    {
        return true;
    }

    public Guid EFValue
    {
        get { return Value; }
    }
}

... and modified the Fluent API code, as follows:

modelBuilder
  .Entity<MyEntity>()
    .HasKey(x => x.MyEntityId.EFValue);
    .Property(x => x.MyEntityId.Value)
    .HasColumnName("id")

But then I get the same 'System.InvalidOperationException' exception.

Is it not possible to define the property of a Type like this as a Primary Key in EF6?

1
Consider yourself as a relational DBMS, how do you handle types as keys? If you can then expect EF to do that either. - Hamid Pourjam
Even if you could do this, are you sure you want to? Think about it, if EF has to serialize (most likely in binary) the key for the table, this will result in very large and difficult to index keys. Keys should be simple and sortable, I'm not sure you gain anything by changing the PK or FK into a semantic type. - Ron Beyer
I am struggling to see why it's an issue, tbh. I am not attempting to use a type as a key - simply the value of a property of the instance of a type - in this case, the key in the database table is still a GUID. The type itself has a property that I want to use as the key. I don't, in my probable ignorance, see the difference between being able to do this and mapping a property to a column using Fluent API: modelBuilder .Entity<MyEntity>().Property(x => x.MyEntityId.Value) .HasColumnName("id"). I am clearly missing something. - user2209634
If an object has another object as a property, EF will tend to create a new table and link them via a 1:1 relationship. But that will only work with primary keys, so RF would need a key column to access the key ... My usual solution is to insert an integer id field into each class that is persisted with EF. - DasKrümelmonster
Thanks for your suggestion. But introducing an integer id doesn't really help. I am in this situation because I effectively want to strongly-type the ID field as "MyEntityID", which in this case is a GUID. Changing it to an strongly-typed int will give the same problem. Changing it to a simple int will of course work, but only by eliminating what I want to achieve in the first place! - user2209634

1 Answers

1
votes

The error made it pretty clear you can't do what you want. Simply wrap the ID property at the entity level and it'll work:

public partial class MyEntity
{
    public MyEntityId BadIdea { get; set; }

    [Key]
    public Guid Id 
    { 
       get 
       { 
            // needs null check
            return BadIdea.Value; 
       } 
       set 
       { 
           // needs null check
           BadIdea.Value = value; 
       }
    }
}