开发者

How can I combine multiple refactored Select expressions in Linq (to EF or SQL)?

Say I have this view model:

public class SeriesLinkViewModel
{
    public static Expression<Func<Series, SeriesLinkViewModel>> FromSeries =
        s => new SeriesLinkViewModel
        {
            Name = s.Name,
            Slug = s.Slug,
        };

    public string Name { get; set; }
    public string Slug { get; set; }
}

I stuck the projection function in there for convenience, so now I can say something like:

var links = dc.Series.Select(SeriesLinkViewModel.FromSeries);

Awesom开发者_JS百科e. But what do I do if I wanted to add to this query? Say I wanted to also pull a Description column from the table. Normally, I could just do a select new { } and put Description in there, but I can't quite do that because I can only put one projection function in `.Select() .

I was hoping I could do something like this:

q = from s in dc.Series
    select new
    {
        Series = SeriesLinkViewModel.FromSeries.Compile()(s),
        Description = s.Description
    };

But I get an exception:

System.InvalidCastException: Unable to cast object of type 'System.Linq.Expressions.FieldExpression' to type 'System.Linq.Expressions.LambdaExpression'.

Or could I at least have all these queries done in one round trip somehow? I know TransactionScope works for making changes, but I don't think it causes queries to be done all at once.


Solved it with LinqKit:

var fs = SeriesLinkViewModel.FromSeries; //needs to be local for some reason
q = from s in dc.Series.AsExpandable() //enables LinqKit to do its magic
    select new
    {
        Series = fs.Invoke(s), //and voila!
        Description = s.Description
    };


This is an addition to Rei's answer (which I marked as correct). If you want to remove the dependency on the local variable, take a look at this answer from Dan Abramov on how to fix LinqKit.


I know this is not exactly what you are looking for, but one possible work around is to create a method like this

private IQueryable<SeriesLinkViewModel> FromSeries(IQueryable<Series> seriesQuery)
{
    return from s in seriesQuery
           select new SeriesLinkViewModel
           {
                Name = s.Name,
                Slug = s.Slug
           };
}

Then when you want to use the projection, run your query through it.

return FromSeries(from s in Series
                  where s.Name == "foo"
                  select s);

Not ideal because you aren't creating a reusable expression that can be combined with others, but at least you would have just one mapping function that all similar queries run through.


This is in my opinion a nicer solution.

public class SeriesLinkViewModel
{
    public static Expression<Func<Series, SeriesLinkViewModel>> FromSeries =
        s => new SeriesLinkViewModel
        {
            Name = s.Name,
            Slug = s.Slug,
        };

    public string Name { get; set; }
    public string Slug { get; set; }
}

public class SeriesLinkExtendedViewModel: SeriesLinkViewModel
{
    public new static Expression<Func<Series, SeriesLinkExtendedViewModel>> FromSeries = 
        SeriesLinkViewModel.FromSeries.Merge(s => new SeriesLinkExtendedViewModel
        {
            Description = s.Description
        });

    public string Description { get; set; }
}

// Somewhere else...
var q = from s in dc.Series.Select(SeriesLinkExtendedViewModel.FromSeries);

The "Merge" extension method will return a projection that is the result of merging both projection expressions, thus containing the three columns: Name, Slug and Description.

The actual implementation can be found in this link: https://coding.abel.nu/2013/01/merging-expression-trees-to-reuse-in-linq-queries/

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜