La domanda è simile a questa , ma la risposta non fornisce due cose fondamentali per me:
Voglio scrivere query come questa:
this.context.User
.Where(t => t.Id > 10)
.SelectCustom(t => t.Address.Country.Title)
.OrderBy(t => t.DisplayName)
.Skip(10).Take(5);
Con risposta nel link fornito, arrivo così lontano:
public class SelectList<TSource>
{
private List<MemberInfo> members = new List<MemberInfo>();
public SelectList<TSource> Add<TValue>(Expression<Func<TSource, TValue>> selector)
{
var member = ((MemberExpression)selector.Body).Member;
members.Add(member);
return this;
}
public Expression<Func<TSource, TResult>> ToDynamicColumns()
{
return this.members.??????????;
}
}
public static IQueryable<T> SelectCustom<T>(this IQueryable<T> query, Expression<Func<TSource, TKey>> FirstAdditional = null)
{
var columns = new SelectList<T>();
columns.Add(t => t.Id);
columns.Add(t => t.DisplayName)
if (FirstAdditional != null)
columns.Add(FirstAdditional);
return query.Select(columns.ToDynamicColumns);
}
Questo può essere fatto con EF Core 2.0?
Si potrebbe fare ciò con Expression.ListInit, qui il TResult
deve avere un metodo di istanza Add
, ad esempio Dictionary<string, object>
. Questo potrebbe funzionare ma non l'ho nemmeno compilato. In ogni caso questo dovrebbe dare abbastanza indicazioni su come costruirlo nel modo desiderato.
public Expression<Func<TSource, Dictionary<string, object>>> ToDynamicColumns()
{
var addMethod = typeof(TResult).GetMethod("Add");
var paramX = Expression.Parameter(typeof(TSource), "e");
var bindings =
this.members.Select (
member =>
return Expression.ElementInit(
addMethod,
Expression.Constant(mem.Name),
Expression.Convert(Expression.Property(paramX, member), typeof(object))
);
)
var listInit = Expression.ListInit(
Expression.New(typeof(TResult)),
bindings
);
return Expression.Lambda<Func<TSource, Dictionary<string, object>>(
listInit,
paramX
);
}
EF esaminerà le operazioni di invocazione lambda come se il corpo di quell'espressione fosse allineato. Quindi consiglierei di lasciare da sole le espressioni di origine e di generare espressioni per invocarle.
Inoltre vorrei mantenere semplice il tipo di risultato e restituire ogni riga come una matrice di oggetti. Ciò dovrebbe comportare un sovraccarico minore rispetto alla creazione di molti dizionari. Se è necessario accedere ai campi per nome, è necessario creare un singolo dizionario per mantenere la relazione tra nomi e numeri di colonna.
public class SelectList<TSource>
{
private List<LambdaExpression> members = new List<LambdaExpression>();
public SelectList<TSource> Add<TValue>(Expression<Func<TSource, TValue>> selector)
{
members.Add(selector);
return this;
}
public Expression<Func<TSource, TResult>> ToDynamicColumns()
{
var parameter = Expression.Parameter(typeof(TSource), "e");
return Expression.Lambda<Func<TSource, object[]>>(
Expression.NewArrayInit(
typeof(object),
members.Select(m =>
Expression.Convert(Expression.Invoke(m, parameter), typeof(object))
)
),
parameter);
}
}
Sebbene nel tuo caso, dal momento che stai scrivendo un metodo di estensione per restituire solo gli stessi dettagli chiave e un singolo campo aggiuntivo, potresti probabilmente definire un singolo tipo generico per contenere i risultati ed evitare qualsiasi confusione con le espressioni Linq;
public class UserResult<V>{
public int Id { get; set; }
public string DisplayName { get; set; }
public V Value { get; set; }
}
public static IQueryable<UserResult<V>> SelectCustom<V>(this IQueryable<User> query, Expression<Func<User, V>> ValueGetter)
{
return query.Select(u => new UserResult<V>{
Id = u.Id,
DisplayName = u.DisplayName,
Value = ValueGetter(u)
});
}
Beh, quasi, se c # ti consentisse di compilare una chiamata di Expression<Delegate>
dall'interno di un'altra. Invece possiamo implementare un ExpressionVisitor per scartare qualsiasi chiamata alla compilazione;
public class DontCompile : ExpressionVisitor
{
protected override Expression VisitMember(MemberExpression node)
{
// Inline any lambda arguments that are expressions
if (node.Expression is ConstantExpression lambdaArgs
&& node.Member is FieldInfo field
&& typeof(Expression).IsAssignableFrom(field.FieldType))
return (Expression)field.GetValue(lambdaArgs.Value);
return base.VisitMember(node);
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
// Don't compile lambda expressions
if (node.Method.Name == "Compile"
&& typeof(LambdaExpression).IsAssignableFrom(node.Object.Type))
return Visit(node.Object);
return base.VisitMethodCall(node);
}
public static Expression<T> Tidy<T>(Expression<T> func) => (Expression<T>)new DontCompile().Visit(func);
}
...
return query.Select(DontCompile.Tidy<...>(u => new UserResult<V>{
Id = u.Id,
DisplayName = u.DisplayName,
Value = ValueGetter.Compile()(u)
});
...
Ma questo inizia a sembrare un po 'confuso.