C# 使用lambda表达式将反射属性传递给.HasKey()

C# 使用lambda表达式将反射属性传递给.HasKey(),c#,lambda,entity-framework-6,ef-fluent-api,C#,Lambda,Entity Framework 6,Ef Fluent Api,我希望有一个灵活的实体DbContext,可以将我的数据库第一个模式放在一个嵌套类中。 如果每个表都有一个名为Id的列作为主键,那么下面的poc实际上可以工作。 但是如果主键不同,我需要指定主键 我可以使用Entity的[key]属性指定键,它实际上可以工作。 但我不想这样做,因为我想将实体代码与模式定义分开。 我宁愿编写一个自定义属性,并编写一些逻辑来检查自定义属性的列是否正确 通常我会使用entityTypeConfiguration.HasKey(MyTable=>MyTable.MyKe

我希望有一个灵活的实体DbContext,可以将我的数据库第一个模式放在一个嵌套类中。 如果每个表都有一个名为Id的列作为主键,那么下面的poc实际上可以工作。 但是如果主键不同,我需要指定主键

我可以使用Entity的
[key]
属性指定键,它实际上可以工作。 但我不想这样做,因为我想将实体代码与模式定义分开。 我宁愿编写一个自定义属性,并编写一些逻辑来检查自定义属性的列是否正确

通常我会使用
entityTypeConfiguration.HasKey(MyTable=>MyTable.MyKey)
将所需属性设置为主键。 但是我不知道如何编写正确的Lambda表达式来设置所需的属性,因为我使用的是泛型和反射

这是我的密码:

using MySql.Data.Entity;
using System;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
using System.Linq;

class MySchema // my database
{
    public class MyTable // a table inside mySchema
    {
        public int MyKey { get; set; } // a column inside myTable
        public string MyValue { get; set; } // another column inside myTable
    }
}

static class Program
{
    static void Main()
    {
        var myTable = new EfDataContext<MySchema>().Set<MySchema.MyTable>();
        Console.Write(myTable.Where(a => a.MyKey == 1).First().MyValue);
    }
}

[DbConfigurationType(typeof(MySqlEFConfiguration))] // using mysql
public class EfDataContext<Schema> : DbContext
{
    public EfDataContext() : base("db") { } // use connection string "db"
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); // let's assume my naming is like in the database

        foreach (Type table in typeof(Schema).GetNestedTypes())
        {
            // type table is now a variable, so we cannot use it as a generic like: this.createEntity<table>(modelBuilder);
            this.GetType().GetMethod("CreateEntity").MakeGenericMethod(table).Invoke(this, new object[] { modelBuilder });
        }
    }

    public void CreateEntity<Table>(DbModelBuilder modelBuilder) where Table : class
    {
        var entityTypeConfiguration = modelBuilder.Entity<Table>();

        // So far, so good!
        // But if there is no column named "Id", I need to tell EF which column is the primary key.
        // If I weren't using a generic type, I would that column MyKey is the primary key of table MyTable like this:
        // entityTypeConfiguration.HasKey(row => row.MyKey);
        // But since now I am using a generic type as the table, and propertyInfo appears to be the primary key, what do I say?

        foreach (var propertyInfo in typeof(Table).GetProperties())
        {
            if (propertyInfo.DeclaringType.Name == "MyTable" && propertyInfo.Name == "MyKey") // just a silly example for now
            {
                // this throws an InvalidOperationException: The properties expression 'row => value(EfDataContext`1+<>c__DisplayClass2_0`1[MySchema,MySchema+MyTable]).propertyInfo' is not valid. The expression should represent a property: C#: 't => t.MyProperty'  
                entityTypeConfiguration.HasKey(row => propertyInfo);
            }
        }
    }
}
使用MySql.Data.Entity;
使用制度;
使用System.Data.Entity;
使用System.Data.Entity.ModelConfiguration.Conventions;
使用System.Linq;
类MySchema//我的数据库
{
公共类MyTable//mySchema中的表
{
public int MyKey{get;set;}//myTable中的一列
公共字符串MyValue{get;set;}//myTable中的另一列
}
}
静态类程序
{
静态void Main()
{
var myTable=new EfDataContext().Set();
Write(myTable.Where(a=>a.MyKey==1.First().MyValue);
}
}
[DbConfigurationType(typeof(MySqlEFConfiguration))]//使用mysql
公共类EfDataContext:DbContext
{
public EfDataContext():base(“db”){}//使用连接字符串“db”
模型创建时受保护的覆盖无效(DbModelBuilder modelBuilder)
{
基于模型创建(modelBuilder);
modelBuilder.Conventions.Remove();//假设我的命名与数据库中的命名类似
foreach(typeof(Schema).GetNestedTypes()中的类型表)
{
//类型表现在是一个变量,所以我们不能将其作为泛型使用,比如:this.createEntity(modelBuilder);
this.GetType().GetMethod(“CreateEntity”).MakeGenericMethod(table).Invoke(这是新对象[]{modelBuilder});
}
}
public void CreateEntity(DbModelBuilder modelBuilder),其中Table:class
{
var entityTypeConfiguration=modelBuilder.Entity();
//到目前为止,一切都很好!
//但是如果没有名为“Id”的列,我需要告诉EF哪个列是主键。
//如果我没有使用泛型类型,我希望列MyKey是表MyTable的主键,如下所示:
//entityTypeConfiguration.HasKey(row=>row.MyKey);
//但是现在我使用泛型类型作为表,propertyInfo似乎是主键,我该怎么说?
foreach(typeof(Table.GetProperties()中的var propertyInfo)
{
if(propertyInfo.DeclaringType.Name==“MyTable”&&propertyInfo.Name==“MyKey”)//现在只是一个愚蠢的例子
{
//这引发了InvalidOperationException:属性表达式“row=>value(EfDataContext`1+c\uu DisplayClass2\u 0`1[MySchema,MySchema+MyTable])。propertyInfo”无效。该表达式应表示一个属性:c#::“t=>t.MyProperty”
entityTypeConfiguration.HasKey(行=>propertyInfo);
}
}
}
}

您可以创建以下扩展方法

public static class ConfigurationExtensions
{
    public static EntityTypeConfiguration<T> HasKey<T>(this EntityTypeConfiguration<T> config, PropertyInfo property) where T : class
    {
        //entity type
        ParameterExpression parameter = Expression.Parameter(typeof(T));
        //entity.key
        Expression prop = Expression.Property(parameter, property.Name);

         //entity => entity.key
        var lambda = Expression.Lambda(prop, parameter);

        MethodInfo hasKeyMethod = typeof(EntityTypeConfiguration<T>)
            .GetMethods(BindingFlags.Public | BindingFlags.Instance)
            .Where(m => m.Name == nameof(EntityTypeConfiguration<T>.HasKey)
                && m.GetParameters().Count() == 1)
            .First()
            .MakeGenericMethod(property.PropertyType);

        return (EntityTypeConfiguration<T>)hasKeyMethod.Invoke(config, new object[] { lambda });
    }
}

您可以创建以下扩展方法

public static class ConfigurationExtensions
{
    public static EntityTypeConfiguration<T> HasKey<T>(this EntityTypeConfiguration<T> config, PropertyInfo property) where T : class
    {
        //entity type
        ParameterExpression parameter = Expression.Parameter(typeof(T));
        //entity.key
        Expression prop = Expression.Property(parameter, property.Name);

         //entity => entity.key
        var lambda = Expression.Lambda(prop, parameter);

        MethodInfo hasKeyMethod = typeof(EntityTypeConfiguration<T>)
            .GetMethods(BindingFlags.Public | BindingFlags.Instance)
            .Where(m => m.Name == nameof(EntityTypeConfiguration<T>.HasKey)
                && m.GetParameters().Count() == 1)
            .First()
            .MakeGenericMethod(property.PropertyType);

        return (EntityTypeConfiguration<T>)hasKeyMethod.Invoke(config, new object[] { lambda });
    }
}

通常,主键是实体中的第一个元素。不确定这是否适用于您的场景,但这是一个相当安全的赌注。通常主键是实体中的第一个元素。不确定这是否适用于您的场景,但这是一个相当安全的赌注。