views:

584

answers:

3

Hi folks.

i've got a LinqToSql query with a custom extension method at the end. this extension method is erroring when I try to linq2sql tries to generate the sql statement.

Error:

Method 'System.Collections.Generic.IList1[System.String] ToListIfNotNullOrEmpty[String](System.Collections.Generic.IEnumerable1[System.String])' has no supported translation to SQL.

Extension Method:

public static IList<T> ToListIfNotNullOrEmpty<T>(this IEnumerable<T> value)
{
    return value.IsNullOrEmpty()
               ? null
               : (value is IList<T> ? value as IList<T> : new List<T>(value));
}

Sample Linq to sql code:

public IQueryable<Models.Post> GetPosts()
{
    var dataLoadOptions = new DataLoadOptions();
    dataLoadOptions.LoadWith<Post>(x => x.PostTags);
    dataLoadOptions.LoadWith<PostTag>(x => x.Tag);
    _sqlDatabase.LoadOptions = dataLoadOptions;

    return from p in _sqlDatabase.Posts
        select new Models.Post
        {
            PostId = p.PostId,

            CommentList = (from c in p.Comments
                select new Models.Comment
                {
                    PostId = c.PostId,
                    < ... stuff snipped ... >
                }).ToListIfNotNullOrEmpty(),

            < ... more stuff snipped ... >

            TagList = (from t in p.PostTags
                select t.Tag.Description).ToListIfNotNullOrEmpty(),
        };
}

Normally, I would just have a ToList() method at the end, but i want to return a null object if the list is empty .. hence my extension method.

Can anyone suggest how I should fix this to make it work?

+1  A: 

Have you tried this:

return _postRepository.Comments.ToList().ToNullIfEmpty();

With extension method:

public static IList<T> ToNullIfEmpty<T>(this List<T> value)
{
    return value.IsNullOrEmpty() ? null: value;
}

The problem is this bit:

TagList = (from t in p.PostTags
            select t.Tag.Description).ToListIfNotNullOrEmpty()

If you had this then it would work:

select new Models.Post
    {
        ....
    }.ToList().ToNullIfEmpty();

Or you could try changing the extension method to extend IQueryable as the other commenter suggested.

Jonathan Parker
that's a kewl idea.. but ... => Method 'System.Collections.Generic.IList`1[System.String] ToNullIfEmpty[String](System.Collections.Generic.List`1[System.String])' has no supported translation to SQL.
Pure.Krome
If you use ToList() beforehand, ToNullIfEmpty() won't be interpreted to SQL and you should not receive such an exception.
Frank Schwieterman
I've updated the first method to _postRepository.Comments (assuming that is the name of the table in your linq2sql mapping).
Jonathan Parker
I agree Frank .. but i the compiler isn't lying! when i saw Jonathan's reply, i thought 'sweet! that makes sence. especially since the ToList will then hit the DB, come back and THEN the IsNullIfEmpty() is called. But it's erroring. That said, have u read Sruly's reply, below?
Pure.Krome
+2  A: 

If you make an extension method taking IQueriable<T> instead of IEnumerable<T> you can control when the query is executed:

    public static IList<T> ToListIfNotNullOrEmpty<T>(this IQueryable<T> value)
    {
        var results = value.Provider.Execute<IEnumerable<T>>(value.Expression);
        return results == null || results .Count() == 0 
                   ? null
                   : results.ToList();
    }
Pop Catalin
Um, the variable 'results' is never used ... ??? is that a bug?
Pure.Krome
It's not a bug in the sense that it affects the outcome of the method, but you're right I might as well use the results local value for clarity. I've fixed that.
Pop Catalin
Hi Pop. When i tried this, i got an error: The query results cannot be enumerated more than once. (screenshot: http://img22.imageshack.us/img22/9318/errorsit.png)
Pure.Krome
Damn. Pop, i've edited my original post with the proper linq code that is getting called. I thought i was calling some other method, but i noticed it wasn't. the proper repository code is now listed above. I don't know if that changes things.... thoughts?
Pure.Krome
@Pure.Krome: as a side question why do you want to return null instead of and empty list? it greatly simplifies your code if your return an empty list, no more null checks, just .. post.TagList.Length ..
Pop Catalin
because i'm a big fan of nulls when the object has 'no data'. so for an enumerable collection, this means null (for me). otherwise i have an instance of 'nothing' (in my opinion).
Pure.Krome
Pop, can u read some of the comments in the posts below also? I would like to know your thoughts.
Pure.Krome
A: 

I have had the same issue. The problem is that you can not use extension methods within a defered linq2sql expression. This is because the IQueryable provider tries to translate all the methods in the expression tree to sql when the qury is executed. It can not tell the diference between you method and the ones that are native to linq2sql. Therefore the problem.

The solution is to add the method to a partial class that will extend the native linq2sql classes instead of as an extension method.

EDIT: I am adding the suggested changes to the code

public IQueryable GetPosts()

{    
    var dataLoadOptions = new DataLoadOptions();
    dataLoadOptions.LoadWith<Post>(x => x.PostTags);
    dataLoadOptions.LoadWith<PostTag>(x => x.Tag);
    _sqlDatabase.LoadOptions = dataLoadOptions;
    return from p in _sqlDatabase.Posts
        select new Models.Post
        {
            PostId = p.PostId,
            CommentList = p.GetCommentList(),
            < ... more stuff snipped ... >
            TagList = (from t in p.PostTags
                select t.Tag.Description).ToListIfNotNullOrEmpty()
        };
}

I would then have the GetCommentList() method in a partial class

public partial class Post
{
     public List<Comment> GetCommentList()
     {
           List<Commment> resultList = from this.Comments etc...
           < ... put the select code here ... >
           if(resultList.Count > 0)
                return resultList;

           return null;
     }
}
Sruly
You *can* use extension methods within a deferred expression, if the extension method takes an IQueryable<T> as the first parameter, because linq to sql, calls those methods instead of trying to translate them
Pop Catalin
Maybe you should be able to but in my experience I havent been able to without getting the same error Pure.krome mentioned in the question
Sruly
So you just factored out the GetcommentList() stuff?
Pure.Krome
Yes. That should work. It has worked for me in the past. You will also have to maker the same change for the tags
Sruly