views:

250

answers:

3

I want to separate out often used expressions in linq queries. I'm using Entity Framework 4 and also LINQKit but I still don't know how I should do it the right way. Let me give you an example:

Article article = dataContainer.Articles.FirstOrDefault(a => a.Id == id);

IEnumerable<Comment> comments =
  (from c in article.Comments
   where CommentExpressions.IsApproved.Invoke(c)
   select c);


public static class CommentExpressions
{
    public static Expression<Func<Comment, bool>> IsApproved
    {
        get
        {
            return c => c.IsApproved;
        }
    }
}

Of course the IsApproved expression would be something much more complicated.

The problem is that the Invoke() won't work because I didn't call .asExpandable() from LINQKit on container.Comments but I can't call it because it's just an ICollection instead of an ObjectSet.

So my question is: Do I always have to go through the data context when I want to include external expressions or can I somehow use it on the object I fetched (Article)?

Any ideas or best practices? Thanks very much! neo

A: 

As you are using LinqKit use the predicate builder:

IEnumerable<Comment> comments =
  article.Comments.Where(CommentExpressions.IsApproved).Select(c=>c);

 public static class CommentExpressions
 {
    public static Expression<Func<Module, bool>> IsApproved
    {
        get
        {
            var pred = PredicateBuilder.True<Module>();
            pred = pred.And(m => m.IsApproved);
            return pred
        }
    }
 }
Chao
Doesn't work for me because article.Comments.Where() accepts only Func<Comment,bool> and not an Expression. (There was also a typo, it's not "Module, bool" but "Comment, bool", sorry)
neo
+1  A: 

The problem is EF doesn't support Invoke expressions, so you need to fold the expression into the EF in a different way.

You should check out Damien's 'Client-Side properties' post which does basically what you are asking for.

And for more background information check out Colin Meek's post showing how to visit and replace Invoke expressions with expressions EF can handle.

Hope this helps

Alex

Alex James
Hm... Damien's post depends on putting ".WithTranslations()" at the end of the expression but this is again not possible in my case because after the Article object is fetched, I get no IQueryable's e.g. for article.Comments and as .WithTranslations is an extension for IQueryable this won't help. Tell me if I overlooked something.
neo
A: 

I think I found an answer to my "problem". My problem wasn't really a problem but rather a wrong way of thinking.

When I read through this, it came clear to me that I just wanted to find a solution for a wrong problem. Let me explain that....

I'm using the POCO template with EF4 and have lazy loading still activated. With this I could magically walk through the objects without sending additional queries, at least they were not visible in my code. But of course, for each access to a relationship, queries were sent, and I also observed these with EF Profiler. And of course I also wanted to have them in their optimal way, i.e. "use the where condition in that sql" instead of "fetch all rows and do the filtering afterwards". This way of thinking is also called premature optimization.

It finally hit me when I started thinking "How would it be if I don't use lazy loading?" Simple, all data I want is fetched in the first place and afterwards you operate on that data and on nothing else, i.e. no hidden queries. When looking at it this way it makes sense to have ICollection instead of IQueryable on your domain object's relationships. And of course I can't use an Expression> for a .Where() call on a ICollection, but rather the inner Func<..>.

To answer my own question, Expression<>'s must only be used while having access to the repository or being aware of it which the POCO objects are not. If they should be used outside, i.e. on a ICollection, they have to be compiled to Func objects like that:

IEnumerable<Comment> comments =
  (from c in article.Comments
   select c).Where(CommentExpressions.IsApproved.Compile());

And if there really is a need for high performance then the repository has to be asked to fetch all the comments belonging to that article by matching on the ID and where CommentExpressions.IsApproved is fulfilled. Something like that:

IEnumerable<Comment> comments =
  (from c in dataContainer.ArticleComments
   where c.ArticleId == article.Id
   select c).Where(CommentExpressions.IsApproved);

Now the last where condition retains an Expression because of the missing .compile() and can be used in the sql.

I'm nearly satisfied with this. What I find annoying is the need to call ".compile()" and what I still don't understand is how I should construct or let one Expression use another expression which doesn't seem to be possible except by calling .compile() while including it because it's again only ICollection on which I cannot put Expression objects. I suppose here I can use LINQKit which then strips out the compile() calls.

I hope I'm going in the right direction. If you spot any logical error or can think of better ways doing that, please tell me in the comments so I can update the answer. Thanks all!

neo