问题
This is just selection of some columns from Cars:
var qs = myDataContext.Cars
.Select(c => new { c.id, c.color })
.ToList();
What I need is function, that would do the same, but via SqlCommand, so I can alter the process. Its (simplified) code is here
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
}
The function works well for ordinary classes like this
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();
But It fails on anonymous types
var qa = myDataContext.Cars
.ProjectionFunction(c => new { c.id, c.color })
.ToList();
I get runtime error
The type <>f__AnonymousType20`2[System.Nullable`1[System.Int32],System.String] must declare a default (parameterless) constructor in order to be constructed during mapping.
for Translate<> function. The ExecuteQuery<> I tried too does the same. It is hard to believe that DataContext does not know how to build anonymous type, it is what he does all the time. What am I missing? How can I make him do that for me?
The problem with separate one use class is, that its properties have to be explicitly synchronized with types and names of properties of the original class, which makes this approach somewhat impractical.
回答1:
I would still like to know if there is a way to make DataContext translate to anonymous types, but it well may be, that it does not expose this behaviour. In that case, another materializer has to be used. In the end, it was not that difficult to write one, so I shared it in case someone got interested. The only way to initialize anonymous type is via new operator. Fortunately expression can be built to do that in runtime.
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);
}
}
}
来源:https://stackoverflow.com/questions/42406032/datacontext-translate-for-anonymous-types