DataContext Translate<>对于匿名类型

时间:2017-02-23 02:38:07

标签: c# linq linq-to-sql projection anonymous-types

这只是从汽车中选择一些列:

var qs = myDataContext.Cars
    .Select(c => new { c.id, c.color })
    .ToList();

我需要的是功能,它会做同样的事情,但通过SqlCommand,所以我可以改变这个过程。其(简化)代码在这里

public static IEnumerable<P> ProjectionFunction<T, P>(
    this System.Data.Linq.Table<T> input,
    Func<T, P> projection
    ) where T : class
{
    System.Data.Linq.DataContext dc = input.Context;

    string paramList = string.Join(
        ",",
        typeof(P).GetProperties().Select(s => s.Name)
        );

    System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand(
       "SELECT " + paramList + " FROM " + typeof(T).Name,
       (System.Data.SqlClient.SqlConnection)dc.Connection
       );

    cmd.CommandType = CommandType.Text;

    if (cmd.Connection.State == ConnectionState.Closed)
    {
        cmd.Connection.Open();
    }

    return dc.Translate<P>(cmd.ExecuteReader()); // <-- the problem is here
}

该函数适用于像这样的普通类

private class X
{
    public int? id { get; set; }
    public string color { get; set; }
}

var qx = myDataContext.Cars
    .ProjectionFunction(c => new X () { id = c.id, color = c.color })
    .ToList();

但它在匿名类型上失败

var qa = myDataContext.Cars
    .ProjectionFunction(c => new { c.id, c.color })
    .ToList();

我收到运行时错误

  

类型   &LT;&GT; f__AnonymousType20`2 [System.Nullable`1 [System.Int32],System.String]   必须声明一个默认(无参数)构造函数   在绘图过程中构建。

表示Translate<>功能。我尝试的ExecuteQuery<>也是如此。很难相信DataContext不知道如何构建匿名类型,这是他一直在做的事情。我错过了什么?我怎么能让他这样做呢?

单独使用一个类的问题是,它的属性必须与原始类的属性的类型和名称显式同步,这使得这种方法有点不切实际。

1 个答案:

答案 0 :(得分:0)

我仍然想知道是否有办法让DataContext转换为匿名类型,但很可能,它不会暴露这种行为。在这种情况下,必须使用另一种材料。最后,编写一个并不困难,所以我分享它以防有人感兴趣。初始化匿名类型的唯一方法是通过new运算符。幸运的是,可以在运行时构建表达式。

private static Func<object[], P> getMaterializer<P>(
    System.Reflection.PropertyInfo[] props, IEnumerable<string> propertyNames)
{
    Type[] propertyTypes = props.Select(p => p.PropertyType).ToArray();
    ParameterExpression arrExpr = Expression.Parameter(typeof(object[]));
    var constructor = typeof(P).GetConstructor(propertyTypes);

    if (constructor == null || !constructor
        .GetParameters()
        .Select(p => p.Name)
        .SequenceEqual(propertyNames))
    {
        return null;
    }

    Expression[] paramExprList = propertyTypes.Select((type, i) =>
    {
        Expression ei = Expression.ArrayIndex(arrExpr, Expression.Constant(i));

        if (type.IsGenericType || type == typeof(string))
        {
            return (Expression)Expression.Condition(
                Expression.Equal(ei, Expression.Constant(DBNull.Value)),
                Expression.Convert(Expression.Constant(null), type),
                Expression.Convert(ei, type)
                );
        }
        else
        {
            return Expression.Convert(ei, type);
        }
    }).ToArray();

    return Expression.Lambda<Func<object[], P>>(
        Expression.New(constructor, paramExprList),
        arrExpr
        ).Compile();
}


private static System.Collections.Concurrent.ConcurrentDictionary<Type, Tuple<string, string, object>> cachedProjections =
    new System.Collections.Concurrent.ConcurrentDictionary<Type, Tuple<string, string, object>>();

private static Tuple<string, string, object> getProjection<T, P>()
{
    Type typeP = typeof(P);
    Tuple<string, string, object> projection;

    if (!cachedProjections.TryGetValue(typeP, out projection))
    {
        Type typeT = typeof(T);
        System.Reflection.PropertyInfo[] props = typeP.GetProperties();
        List<string> propertyNames = props.Select(p => p.Name).ToList();

        projection = new Tuple<string, string, object>(
            string.Join(",", propertyNames),
            typeT.Name,
            typeT == typeP ? null : getMaterializer<P>(props, propertyNames)
            );

        cachedProjections.TryAdd(typeP, projection);
    }

    return projection;
}

private static IEnumerable<P> Materialize<P>(SqlCommand cmd,
    Func<object[], P> materializer)
{
    using (var reader = cmd.ExecuteReader(CommandBehavior.CloseConnection))
    {
        object[] obj = new object[reader.VisibleFieldCount];

        while (reader.Read())
        {
            reader.GetValues(obj);
            yield return materializer(obj);
        }
    }
}

public static IEnumerable<P> ProjectionFunction<T, P>(
    this System.Data.Linq.Table<T> input,
    Func<T, P> projectionFunction
 ) where T : class
{
    var projection = getProjection<T, P>();

    using (SqlCommand cmd = new SqlCommand(
       "SELECT " + projection.Item1
       + " FROM " + projection.Item2,
       new SqlConnection(input.Context.Connection.ConnectionString)
       ))
    {
        cmd.CommandType = CommandType.Text;
        cmd.Connection.Open();

        var materializer = (Func<object[], P>)projection.Item3;
        if (materializer == null)
        {
            return input.Context.Translate<P>(cmd.ExecuteReader(CommandBehavior.CloseConnection));
        }
        else
        {
            return Materialize(cmd, materializer);
        }
    }
}