Barry réponse fournit une solution à la question posée par le posteur d'origine. Grâce à ces deux personnes pour poser et de répondre.
J'ai trouvé ce fil que j'essayais de trouver une solution à tout problème similaire: par programme la création d'une arborescence d'expression qui comprend un appel à la Toute méthode. Comme une contrainte supplémentaire, cependant, le but ultime de ma solution a été de passer une telle créé dynamiquement expression par le biais de Linq-to-SQL, afin que le travail de chacun() l'évaluation est en fait effectuée dans la base de données elle-même.
Malheureusement, la solution que discuté jusqu'à présent n'est pas quelque chose que Linq-to-SQL peut gérer.
D'exploitation en vertu de l'hypothèse que cela pourrait être un très populaire raison de vouloir construire une expression dynamique de l'arbre, j'ai décidé d'augmenter le fil avec mes conclusions.
Lorsque j'ai tenté d'utiliser le résultat de Barry CallAny() comme une expression Linq-to-SQL where() de la clause, j'ai reçu une exception InvalidOperationException avec les propriétés suivantes:
- HResult=-2146233079
- Message="Interne .NET Framework Fournisseur de Données erreur de 1025"
- Source=Système.Les données.Entité
Après avoir comparé codé en dur expression d'arbre en arbre créé dynamiquement une aide CallAny(), j'ai trouvé que le problème était dû à la Compilation() de l'expression de prédicat et de la tentative d'invoquer la résultante délégué dans la CallAny(). Sans creuser profondément dans Linq-to-SQL détails de mise en œuvre, il semble raisonnable pour moi que Linq-to-SQL ne sais pas quoi faire avec une telle structure.
Donc, après quelques essais, j'ai pu atteindre mon objectif souhaité par une légère révision de l'suggéré CallAny() de la mise en œuvre de prendre un predicateExpression plutôt que d'un délégué pour le Tout() logique des prédicats.
Ma nouvelle méthode est:
static Expression CallAny(Expression collection, Expression predicateExpression)
{
Type cType = GetIEnumerableImpl(collection.Type);
collection = Expression.Convert(collection, cType); // (see "NOTE" below)
Type elemType = cType.GetGenericArguments()[0];
Type predType = typeof(Func<,>).MakeGenericType(elemType, typeof(bool));
// Enumerable.Any<T>(IEnumerable<T>, Func<T,bool>)
MethodInfo anyMethod = (MethodInfo)
GetGenericMethod(typeof(Enumerable), "Any", new[] { elemType },
new[] { cType, predType }, BindingFlags.Static);
return Expression.Call(
anyMethod,
collection,
predicateExpression);
}
Maintenant, je vais démontrer son utilisation avec EF. Pour plus de clarté, je tiens d'abord à montrer le jouet modèle de domaine & EF contexte, je suis en utilisant. Fondamentalement, mon modèle est simpliste les Blogs et les Postes de domaine ... où un blog a plusieurs postes et chaque poste a une date:
public class Blog
{
public int BlogId { get; set; }
public string Name { get; set; }
public virtual List<Post> Posts { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public DateTime Date { get; set; }
public int BlogId { get; set; }
public virtual Blog Blog { get; set; }
}
public class BloggingContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
}
Avec ce domaine établi, voici mon code pour en fin de compte l'exercice de la version révisée de la CallAny() et Linq-to-SQL faire le travail d'évaluation du Tout(). Mon exemple particulier va se concentrer sur le retour de tous les Blogs qui ont au moins un Post qui est plus récente que déterminée à la date butoir.
static void Main()
{
Database.SetInitializer<BloggingContext>(
new DropCreateDatabaseAlways<BloggingContext>());
using (var ctx = new BloggingContext())
{
// insert some data
var blog = new Blog(){Name = "blog"};
blog.Posts = new List<Post>()
{ new Post() { Title = "p1", Date = DateTime.Parse("01/01/2001") } };
blog.Posts = new List<Post>()
{ new Post() { Title = "p2", Date = DateTime.Parse("01/01/2002") } };
blog.Posts = new List<Post>()
{ new Post() { Title = "p3", Date = DateTime.Parse("01/01/2003") } };
ctx.Blogs.Add(blog);
blog = new Blog() { Name = "blog 2" };
blog.Posts = new List<Post>()
{ new Post() { Title = "p1", Date = DateTime.Parse("01/01/2001") } };
ctx.Blogs.Add(blog);
ctx.SaveChanges();
// first, do a hard-coded Where() with Any(), to demonstrate that
// Linq-to-SQL can handle it
var cutoffDateTime = DateTime.Parse("12/31/2001");
var hardCodedResult =
ctx.Blogs.Where((b) => b.Posts.Any((p) => p.Date > cutoffDateTime));
var hardCodedResultCount = hardCodedResult.ToList().Count;
Debug.Assert(hardCodedResultCount > 0);
// now do a logically equivalent Where() with Any(), but programmatically
// build the expression tree
var blogsWithRecentPostsExpression =
BuildExpressionForBlogsWithRecentPosts(cutoffDateTime);
var dynamicExpressionResult =
ctx.Blogs.Where(blogsWithRecentPostsExpression);
var dynamicExpressionResultCount = dynamicExpressionResult.ToList().Count;
Debug.Assert(dynamicExpressionResultCount > 0);
Debug.Assert(dynamicExpressionResultCount == hardCodedResultCount);
}
}
Où BuildExpressionForBlogsWithRecentposts() est une fonction d'assistance qui utilise CallAny() comme suit:
private Expression<Func<Blog, Boolean>> BuildExpressionForBlogsWithRecentPosts(
DateTime cutoffDateTime)
{
var blogParam = Expression.Parameter(typeof(Blog), "b");
var postParam = Expression.Parameter(typeof(Post), "p");
// (p) => p.Date > cutoffDateTime
var left = Expression.Property(postParam, "Date");
var right = Expression.Constant(cutoffDateTime);
var dateGreaterThanCutoffExpression = Expression.GreaterThan(left, right);
var lambdaForTheAnyCallPredicate =
Expression.Lambda<Func<Post, Boolean>>(dateGreaterThanCutoffExpression,
postParam);
// (b) => b.Posts.Any((p) => p.Date > cutoffDateTime))
var collectionProperty = Expression.Property(blogParam, "Posts");
var resultExpression = CallAny(collectionProperty, lambdaForTheAnyCallPredicate);
return Expression.Lambda<Func<Blog, Boolean>>(resultExpression, blogParam);
}
REMARQUE: j'ai trouvé un autre apparemment sans importance delta entre codées en dur et dynamique, construit des expressions. La dynamique construit un a un "extra" convertir un appel pour que les codée en dur version ne semble pas avoir (ou le besoin?). La conversion est introduit dans le CallAny() de la mise en œuvre. Linq-to-SQL semble être ok avec elle, alors je l'ai laissé en place (bien que c'était inutile). Je n'étais pas tout à fait certain si cette conversion peut être nécessaire dans certains plus robuste usages que mon jouet de l'échantillon.