Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Entity framework migrations throws exception when using extension method for fluent API

I'm using Entity Framework 6, Code-First with Fluent-API.

I have the following extension method to set the primary key on my entities:

public static class ConfigurationExtensions
{
    public static void HasPrimaryKey<TEntityType>(this EntityTypeConfiguration<TEntityType> configuration)
        where TEntityType : class, IEntity
    {
        configuration
            .HasKey(m => m.Id)
            .Property(t => t.Id)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
    }
}

with every Code-First Entity implementing the following simple interface:

public interface IEntity
{
    int Id { get; }
}

Now suppose I have the following Entity:

public class MyEntity : IEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
}

which I'm configuring with Fluent-API in my DbContext

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Configurations.Add(new MyEntityConfiguration());
}

with the configuration class for the entity:

public class MyEntityConfiguration : EntityTypeConfiguration<MyEntity>
{
    public MyEntityConfiguration()
    {
        this.HasPrimaryKey()
    }
}

Oddly when I execute Add-Migration in the Package Manager Console I get the following exception:

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.InvalidOperationException: The property 'Id' cannot be used as a key property on the entity 'MyEntity' because the property type is not a valid key type. Only scalar types, string and byte[] are supported key types.
   at System.Data.Entity.ModelConfiguration.Configuration.Types.EntityTypeConfiguration.Key(PropertyInfo propertyInfo, Nullable`1 overridableConfigurationParts)
   at System.Data.Entity.ModelConfiguration.Configuration.Types.EntityTypeConfiguration.Key(IEnumerable`1 keyProperties)
   at System.Data.Entity.ModelConfiguration.EntityTypeConfiguration`1.HasKey[TKey](Expression`1 keyExpression)
   at ConfigurationExtensions.HasPrimaryKey[TEntityType](EntityTypeConfiguration`1 configuration) in C:\path\ConfigurationExtensions.cs

It is odd because when I refactor the code in the extension method back into the constructor, like this:

public class MyEntityConfiguration : EntityTypeConfiguration<MyEntity>
{
    public MyEntityConfiguration()
    {
        HasKey(m => m.Id)
        .Property(t => t.Id)
        .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
    }
}

then, the exception is not thrown by the framework and everything works as expected. Why? Framework bug?

like image 355
QuantumHive Avatar asked Mar 01 '26 05:03

QuantumHive


1 Answers

The exception message is misleading. The actual issue is quite trivial. EF supports only properties that have a property setter.

Since the expression m => m.Id in your generic extension method binds to the Id property of the IEntity interface, which has no setter (in contrast with the implementing class), EF does not consider it a valid property and throws the exception with the misleading message about property type.

To fix it, simply define setter for the Id property inside the interface:

public interface IEntity
{
    int Id { get; set; }
}

It can also be resolved by building the lambda expression manually using the Expression class methods, but I think modifying the interface is easier :) But here it is for completeness and in case you don't want to break your interface design:

public static class ConfigurationExtensions
{
    public static void HasPrimaryKey<TEntityType>(this EntityTypeConfiguration<TEntityType> configuration)
        where TEntityType : class, IEntity
    {
        var parameter = Expression.Parameter(typeof(TEntityType), "m");
        var keyProperty = Expression.Lambda<Func<TEntityType, int>>(Expression.Property(parameter, nameof(IEntity.Id)), parameter);
        configuration
            .HasKey(keyProperty)
            .Property(keyProperty)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
    }
}
like image 91
Ivan Stoev Avatar answered Mar 03 '26 18:03

Ivan Stoev



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!