Say I have an entity that I want to query with ranking applied:
public class Person: Entity
{
public int Id { get; protected set; }
public string Name { get; set; }
public DateTime Birthday { get; set; }
}
In my query I have the following:
Expression<Func<Person, object>> orderBy = x => x.Name;
var dbContext = new MyDbContext();
var keyword = "term";
var startsWithResults = dbContext.People
.Where(x => x.Name.StartsWith(keyword))
.Select(x => new {
Rank = 1,
Entity = x,
});
var containsResults = dbContext.People
.Where(x => !startsWithResults.Select(y => y.Entity.Id).Contains(x.Id))
.Where(x => x.Name.Contains(keyword))
.Select(x => new {
Rank = 2,
Entity = x,
});
var rankedResults = startsWithResults.Concat(containsResults)
.OrderBy(x => x.Rank);
// TODO: apply thenby ordering here based on the orderBy expression above
dbContext.Dispose();
I have tried ordering the results before selecting the anonymous object with the Rank
property, but the ordering ends up getting lost. It seems that linq to entities discards the ordering of the separate sets and converts back to natural ordering during both Concat
and Union
.
What I think I may be able to do is dynamically transform the expression defined in the orderBy
variable from x => x.Name
to x => x.Entity.Name
, but I'm not sure how:
if (orderBy != null)
{
var transformedExpression = ???
rankedResults = rankedResults.ThenBy(transformedExpression);
}
How might I be able to use Expression.Lambda
to wrap x => x.Name
into x => x.Entity.Name
? When I hard code x => x.Entity.Name
into the ThenBy
I get the ordering that I want, but the orderBy
is provided by the calling class of the query, so I don't want to hard-code it in. I have it hardcoded in the example above for simplicity of explanation only.
Since you're ordering by
Rank
first, and theRank
values are identical within each sequence, you should be able to just sort independently and then concatenate. It sounds like the hiccup here would be that, according to your post, Entity Framework isn't maintaining sorting acrossConcat
orUnion
operations. You should be able to get around this by forcing the concatenation to happen client-side:This should render the
Rank
property unnecessary and probably simplify the SQL queries being executed against your database, and it doesn't require mucking about with expression trees.The downside is that, once you call
AsEnumerable()
, you no longer have the option of appending additional database-side operations (i.e., if you chain additional LINQ operators afterConcat
, they will use the LINQ-to-collections implementations). Looking at your code, I don't think this would be a problem for you, but it's worth mentioning.This should help. However you are going to have to concrete up the Anonymous type for this to work. My LinqPropertyChain will not work with it, since its going to be difficult to create the
Expression<Func<Anonymous, Person>>
whilst its still Anonymous.Figured out a way to do this with less Explicite Generics.