Я пытаюсь обойти раздражение, вызванное ошибкой проектирования в структуре модели данных. Рефакторинг не вариант, потому что EF сходит с ума. ASP.NET 4.6 Framework.
Структура выглядит следующим образом:
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; }
}
Могу поспорить, ты уже видишь недостаток? Действительно, по неизвестной причине кто-то решил поместить CourseId
и его навигационное свойство в производные типы вместо родительских. Теперь каждый раз, когда я хочу получить доступ к Course
из CourseInstance
, я делаю что-то вроде:
x.course => courseInstance is InternalCourseInstance
? (courseInstance as InternalCourseInstance).Course
: (courseInstance as OpenCourseInstance).Course;
Вы можете увидеть, как это может стать действительно уродливым, с помощью нескольких других типов экземпляров курса, которые происходят от CourseInstance
.
Я ищу способ сократить это, по сути, создать метод или выражение, которое делает это внутренне. Однако есть еще одна проблема - она должна быть переведена на SQL, поскольку чаще всего не * это приведение используется на IQueryable
.
Самое близкое, что я нашел к решению:
// CourseInstance.cs
public static Expression<Func<CourseInstance, Course>> GetCourseExpression =>
t => t is OpenCourseInstance
? (t as OpenCourseInstance).Course
: (t as InternalCrouseInstance).Course
Это должно работать, однако иногда мне нужно Id
или Name
из Course
. И, насколько я могу судить, нет никакого способа - расширить это Выражение при определенных обстоятельствах, чтобы вернуть Id
или Name
.
Я легко могу сделать это внутри метода, но тогда, по понятным причинам, он не работает на LINQ to Entities.
Я знаю, что это проблема конкретного проекта, однако на данном этапе ее нельзя решить, поэтому я пытаюсь найти достойную работу вокруг.
Решение
Во-первых, спасибо HimBromBeere за его ответ и терпение. Я не мог заставить его общую перегрузку сработать, в моем случае это было сбрасывание, как вы можете видеть в обсуждении ниже его ответа. Вот как я решил это в итоге:
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);
Использование
// 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));
Мысли
Проблема с предложенной реализацией, на мой взгляд, находится в пределах строки Expression.Call
. За MS документов :
Создает выражение MethodCallExpression, представляющее вызов метода, который принимает аргументы.
Однако мое желаемое выражение не содержит вызовов методов - поэтому я удалил его, и оно сработало. Теперь я просто использую делегат, чтобы извлечь имя нужного свойства и получить его с другим MemberAccessExpression
.
Это только моя интерпретация. Рад поправиться, если я ошибаюсь.
Замечания : Я рекомендую кэшировать вызовы typeof
в приватных полях, а не вызывать их каждый раз, когда вы строите выражение. Также это может работать для более чем двух производных классов (в моем случае InternalCourseInstance
и OpenCourseInstance
), вам просто нужны дополнительные ConditionalExpression
(s).
Редактировать
Я отредактировал раздел кода - кажется, Expression.Convert
не поддерживается EntityFramework, однако Expression.TypeAs
работает точно так же.