Передайте отраженное свойство в .HasKey (), используя лямбда-выражение - PullRequest
0 голосов
/ 12 февраля 2019

Я хочу иметь гибкий Entity DbContext таким образом, чтобы я мог поместить свою схему, основанную на базе данных, в один вложенный класс.Этот документ ниже действительно работает, если в каждой таблице есть столбец с именем Id, выступающий в качестве первичного ключа.Но если первичный ключ отличается, мне нужно указать первичный ключ.

Я мог бы указать ключ, используя атрибут [Key] объекта Entity, и он на самом деле работал бы.Но я не хочу этого делать, потому что я хочу отделить код Entity от определения схемы.Я предпочел бы написать пользовательский атрибут и написать некоторую логику, которая проверяет наличие столбцов для пользовательского атрибута.

Обычно я бы использовал entityTypeConfiguration.HasKey(MyTable => myTable.MyKey);, чтобы установить желаемое свойство в качестве первичного ключа.Но я не знаю, как написать правильное лямбда-выражение, чтобы установить желаемое свойство, потому что я использую обобщения и отражения.

Вот мой код:

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);
            }
        }
    }
}

1 Ответ

0 голосов
/ 13 февраля 2019

Вы можете создать следующий метод расширения

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 });
    }
}

И использовать его

entityTypeConfiguration.HasKey(propertyInfo);
...