Native Entity frameworks express exception when using Extension Method for Free API
I am using Entity Framework 6, Code-First with Fluent-API.
I have the following extension method to set a primary key on my objects:
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 each 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 am setting up using the Fluent-API in my DbContext
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new MyEntityConfiguration());
}
with a config class for an object:
public class MyEntityConfiguration : EntityTypeConfiguration<MyEntity>
{
public MyEntityConfiguration()
{
this.HasPrimaryKey()
}
}
Oddly enough, 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
This is weird because when I refactor the code in the extension method back to the constructor, for example:
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. What for? Framework error?
source to share
The error message is misleading. The actual problem is pretty trivial. EF only supports properties that have a property setter.
Since the expression m => m.Id
in your generic extension method binds to an Id
interface property IEntity
that does not have a setter (unlike an implementation class), EF does not consider it a valid property and throws an exception with a misleading property type message.
To fix this, just define a setter for the property Id
inside the interface:
public interface IEntity
{
int Id { get; set; }
}
It can also be resolved by creating the lambda expression manually using class methods Expression
, but I think it is easier to change the interface :) But here's the 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);
}
}
source to share