I have a project that allows for some computed properties and business logic implemented via decorator and interfaces that controls all access to an EF Code first layer. I want to expose this business logic layer via oData and allow standard IQueryable functionality to filter, order and page. I need the query to be applied on the db level and not just resulting IEnumerable via Linq to Objects query for various reasons.
My class structure looks something like LogicClass(Repository) > Interface > Decorator > Poco. And the classes look something like:
public class PeopleLogicLayer
{
// ... business and query logic ...
// basic query used internally
private System.Linq.IQueryable<PersonEfPoco> GetQuery()
{
if (this.CurrentQuery == null) this.ResetQuery();
var skipQuantity = (this.Page <= 1) ? 0 : (this.Page - 1) * this.PageSize;
return this.CurrentQuery.Skip(skipQuantity)
.Take(this.PageSize)
.AsQueryable();
}
}
public interface IPerson
{
int Id { get; set; }
String FirstName { get; set; }
String LastName { get; set; }
String FullName { get; }
}
public class PersonEfPoco
{
public int Id { get; set; }
public String FirstName { get; set; }
public String LastName { get; set; }
}
public class PersonDecorator : IPerson
{
private PersonEfPoco _person;
public PersonDecorator(PersonEfPoco person)
{
this._person = person;
}
public int Id
{
get { return this._person.Id; }
set { this._person.Id = value; }
}
public String FirstName
{
get { return this._person.FirstName; }
set { this._person.FirstName = value; }
}
public String LastName
{
get { return this._person.LastName }
set { this._person.LastName = value }
}
public String FullName
{
get { return $"{this._person.FirstName} {this._person.LastName}"; }
}
}
What I want to be able to do is something like:
List<IPerson> peopleNamedBob =
from o in (new PeopleHiddenBehindBusinessLogic()) where o.FirstName == "Bob" select o;
or
List<IPerson> peopleNamedBob =
(new PeopleHiddenBehindBusinessLogic()).Where(o => o.FirstName == "Bob").ToList();
This is an over simplification. It is not really possible to do a in-query conversion via 'select new PersonDecorator(o)' since there is complex logic in the decorator layer and this is handled in there, and we don't want to allow access directly to the EF layer, instead preferring to keep the queries on the more abstract layer of the decorator.
I have considered going down the path of implementing a custom Linq provider from scratch like is mentioned here. However that article is very dated and I would think there is a better way in the last 5 years. I found re-linq which sounds like it has potential. However, when I search for tutorials for re-linq there isn't much to be had.
From what I can gather, the high level steps are to create a visitor to replace the subject of the query and convert the filter expressions to match the poco (if it can, most the property names will match) and pass it on to EF. Then save off the expressions not compatible with the EF Poco to later filter the final decorated collection. (purposely leaving out the complications of paging for now)
UPDATE
I recently found the fluid method of supporting Linq but I am still lacking information about how to break down a 'Where' expression with the goal of using filters for IPerson
on PersonEfPoco
.
This brings me to my choices.
- completely custom Linq provider like this
- use re-linq - could use help finding a tutorial
- or does more recent Linq offer a more precise method of implementation
So what is the most up-to-date method?