C# 从父级获取派生类属性,无需重复转换
我正试图解决数据模型结构中的设计失败引起的麻烦。重构不是一个选项,因为EF变得疯狂。ASP.NET4.6框架 结构如下:C# 从父级获取派生类属性,无需重复转换,c#,C#,我正试图解决数据模型结构中的设计失败引起的麻烦。重构不是一个选项,因为EF变得疯狂。ASP.NET4.6框架 结构如下: class Course { // properties defining a Course object. Example: Marketing course public string Name { get; set; } } class CourseInstance { // properties that define an Instan
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
及其导航属性放在派生类型上,而不是放在父类型上。现在,每次我想从课程安装
访问课程
,我都会执行以下操作:
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
用法
//他的第一个建议——它有效,检索'CourseInstance'的'Course'属性`
var courses=courseInstancesQuery.Select(GetCourse())
//上面是我修改过的重载。
var courseNames=courseInstancesQuery.Select(GetCourseProperty(c=>c.Name));
思想
在我看来,建议的实现的问题在表达式.Call
行中。Per:
创建MethodCallExpression,该表达式表示对接受参数的方法的调用
然而,我想要的表达式不包含任何方法调用,所以我删除了它,它就工作了。现在,我只需使用委托提取所需属性的名称,并使用另一个MemberAccessExpression
获取该名称
不过,这只是我的解释。如果我错了,很高兴得到纠正
备注:我建议在私有字段中缓存typeof
调用,而不是每次构建表达式时都调用它们。同样,这可以用于两个以上的派生类(在我的例子中是InternalCourseInstance
和OpenCourseInstance
),您只需要一个额外的ConditionalPression
编辑
我编辑了代码部分-EntityFramework似乎不支持
Expression.Convert
,但是Expression.TypeAs
的工作原理是一样的。您必须使用表达式树创建表达式:
Expression<Func<CourseInstance, Course>> CreateExpression()
{
// (CourseInstance x) => x is InternalCourseInstance ? ((InternalCourseInstance)x).Course : ((OpenCourseInstance).x).Course
ParameterExpression param = Expression.Parameter(typeof(CourseInstance), "x");
Expression expr = Expression.TypeIs(param, typeof(InternalCourseInstance));
var cast1Expr = Expression.MakeMemberAccess(Expression.Convert(param, typeof(InternalCourseInstance)), typeof(InternalCourseInstance).GetProperty(nameof(InternalCourseInstance.Course)));
var cast2Expr = Expression.MakeMemberAccess(Expression.Convert(param, typeof(OpenCourseInstance)), typeof(OpenCourseInstance).GetProperty(nameof(OpenCourseInstance.Course)));
expr = Expression.Condition(expr, cast1Expr, cast2Expr);
return Expression.Lambda<Func<CourseInstance, Course>>(expr, param);
}
expr = Expression.Call(null, func.Method, expr);
为了从实例中获取CourseId
或名称
,您必须引入一个委托,该委托需要Course
的实例并返回任意类型的T
。这意味着您需要在表达式树中添加对该委托的调用:
Expression<Func<CourseInstance, Course>> CreateExpression()
{
// (CourseInstance x) => x is InternalCourseInstance ? ((InternalCourseInstance)x).Course : ((OpenCourseInstance).x).Course
ParameterExpression param = Expression.Parameter(typeof(CourseInstance), "x");
Expression expr = Expression.TypeIs(param, typeof(InternalCourseInstance));
var cast1Expr = Expression.MakeMemberAccess(Expression.Convert(param, typeof(InternalCourseInstance)), typeof(InternalCourseInstance).GetProperty(nameof(InternalCourseInstance.Course)));
var cast2Expr = Expression.MakeMemberAccess(Expression.Convert(param, typeof(OpenCourseInstance)), typeof(OpenCourseInstance).GetProperty(nameof(OpenCourseInstance.Course)));
expr = Expression.Condition(expr, cast1Expr, cast2Expr);
return Expression.Lambda<Func<CourseInstance, Course>>(expr, param);
}
expr = Expression.Call(null, func.Method, expr);
null
非常重要,因为指向匿名方法的委托将从编译器转换为静态方法。另一方面,如果委托指向一个命名的非静态方法,您当然应该提供一个实例,然后为该实例调用此方法:
expr = Expression.Call(instanceExpression, func.Method, expr);
请注意,编译后的方法现在返回的是T
,而不是课程
,因此最终的方法如下所示:
Expression<Func<CourseInstance, T>> CreateExpression<T>(Func<Course, T> func)
{
// x => func(x is InternalCourseInstance ? ((InternalCourseInstance)x).Course : ((OpenCourseInstance).x).Course)
ParameterExpression param = Expression.Parameter(typeof(CourseInstance), "x");
Expression expr = Expression.TypeIs(param, typeof(InternalCourseInstance));
var cast1Expr = Expression.MakeMemberAccess(Expression.Convert(param, typeof(InternalCourseInstance)), typeof(InternalCourseInstance).GetProperty(nameof(InternalCourseInstance.Course)));
var cast2Expr = Expression.MakeMemberAccess(Expression.Convert(param, typeof(OpenCourseInstance)), typeof(OpenCourseInstance).GetProperty(nameof(OpenCourseInstance.Course)));
expr = Expression.Condition(expr, cast1Expr, cast2Expr);
expr = Expression.Call(null, func.Method, expr);
return Expression.Lambda<Func<CourseInstance, T>>(expr, param);
}
Expression创建表达式(Func Func)
{
//x=>func(x是InternalCourseInstance?((InternalCourseInstance)x)。课程:((OpenCourseInstance.x)。课程)
ParameterExpression param=表达式参数(typeof(CourseInstance),“x”);
expr=Expression.TypeIs(参数,typeof(InternalCourseInstance));
var cast1Expr=Expression.MakeMemberAccess(Expression.Convert(param,typeof(InternalCourseInstance)),typeof(InternalCourseInstance).GetProperty(nameof(InternalCourseInstance.Course));
var cast2Expr=Expression.MakeMemberAccess(Expression.Convert(param,typeof(OpenCourseInstance)),typeof(OpenCourseInstance).GetProperty(nameof(OpenCourseInstance.Course));
expr=Expression.Condition(expr,cast1Expr,cast2Expr);
expr=Expression.Call(null,func.Method,expr);
返回表达式.Lambda(expr,param);
}
我不确定这是否适用于EF,但您是否可以使用动态
将您的表达式转换为类似表达式
?不太理想,但你的设计已经被打破了。事实上,我恨我自己甚至建议它…@HimBromBeere我想到了这一点,但它并不能解决概念层面的问题。如果需要,我仍然没有办法获得课程的Id
。通常情况下,Inq表达式和AutoMapper配置中不需要这样做,这意味着我不能简单地使用follow-upSelect
语句获取Id
。请不要有你自己,我已经对这个问题表达了足够的仇恨:)请将你的解决方案转移到它自己的答案上来,谢谢。谢谢你的回复。如果我理解正确-结果
将包含课程
对象,无论我通过课程实例
、内部课程实例
还是开放课程实例
?这在LINQ中对实体有效吗?这比我的经验更深刻。我也不会,但是我认为EF是考虑表达式树的原因之一。否则,您可以按照问题中所写的方式创建方法。是的,result
包含一个Course
的实例。仍然无法获取表达式。请调用以工作。它看起来是这样的:Expression.Call(null,propertySelector.Method,outerExpression)
并抛出静态方法需要null实例,非静态方法需要非null实例。参数名称:方法例外我不确定什么是静态的,什么不是。我的整个表达式被定义为一个静态方法,附加到我的数据模型-CourseInstance
,但这不应该是一个问题,事实上也不是。根据编译器-propertySelector.Method
是sup
Expression<Func<CourseInstance, T>> CreateExpression<T>(Func<Course, T> func)
{
// x => func(x is InternalCourseInstance ? ((InternalCourseInstance)x).Course : ((OpenCourseInstance).x).Course)
ParameterExpression param = Expression.Parameter(typeof(CourseInstance), "x");
Expression expr = Expression.TypeIs(param, typeof(InternalCourseInstance));
var cast1Expr = Expression.MakeMemberAccess(Expression.Convert(param, typeof(InternalCourseInstance)), typeof(InternalCourseInstance).GetProperty(nameof(InternalCourseInstance.Course)));
var cast2Expr = Expression.MakeMemberAccess(Expression.Convert(param, typeof(OpenCourseInstance)), typeof(OpenCourseInstance).GetProperty(nameof(OpenCourseInstance.Course)));
expr = Expression.Condition(expr, cast1Expr, cast2Expr);
expr = Expression.Call(null, func.Method, expr);
return Expression.Lambda<Func<CourseInstance, T>>(expr, param);
}