Generic constraints and Expression type evaluation

2019-08-04 20:24发布

问题:

This is based on a previous question I had: EF Code First implemented interface property

I have something like this.

interface IKeywordedEntity
{
    ICollection<Keyword> Keywords { get; }
}
class Foo : EntityBase, IKeywordedEntity
{
     public virtual ICollection<Keyword> Keywords { get { ... } }
}

The details of EntityBase aren't important.

Initially I wrote this extension method to keep things DRY:

public static void WithKeywords<TEntityType>(this EntityTypeConfiguration<TEntityType> 
    entityTypeConfiguration) where TEntityType : EntityBase, IKeywordedEntity
{
    entityTypeConfiguration.HasMany(e => e.Keywords).WithMany();
}

I would invoke it like so:

modelBuilder.Entity<Foo>.WithKeywords();

However, Entity Framework or the C# compiler is treating e in the lambda as IKeywordedEntity not TEntityType. This freaks out Entity Framework.

Knowing this, I experimented with manually writing the lambda as an Expression for HasMany. I came up with the following:

public static void WithKeywords<TEntityType>(this EntityTypeConfiguration<TEntityType> 
    entityTypeConfiguration) where TEntityType : EntityBase, IKeywordedEntity
{
    var rootExpression = Expression.Parameter(typeof (TEntityType));
    var expression = Expression.Property(rootExpression, "Keywords");

    entityTypeConfiguration.HasMany(Expression.Lambda<Func<TEntityType, ICollection<Keyword>>>(expression, rootExpression)).WithMany();
}

Now the IntelliSense is showing me the combination of EntityBase and IKeywordedEntity correctly and telling me that e is TEntityType.

My question is: why does the passed/compiled expression tree from the lambda treat e as IKeywordedEntity, not TEntityType?

回答1:

I'm no expert on Expressions and Lambdas, but this is what I think is going on.

When we just specify a lambda to convert it to an expression tree, the compiler does the work of converting things to the tree. In the method body all the compiler can see is IKeyworkedEntity.Keywords not TEntityType.Keywords and it uses the explicit property name i.e. uses the following

var rootExpression = Expression.Parameter(typeof (IKeyworkedEntity));
var expression = Expression.Property(rootExpression, "Keywords");

That is causing the issue which we see. Now if we build the expression tree ourselves, we know that there is a property on the TEntityType and we are saying so in the tree. This causes the migration to go through since it sees the property on a class and not on an interface.



标签: c# .net lambda