gpt4 book ai didi

c# - LINQ 表达式树 Any() inside Where()

转载 作者:太空狗 更新时间:2023-10-29 20:34:02 25 4
gpt4 key购买 nike

我正在尝试生成以下 LINQ 查询:

//Query the database for all AdAccountAlerts that haven't had notifications sent out
//Then get the entity (AdAccount) the alert pertains to, and find all accounts that
//are subscribing to alerts on that entity.
var x = dataContext.Alerts.Where(a => a.NotificationsSent == null)
.OfType<AdAccountAlert>()
.ToList()
.GroupJoin(dataContext.AlertSubscriptions,
a => new Tuple<int, string>(a.AdAccountId, typeof(AdAccount).Name),
s => new Tuple<int, string>(s.EntityId, s.EntityType),
(Alert, Subscribers) => new Tuple<AdAccountAlert, IEnumerable<AlertSubscription>> (Alert, Subscribers))
.Where(s => s.Item2.Any())
.ToDictionary(kvp => (Alert)kvp.Item1, kvp => kvp.Item2.Select(s => s.Username));

使用表达式树(当我需要使用反射和运行时类型时,这似乎是我唯一能做到的方法)。请注意,在实际代码中(见下文),AdAccountAlert 实际上是通过反射和 for 循环实现动态的。

我的问题:我可以生成直到 .Where() 子句的所有内容。由于类型不兼容,whereExpression 方法调用失败。通常我知道放在那里什么,但 Any() 方法调用让我感到困惑。我已经尝试了所有我能想到的类型,但没有运气。对于 .Where() 和 .ToDictionary() 的任何帮助,我们将不胜感激。

这是我目前所拥有的:

var alertTypes = AppDomain.CurrentDomain.GetAssemblies()
.Single(a => a.FullName.StartsWith("Alerts.Entities"))
.GetTypes()
.Where(t => typeof(Alert).IsAssignableFrom(t) && !t.IsAbstract && !t.IsInterface);

var alertSubscribers = new Dictionary<Alert, IEnumerable<string>>();

//Using tuples for joins to keep everything strongly-typed
var subscribableType = typeof(Tuple<int, string>);
var doubleTuple = Type.GetType("System.Tuple`2, mscorlib", true);

foreach (var alertType in alertTypes)
{
Type foreignKeyType = GetForeignKeyType(alertType);
if (foreignKeyType == null)
continue;

IQueryable<Alert> unnotifiedAlerts = dataContext.Alerts.Where(a => a.NotificationsSent == null);

//Generates: .OfType<alertType>()
MethodCallExpression alertsOfType = Expression.Call(typeof(Enumerable).GetMethod("OfType").MakeGenericMethod(alertType), unnotifiedAlerts.Expression);

//Generates: .ToList(), which is required for joins on Tuples
MethodCallExpression unnotifiedAlertsList = Expression.Call(typeof(Enumerable).GetMethod("ToList").MakeGenericMethod(alertType), alertsOfType);

//Generates: a => new { a.{EntityId}, EntityType = typeof(AdAccount).Name }
ParameterExpression alertParameter = Expression.Parameter(alertType, "a");
MemberExpression adAccountId = Expression.Property(alertParameter, alertType.GetProperty(alertType.GetForeignKeyId()));
NewExpression outerJoinObject = Expression.New(subscribableType.GetConstructor(new Type[] { typeof(int), typeof(string)}), adAccountId, Expression.Constant(foreignKeyType.Name));
LambdaExpression outerSelector = Expression.Lambda(outerJoinObject, alertParameter);

//Generates: s => new { s.EntityId, s.EntityType }
Type alertSubscriptionType = typeof(AlertSubscription);
ParameterExpression subscriptionParameter = Expression.Parameter(alertSubscriptionType, "s");
MemberExpression entityId = Expression.Property(subscriptionParameter, alertSubscriptionType.GetProperty("EntityId"));
MemberExpression entityType = Expression.Property(subscriptionParameter, alertSubscriptionType.GetProperty("EntityType"));
NewExpression innerJoinObject = Expression.New(subscribableType.GetConstructor(new Type[] { typeof(int), typeof(string) }), entityId, entityType);
LambdaExpression innerSelector = Expression.Lambda(innerJoinObject, subscriptionParameter);

//Generates: (Alert, Subscribers) => new Tuple<Alert, IEnumerable<AlertSubscription>>(Alert, Subscribers)
var joinResultType = doubleTuple.MakeGenericType(new Type[] { alertType, typeof(IEnumerable<AlertSubscription>) });
ParameterExpression alertTupleParameter = Expression.Parameter(alertType, "Alert");
ParameterExpression subscribersTupleParameter = Expression.Parameter(typeof(IEnumerable<AlertSubscription>), "Subscribers");
NewExpression joinResultObject = Expression.New(
joinResultType.GetConstructor(new Type[] { alertType, typeof(IEnumerable<AlertSubscription>) }),
alertTupleParameter,
subscribersTupleParameter);

LambdaExpression resultsSelector = Expression.Lambda(joinResultObject, alertTupleParameter, subscribersTupleParameter);

//Generates:
// .GroupJoin(dataContext.AlertSubscriptions,
// a => new { a.AdAccountId, typeof(AdAccount).Name },
// s => new { s.EntityId, s.EntityType },
// (Alert, Subscribers) => new Tuple<Alert, IEnumerable<AlertSubscription>>(Alert, Subscribers))
IQueryable<AlertSubscription> alertSubscriptions = dataContext.AlertSubscriptions.AsQueryable();
MethodCallExpression joinExpression = Expression.Call(typeof(Enumerable),
"GroupJoin",
new Type[]
{
alertType,
alertSubscriptions.ElementType,
outerSelector.Body.Type,
resultsSelector.ReturnType
},
unnotifiedAlertsList,
alertSubscriptions.Expression,
outerSelector,
innerSelector,
resultsSelector);

//Generates: .Where(s => s.Item2.Any())
ParameterExpression subscribersParameter = Expression.Parameter(resultsSelector.ReturnType, "s");
MemberExpression tupleSubscribers = Expression.Property(subscribersParameter, resultsSelector.ReturnType.GetProperty("Item2"));
MethodCallExpression hasSubscribers = Expression.Call(typeof(Enumerable),
"Any",
new Type[] { alertSubscriptions.ElementType },
tupleSubscribers);
LambdaExpression whereLambda = Expression.Lambda(hasSubscribers, subscriptionParameter);
MethodCallExpression whereExpression = Expression.Call(typeof(Enumerable),
"Where",
new Type[] { joinResultType },
joinExpression,
whereLambda);

最佳答案

请注意:ToList() 之后的所有内容(包括在内)不适用于 IQueryable<T>但在 IEnumerable<T> .因此,无需创建表达式树。它肯定不是 EF 或类似的解释。

如果您查看编译器为您的原始查询生成的代码,您会发现它仅在第一次调用 ToList 之前生成表达式树。 .

例子:

以下代码:

var query = new List<int>().AsQueryable();
query.Where(x => x > 0).ToList().FirstOrDefault(x => x > 10);

由编译器翻译为:

IQueryable<int> query = new List<int>().AsQueryable<int>();
IQueryable<int> arg_4D_0 = query;
ParameterExpression parameterExpression = Expression.Parameter(typeof(int), "x");
arg_4D_0.Where(Expression.Lambda<Func<int, bool>>(Expression.GreaterThan(parameterExpression, Expression.Constant(0, typeof(int))), new ParameterExpression[]
{
parameterExpression
})).ToList<int>().FirstOrDefault((int x) => x > 10);

请注意它如何为 ToList 之前的所有内容生成表达式.包括它在内的所有内容都只是对扩展方法的正常调用。

如果您不在代码中模仿这一点,您实际上会向 Enumerable.ToList 发送调用到 LINQ 提供程序 - 然后它尝试将其转换为 SQL 并失败。

关于c# - LINQ 表达式树 Any() inside Where(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16822280/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com