使用表达式树添加新的lambda表达式

3

我看了这里和网络上的许多帖子,但似乎没有一个能够帮助我。

我有一张表,大约有200万条记录,有超过200个列。

一个简单的Web服务允许用户从表中提取特定数量的列,用户可以选择要提取哪些列。

结果需要是逗号分隔值的字符串,因此我的查询需要提取请求的列并返回连接字符串。

我已经使用ADO.NET和纯SQL查询完成了这个任务,它工作得很好,但是我被要求在Entity Framework中完成它。

以下是我已经做过的事情。

我将请求的列列表作为字符串数组获取。

以下是我的查询,不确定是否是最佳解决方案或想法,因此在这里寻求帮助。

var valueList2 = ctx.mytable.Where(x => x.pcds == comValue).Select(x => new{temp = x.column1 +", "+ x.column2}).Select(x => x.temp).ToList();

上面的代码给我提供了由逗号分隔的两列字符串,我需要将列名的数组推送到其中的lambda部分。我尝试过以下方法但发现它只适用于特定类型的类而不是匿名类,而且我无法想出如何在多个列中使用它而不使其变得太复杂。
var createdType = typeof(mytable);
var Param = Expression.Parameter(typeof(string), "pr");
var obj = Expression.New(createdType);
var ValueProperty = createdType.GetProperty("long");
var ValueAssignment = Expression.Bind(ValueProperty, Param);
var memberInit = Expression.MemberInit(obj, ValueAssignment);
var lm = Expression.Lambda<Func<string, mytable>>(memberInit, Param);

Thank you


1
所以我认为这两种解决方案(字符串拼接 SQL 端和即时创建类型)都不是好的解决方案。遗憾的是,我不认为 EF 有一个“返回 IEnumerable<object[]>”的方法。那将是完美的解决方案。 - xanatos
1
类似问题:http://stackoverflow.com/questions/31658712/entity-framework-raw-sql-query-selecting-unknown-columns-unknown-return-type - xanatos
1
@JeremyThompson 表达式并不是过度的问题...问题在于EF不是解决这个问题的正确工具。这是一个经典问题,Ado.NET和DbDataReader完全可以胜任。EF不是为处理这些事情而构建的。使用NHibernate将会很容易(使用CriteriaAPI和投影到object[])。 - xanatos
1
@JeremyThompson 这里没有“完美”的解决方案...我将发布一个可能的解决方案... - xanatos
1
@Farshad NHibernate是另一种ORM,类似于Entity Framework,但可以说它是另一种信仰 :-) - xanatos
显示剩余16条评论
2个回答

1

我正在使用 Dynamic Linq源代码)。遗憾的是,关于如何使用它的文档很少 :-) 在一个有趣的回旋效应中,有一个“进化”的版本。这个回旋效应是因为生成动态类的代码是基于我的一个响应 :-) 剩下的代码似乎非常优美... 并且有一套完整的单元测试和代码样例!!!请注意,这个第二个库是第一个库的超集,所以你可能可以将许多示例应用于第一个库! :-)

我正在添加一些静态方法来将 Dynamic Linq 查询的结果转换为 IEnumerable<object[]>.... 示例代码:

using (var ctx = new Model1())
{
    var result = ctx.MyTable
        .Take(100)
        .SimpleSelect(new[] { "ID", "Col1", "Col2" })
        .ToObjectArray();

    foreach (var row in result)
    {
        Console.WriteLine(string.Join(", ", row));
    }
}

更复杂的例子:
var columnsNames = new[] { "SomeNullableInt32", "SomeNonNullableDateTimeColumn" };

// One for each column!
var formatters = new Func<object, string>[]
{
    x => x != null ? x.ToString() : null,
    x => ((DateTime)x).ToShortDateString()
};

var result = ctx.MyTable.Take(100).SimpleSelect(columnsNames).ToObjectArray();

foreach (var row in result)
{
    var stringRow = new string[row.Length];

    for (int i = 0; i < row.Length; i++)
    {
        stringRow[i] = formatters[i](row[i]);
    }

    Console.WriteLine(string.Join(", ", stringRow));
}

这些类... 其中一个 (SimpleSelect) 生成动态SQL Select,并“匿名化”字段名称。我这样做是因为对于每种返回类型,动态Linq都会在运行时生成一个类。除非程序结束,否则该类不会被卸载。通过将列匿名化(我将它们重命名为 Item1Item2Item3 等),我增加了相同类被重用的可能性。请注意,不同类型的列将生成不同的类!(int Item1, string Item2 的类与 int Item1, DateTime Item2 的类不同),另一个类 (ToObjectArray) 返回一个更容易解析的 IEnumerable<object[]>

public static class DynamicLinqTools
{
    private static ConcurrentDictionary<Type, Func<object, object[]>> Converters = new ConcurrentDictionary<Type, Func<object, object[]>>();

    public static IQueryable SimpleSelect(this IQueryable query, string[] fields)
    {
        // With a little luck, "anonymizing" the field names we should 
        // reduce the number of types created!
        // new (field1 as Item1, field2 as Item2)
        return query.Select(string.Format("new ({0})", string.Join(", ", fields.Select((x, ix) => string.Format("{0} as Item{1}", x, ix + 1)))));
    }

    public static IEnumerable<object[]> ToObjectArray(this IQueryable query)
    {
        Func<object, object[]> converter;

        Converters.TryGetValue(query.ElementType, out converter);

        if (converter == null)
        {
            var row = Expression.Parameter(typeof(object), "row");

            // ElementType row2;
            var row2 = Expression.Variable(query.ElementType, "row2");

            // (ElementType)row;
            var cast = Expression.Convert(row, query.ElementType);

            // row2 = (ElementType)row;
            var assign = Expression.Assign(row2, cast);

            var properties = query.ElementType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                .Where(x => x.CanRead && x.GetIndexParameters().Length == 0)
                .ToArray();

            // (object)row2.Item1, (object)row2.Item2, ...
            var properties2 = Array.ConvertAll(properties, x => Expression.Convert(Expression.Property(row2, x), typeof(object)));

            // new object[] { row2.Item1, row2.Item2 }
            var array = Expression.NewArrayInit(typeof(object), properties2);

            // row2 = (ElementType)row; (return) new object[] { row2.Item1, row2.Item2 }
            var body = Expression.Block(typeof(object[]), new[] { row2 }, assign, array);

            var exp = Expression.Lambda<Func<object, object[]>>(body, row);
            converter = exp.Compile();

            Converters.TryAdd(query.ElementType, converter);
        }

        foreach (var row in query)
        {
            yield return converter(row);
        }
    }
}

非常感谢,这似乎是EF中的解决方案。如果我可以访问SQL服务器,您会推荐使用此方法还是存储过程? - Farshad
@Farshad 啊啊啊...我无法选择bad1和bad2之间...通过eval在存储过程中进行的动态查询是bad2...这段代码是bad1... :-) - xanatos
非常感谢您的帮助。 - Farshad

0

这是一个简短而易懂的答案,适用于需要不同答案的人,但根据我们与@xanatos的讨论,它并不是最好的,因为它还返回所有需要在添加到字符串列表之前剪切的列。

List<string> valueList = new List<string>();
using (var ctx = new DataEntities1())
{
    var query = ctx.myTable.Where(x => x.pcds == scode).SingleOrDefault();

    foreach (var item in columnsArray)
    {
        valueList.Add(typeof(myTable).GetProperty(onsColumns[Convert.ToInt32(item)]).GetValue(query).ToString());
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接