asp.net 使用实体框架在运行时动态选择列

jtoj6r0c  于 2023-10-21  发布在  .NET
关注(0)|答案(6)|浏览(107)

我有一个这样的现有函数

public int sFunc(string sCol , int iId)
{
    string sSqlQuery = "  select  " + sCol + " from TableName where ID = " +  iId ;
    // Executes query and returns value in column sCol
}

该表有四列存储整数值,我阅读他们分别使用上述功能。
现在我把它转换成实体框架。

public int sFunc(string sCol , int iId)
{
     return Convert.ToInt32(TableRepository.Entities.Where(x => x.ID == iId).Select(x => sCol ).FirstOrDefault());
}

但是上面的函数返回一个错误
输入字符串格式不正确
因为它返回列名本身。
我不知道如何解决这个问题,因为我是EF的新手。
任何帮助都将不胜感激
谢谢你

7d7tgy0s

7d7tgy0s1#

对8年后的OP没有用处,但是这个问题有很多观点,所以我认为对其他人来说有一个合适的答案可能会有所帮助。
如果使用Entity Framework,则应该执行Linq投影(Select()),因为这会在数据库端产生正确、有效的查询,而不是拉入整个实体。
但是,使用Linq Select(),通常必须提供一个lambda,因此将列/属性名称放在字符串中是这里的主要困难。
最简单的解决方案是使用动态LINQ(EntityFramework.DynamicLinq Nuget package)。这个包提供了原始Linq方法的替代方案,原始Linq方法将字符串作为参数,并将这些字符串转换为适当的表达式。
范例:

async Task<int> GetIntColumn(int entityId, string intColumnName)
{
    return await TableRepository.Entities
        .Where(x => x.Id == entityId)
        .Select(intColumnName) // Dynamic Linq projection
        .Cast<int>()
        .SingleAsync();
}

我还将其转换为async调用,因为现在所有的数据库调用都应该异步执行。当你调用这个方法时,你必须await它才能得到结果(即:var res = await GetIntColumn(...);)。

泛型变体

可能在IQueryable上将其更改为扩展方法更有用,并将列/属性类型更改为泛型类型参数,因此您可以将其与任何列/属性一起使用:

  • (前提是您为所有实体提供了一个指定Id属性的公共接口。
public static async Task<TColumn> GetColumn<TEntity, TColumn>(this IQueryable<TEntity> queryable, int entityId, string columnName)
    where TEntity : IEntity
{
    return await queryable
        .Where(x => x.Id == entityId)
        .Select(columnName) // Dynamic Linq projection
        .Cast<TColumn>()
        .SingleAsync();
}

这是这样叫的:var result = await TableRepository.Entities.GetColumn<Entity, int>(id, columnName);

接受列列表的通用变体

您可以进一步扩展它以支持动态选择多个列:

public static async Task<dynamic> GetColumns<TEntity>(this IQueryable<TEntity> queryable, int entityId, params string[] columnNames)
    where TEntity : IEntity
{
    return await queryable
        .Where(x => x.Id == entityId)
        .Select($"new({string.Join(", ", columnNames)})")
        .Cast<dynamic>()
        .SingleAsync();
}

这是这样叫的:var result = await TableRepository.Entities.GetColumns(id, columnName1, columnName2, ...);
由于返回类型及其成员在编译时是未知的,我们必须在这里返回dynamic。这使得处理结果很困难,但是如果您只想序列化它并将其发送回客户端,那么这是可以实现的。

epfja78i

epfja78i2#

这可能有助于解决您的问题:

public int sFunc(string sCol, int iId)
{
    var _tableRepository = TableRepository.Entities.Where(x => x.ID == iId).Select(e => e).FirstOrDefault();
    if (_tableRepository == null) return 0;

    var _value = _tableRepository.GetType().GetProperties().Where(a => a.Name == sCol).Select(p => p.GetValue(_tableRepository, null)).FirstOrDefault();

    return _value != null ? Convert.ToInt32(_value.ToString()) : 0;
}

这个方法现在可以动态输入方法参数sCol

更新:这不是在当前问题的上下文中,而是一般如何使用表达式选择动态列:

var parameter = Expression.Parameter(typeof(EntityTable));
var property = Expression.Property(parameter, "ColumnName");
//Replace string with type of ColumnName and entity table name.
var selector = Expression.Lambda<Func<EntityTable, string>>(property, parameter);

//Before using queryable you can include where clause with it. ToList can be avoided if need to build further query.
var result = queryable.Select(selector).ToList();
ctrmrzij

ctrmrzij3#

你必须尝试使用动态LINQ。详细信息为HERE

dfuffjeb

dfuffjeb4#

多年来,我们一直在为实体框架6而奋斗。在我们的例子中,我们有300列的数据网格,EF 6用2000行代码生成SQL查询。我们也尝试了与下面描述的相同的优化,但我们最终的性能更差。
EF Core带来了巨大的提升(我们在EF Core 7上进行了测试)。
我们的查询通常是这样构建的:

db.MyDbSet
   .GetOverviewQuery()
   .SelectColumns(selectedColumnNames)
   .ToArrayAsync(...);

其中方法GetOverviewQuery()包含巨大的LINQ选择返回IQueryable。TResult只是一个没有逻辑的DTO类。
我们引入了一个方法SelectColumns(),它从DTO类接收一个选定属性名的列表。

private static IQueryable<TResult> SelectColumns<TResult>(this IQueryable<TResult> source, HashSet<string> selectedColumnNames)
        {
            var props = GetProperties<TResult>(selectedColumnNames);
            var sourceType = source.ElementType;
            var resultType = typeof(TResult);
            var parameter = Expression.Parameter(sourceType, "e");
            var bindings = props
                .Select(prop => Expression.Bind(prop, Expression.PropertyOrField(parameter, prop.Name)));
            var body = Expression.MemberInit(Expression.New(resultType), bindings);
            var selector = Expression.Lambda(body, parameter);
            return source.Provider.CreateQuery<TResult>(
                Expression.Call(typeof(Queryable), "Select", new Type[] { sourceType, resultType },
                    source.Expression, Expression.Quote(selector)));
        }
        
        private static IEnumerable<PropertyInfo> GetProperties<TItem>(HashSet<string> selectedColumnNames)
        {
            return typeof(TItem)
                // Make sure you can use only valid columns with setter
                .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty)
                .Where(p => p.CanWrite)
                .Where(p => !selectedColumnNames.Contains(p.Name));
        }

最后,它减少了最终的SQL大小,因为EF Core删除了所有不在我们的列选择中的列。这种优化也嵌套了选择和联接。例如,如果您只选择10列,则所有列的原始查询将从2000行SQL减少到30行,其中只考虑必要的数据。这将增强表达式树的构建以及SQL服务器查询。
我希望我们能在我们的项目上更早地使用EF Core:D希望它能有所帮助:)

gzszwxb4

gzszwxb45#

不要将字符串列名作为参数传递,而是尝试传入一个lambda表达式,如:

sFunc(x => x.FirstColumnName, rowId);
sFunc(x => x.SecondColumnName, rowId);
...

这将在最后给予您对列名的智能感知,因此您可以避免在列名键入错误时可能出现的错误。
更多关于这里:C# Pass Lambda Expression as Method Parameter
但是,如果您必须保持相同的方法签名,即为了支持其他/遗留代码,那么你可以试试这个:

public string sFunc(string sCol , int iId)
{
    return TableRepository.Entities.Where(x => x.ID == iId).Select(x => (string) x.GetType().GetProperty(sCol).GetValue(x)});
}

你可能需要调整一下,我没有一个快速的测试方法。

ig9co6j1

ig9co6j16#

你可以这样做:

var entity = _dbContext.Find(YourEntity, entityKey);
        // Finds column to select or update
        PropertyInfo propertyInfo = entity.GetType().GetProperty("TheColumnVariable");

相关问题