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/
精彩评论