I made a version that works with LINQ-to-SQL; essentially it uses a
generic tuple as an intermediary, and does the LINQ-to-SQL projection
into the tuple, then a LINQ-to-objects projection from the tuple back
into the type. I'm not saying it is perfect, but it works...
My comments (other chain) about the risk of data loss still stand...
and validation of a partial object might be, erm, interesting - but...
Anyways: double-projection version (not tidied or optimised etc)
follows;
Marc
public static class QueryExt {
public static IQueryable<T> Select<T>(this IQueryable<T>
source, params string[] propertyNames)
where T : new()
{
if (source == null) throw new
ArgumentNullException("source");
if (propertyNames == null) throw new
ArgumentNullException("propertyNames");
Type sourceType = typeof(T), tupleType =
typeof(Tuple<,,,,,,,,,>);
Type[] tupleArgs = tupleType.GetGenericArguments();
if (propertyNames.Length > tupleArgs.Length)
{
throw new NotSupportedException("Too many properties
selected; max " + tupleArgs.Length.ToString());
}
PropertyInfo[] sourceProps =
Array.ConvertAll(propertyNames, name => sourceType.GetProperty(name));
for(int i = 0; i < sourceProps.Length; i++) {
tupleArgs = sourceProps.PropertyType;
}
for(int i = sourceProps.Length; i < tupleArgs.Length; i++)
{
tupleArgs = typeof(byte); // use for any surplus
type-args
}
tupleType = tupleType.MakeGenericType(tupleArgs);
PropertyInfo[] tupleProps = new
PropertyInfo[sourceProps.Length];
for(int i = 0; i < tupleProps.Length; i++) {
tupleProps = tupleType.GetProperty("Value" +
i.ToString());
}
ParameterExpression sourceItem =
Expression.Parameter(sourceType, "t");
MemberBinding[] bindings = new
MemberBinding[sourceProps.Length];
for (int i = 0; i < sourceProps.Length; i++)
{
bindings = Expression.Bind(tupleProps,
Expression.Property(sourceItem, sourceProps));
}
Expression body =
Expression.MemberInit(Expression.New(tupleType.GetConstructor(Type.EmptyTypes))
, bindings);
object result = typeof(QueryExt).GetMethod("SelectUnwrap",
BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod(
typeof(T), tupleType).Invoke(null, new object[]
{source, body, sourceItem, sourceProps, tupleProps});
return (IQueryable<T>) result;
}
static IQueryable<T> SelectUnwrap<T, TTuple>(IQueryable<T>
source, Expression select, ParameterExpression itemParam,
PropertyInfo[] sourceProps, PropertyInfo[] tupleProps)
where T : new() where TTuple : new() {
var items = source.Select(Expression.Lambda<Func<T,
TTuple>>(select, itemParam)).AsEnumerable();
MemberBinding[] bindings = new
MemberBinding[sourceProps.Length];
ParameterExpression tupleItem =
Expression.Parameter(typeof(TTuple), "t");
for (int i = 0; i < sourceProps.Length; i++)
{
bindings = Expression.Bind(sourceProps,
Expression.Property(tupleItem, tupleProps));
}
Expression body =
Expression.MemberInit(Expression.New(typeof(T).GetConstructor(Type.EmptyTypes))
, bindings);
Func<TTuple,T> projection =
Expression.Lambda<Func<TTuple,T>>(body, tupleItem).Compile();
return items.Select(projection).AsQueryable();
}
}
sealed class Tuple<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9>
{
public T0 Value0 { get; set; }
public T1 Value1 { get; set; }
public T2 Value2 { get; set; }
public T3 Value3 { get; set; }
public T4 Value4 { get; set; }
public T5 Value5 { get; set; }
public T6 Value6 { get; set; }
public T7 Value7 { get; set; }
public T8 Value8 { get; set; }
public T9 Value9 { get; set; }
// extend at will...
}