I am trying to work-around an annoyance, caused by design failure in the data model structure. Refactoring is not an option, because EF goes crazy. ASP.NET 4.6 framework.
The structure is as follows:
class Course
{
// properties defining a Course object. Example: Marketing course
public string Name { get; set; }
}
class CourseInstance
{
// properties that define an Instance of course. Example: Marketing course, January
public DateTime StartDate { get; set; }
}
class InternalCourseInstance : CourseInstance
{
// Additional business logic properties. Example : Entry course - Marketing program
public bool IsEntry { get; set; }
public int CourseId { get; set; }
public Course Course { get; set; }
}
class OpenCourseInstance : CourseInstance
{
// Separate branch of instance. Example - Marketing course instance
public int Price { get; set; }
public int CourseId { get; set; }
public Course Course { get; set; }
}
I bet you can already see the flaw? Indeed, for an unknown reason, someone decided to put CourseId and its navigational property on the derived types, instead of parent. Now every time I want to access the Course from CourseInstance, I have do do something like:
x.course => courseInstance is InternalCourseInstance
? (courseInstance as InternalCourseInstance).Course
: (courseInstance as OpenCourseInstance).Course;
You can see how this can become really ugly with several more course instance types that derive from CourseInstance.
I am looking for a way to short-hand that, essentially create a method or expression which does it internally. There is one more problem however - it has to be translatable to SQL, since more often then not this casting is used on IQueryable.
The closest I came to the solution is:
// CourseInstance.cs
public static Expression<Func<CourseInstance, Course>> GetCourseExpression =>
t => t is OpenCourseInstance
? (t as OpenCourseInstance).Course
: (t as InternalCrouseInstance).Course
This should work, however sometimes I need Id or Name of Course. And there is no way, as far as I can tell - to expand this Expression in specific circumstances to return Id or Name.
I can easily do it inside a method, but then it fails on LINQ to Entities, understandably.
I know it's a project-specific problem, however at this stage it cannot be fixed, so I am trying to find a decent work around.
Solution
Firstly, thanks to HimBromBeere for his answer and patience. I couldn't get his generic overload to work, in my case it was throwing as you might see in the discussion below his answer. Here is how I solved it eventually:
CourseInstance.cs
public static Expression<Func<CourseInstance, TProperty> GetCourseProperty<TProperty>(
Expression<Func<Course, TProperty>> propertySelector)
{
var parameter = Expression.Parameter(typeof(CourseInstance), "ci");
var isInternalCourseInstance = Expression.TypeIs(parameter, typeof(InternalCourseInstance);
// 1) Cast to InternalCourseInstance and get Course property
var getInternalCourseInstanceCourse = Expression.MakeMemberAccess(
Expression.TypeAs(parameter, typeof(InternalCourseInstance)), typeof(InternalCourseInstance).GetProperty(nameof(InternalCourseInstance.Course)));
var propertyName = ((MemberExpression)propertySelector.Body).Member.Name;
// 2) Get value of <propertyName> in <Course> object.
var getInternalCourseInstanceProperty = Expression.MakeMemberAccess(
getInternalCourseInstanceCourse, typeof(Course).GetProperty(propertyName);
// Repeat steps 1) and 2) for OpenCourseInstance ...
var expression = Expression.Condition(isInternalCourseInstance, getInternalCourseInstanceProperty, getOpenCourseInstanceProperty);
return Expression.Lambda<Func<CourseInstance, TProperty(expression, parameter);
Usage
// his first suggestion - it works, retrieving the `Course` property of `CourseInstance`
var courses = courseInstancesQuery.Select(GetCourse())
// My modified overload above.
var courseNames = courseInstancesQuery.Select(GetCourseProperty<string>(c => c.Name));
Thoughts
The problem with the suggested implementation in my opinion is within the Expression.Call line. Per MS docs:
Creates a MethodCallExpression that represents a call to a method that takes arguments.
However my desired expression contains no method calls - so I removed it and it worked. Now I simply use the delegate to extract the desired property's name and get that with another MemberAccessExpression.
This is only my interpretation though. Happy to get corrected, if I am wrong.
Remarks: I recommend caching the typeof calls in private fields, instead of calling them every time you build the expression. Also this can work for more then two derived classes (in my case InternalCourseInstance and OpenCourseInstance), you just need an extra ConditionalExpression(s).
Edit
I've edited the code section - it seems Expression.Convert is not supported by EntityFramework, however Expression.TypeAs works just the same.
dynamicturning your expression into something likeExpression<Func<dynamic, Course>>? Not ideal, but as your design is broken anyway. In fact I hate myself for even suggesting it...IdofCourse, if I need. And more often then not this is required inLINQ expressions and AutoMapper configurations, which means I cannot simply get theIdwith follow upSelectstatement, for example. Please don't have yourself, I've expressed enough hate on this subject already :)