实体框架:带有未知类型列的通用表中的数据

发布于 2025-02-06 04:35:50 字数 1689 浏览 4 评论 0原文

我是实体框架的新手,在我的项目中,我必须执行通用查询以检索数据库列的最大值。假设这是我的主要方法:

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

从字面上看,SQL等效是

SELECT MAX(<columnName>) FROM <tableName>

我创建了一个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));

hasconversion内的问号是因为我实际上不知道该列的DB类型是哪个DB类型(我只知道它是数字),但是我想将其转换为long,因为那是genericdbentity.numericcolumn的数据类型。我试图用对象替换问号,但它说:

属性“ genericdbentity.numericcolumn”是类型为“对象”,当前数据库提供商不支持。要么更改属性CLR类型,要么使用“ [notmapped]''属性或使用“ enmodeLcreating”中的'entityTypebuilder.ignore'忽略属性。

有什么简单的方法可以实现这一目标吗?

PS:我还尝试使用Dynamic,包括hasConversion和作为numericColumn的数据类型,但这无济于事。

编辑:所谓的编译查询是这个:

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

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. Let's say that this is my main method:

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

and the SQL equivalent is literally

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:

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. I tried to replace the question marks with object, but it says:

The property 'GenericDbEntity.NumericColumn' is of type 'object' which is not supported by the current database provider. Either change the property CLR type, or ignore the property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.

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.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

梦冥 2025-02-13 04:35:51

添加了为特定表和列生成LINQ查询的解决方案。为了使其正常工作,您必须删除 hasConversion,因为带有转换器的属性在LINQ查询中无法使用。

例如,

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

将生成并执行以下查询:

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

实现

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

Added solution which generates LINQ query for particular table and column. For making it working you have to REMOVE HasConversion because properties with converters cannot be used in LINQ queries.

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(
quot;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(
quot;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);
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文