简体   繁体   English

实体框架:从具有未知类型列的通用表中读取数据

[英]Entity Framework: read data from generic table with unknown type column

I am new to Entity Framework, and in my project I have to perform a generic query to retrieve the maximum value of a db column.我是 Entity Framework 的新手,在我的项目中,我必须执行通用查询来检索 db 列的最大值。 Let's say that this is my main method:假设这是我的主要方法:

long GetMaxValue(string tableName, string columnName)
{
    // code here
}

and the SQL equivalent is literally和 SQL 等价的字面意思是

SELECT MAX(<columnName>) FROM <tableName>

I have created a GenericDbEntity class which contains only a NumericColumn property representing this column, and in the implementation of OnModelCreating inside my DbContext I have this:我创建了一个GenericDbEntity类,它只包含一个表示该列的NumericColumn属性,并且在我的OnModelCreating内的DbContext的实现中,我有这个:

modelBuilder.Entity<GenericDbEntity>().ToTable(tableName).HasNoKey();
modelBuilder.Entity<GenericDbEntity>().Property(e => e.NumericColumn)
                                      .HasColumnName(columnName)
                                      .HasConversion(p => (?)p, (? p) => Convert.ToInt64(p));

The question marks inside HasConversion are because I don't actually know which is the db type of that column (I only know it's numeric), but I want to convert it to long , since that's the data type of GenericDbEntity.NumericColumn . HasConversion中的问号是因为我实际上不知道该列的 db 类型(我只知道它是数字),但我想将其转换为long ,因为那是GenericDbEntity.NumericColumn的数据类型。 I tried to replace the question marks with object , but it says:我试图用object替换问号,但它说:

The property 'GenericDbEntity.NumericColumn' is of type 'object' which is not supported by the current database provider.属性“GenericDbEntity.NumericColumn”属于“对象”类型,当前数据库提供程序不支持。 Either change the property CLR type, or ignore the property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.更改属性 CLR 类型,或使用“[NotMapped]”属性或使用“OnModelCreating”中的“EntityTypeBuilder.Ignore”忽略该属性。

Is there an easy way to achieve this?有没有简单的方法来实现这一目标?

PS: I tried also to use dynamic , both in HasConversion and as data type of NumericColumn , but it didn't help. PS:我也尝试在HasConversionNumericColumn的数据类型中使用dynamic ,但这没有帮助。

Edit: The called compiled query is this one:编辑:调用的编译查询是这个:

private static readonly Func<DatabaseContext, long> getMaxValue =
    EF.CompileQuery((DatabaseContext context) =>
                        context.GenericEntity
                               .Max(e => e.NumericColumn));

Added solution which generates LINQ query for particular table and column.添加了为特定表和列生成 LINQ 查询的解决方案。 For making it working you have to REMOVE HasConversion because properties with converters cannot be used in LINQ queries.为了使其正常工作,您必须删除HasConversion ,因为不能在 LINQ 查询中使用带有转换器的属性。

For example for call例如调用

var maxValue = context.GetMaxValue("Persons", "id");

Will be generated and executed the following query:将生成并执行以下查询:

var maxValue = (long)context.Set<Person>().Max(e => e.Id);

And implementation和实施

public static class QueryableExtensions
{
    public static long GetMaxValue(this DbContext ctx, string tableName, string columnName)
    {
        var model = ctx.Model;

        var entityType = model.GetEntityTypes().FirstOrDefault(et => tableName.Equals(et.GetTableName(), StringComparison.InvariantCultureIgnoreCase));

        if (entityType == null)
            throw new InvalidOperationException($"Entity for table '{tableName}' not found.");

        // GetColumnName() can be obsolete, it depends on EF Core version.
        var prop = entityType.GetProperties().FirstOrDefault(p => columnName.Equals(p.GetColumnName(), StringComparison.InvariantCultureIgnoreCase));

        if (prop == null)
            throw new InvalidOperationException($"Property for column '{tableName}'.'{columnName}' not found.");

        var entityParam = Expression.Parameter(entityType.ClrType, "e");
        var ctxParam = Expression.Parameter(typeof(DbContext), "ctx");

        // ctx.Set<entityType>()
        var setQuery = Expression.Call(ctxParam, nameof(DbContext.Set), new[] {entityType.ClrType});

        Expression propExpression;
        if (prop.PropertyInfo == null)
        {
            // 'prop' is Shadow property, so call via EF.Property(e, "name")
            propExpression = Expression.Call(typeof(EF), nameof(EF.Property), new[] { prop.ClrType },
                entityParam, Expression.Constant(prop.Name));
        }
        else
        {
            propExpression = Expression.MakeMemberAccess(entityParam, prop.PropertyInfo);
        }

        // e => e.Prop
        var propLambda = Expression.Lambda(propExpression, entityParam);

        // ctx.Set<entityType>().Max(e => e.Prop)
        Expression maxCall = Expression.Call(typeof(Queryable), nameof(Queryable.Max),
            new[] { entityType.ClrType, prop.ClrType }, 
            setQuery, propLambda);

        // cast if needed
        if (maxCall.Type != typeof(long))
            maxCall = Expression.Convert(maxCall, typeof(long));

        // ctx => ctx.Set<entityType>().Max(e => e.Prop)
        var maxLambda = Expression.Lambda<Func<DbContext, long>>(maxCall, ctxParam);

        // probably we can cache such compiled retrieving function
        var func = maxLambda.Compile();

        return func(ctx);
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM