基于接口生成表达式

时间:2010-08-31 22:33:31

标签: c# entity-framework lambda code-reuse

我正在寻找例外 无法将类型'MySomeTypeThatImplementsISomeInterfaceAndIsPassedAs [T] ToTheClass'强制转换为'ISomeInterface'。 LINQ to Entities仅支持转换实体数据模型基元类型。

我的存储库看起来像

public interface IRepository<T>
{
    IQueryable<T> Get(System.Linq.Expressions.Expression<System.Func<T, bool>> Query);
}

另外,我有服务类

public abstract class FinanceServiceBase<TAccount, TParcel, TPayment>
    where TAccount : IAccount
    where TParcel : IParcel
    where TPayment : IPayment
 {
     //...
     IRepository<TPayment> ParcelRepository {get; private set;}         

     public bool MakePayment(TPayment payment)
     {
         //...
         ParcelRepository.Get(p => p.ParcelId == 2);
         // here my exception is thrown
         // **p.ParcelId is in IParcel**
         //...
     }
 }
 //...

通过这门课,我可以控制很多关于财务的事情而无需重写其他程序的代码。我用3个通用参数做了这个类,因为我无法使用IRepository,因为我的存储库不能是<out T>

ParcelId是Int32
TParcel是typeof( ParcelToReceive ),是实现 IParcel 的实体,并且是使用codeonly生成的

当我调用Get并且生成的lambda看起来像

时,会出现问题
(**(ISomeInterface)**$p).SomeInterfaceMember == 

而不是

($p.SomeInterfaceMember)

所以,实体框架尝试执行转换并抛出异常。我想知道的是:无论如何告诉linq lambda字段p.ParcelId来自 TParcel 而不是来自 IParcel

已经尝试过(没有运气):

p => ((TParcel)p).ParcelId 

由于

2 个答案:

答案 0 :(得分:4)

似乎将where TAccount : IAccount中的通用约束设置为像where TAccount : class, IAccount这样的东西告诉实体框架该表达式包含,并且它不会使显式转换为它可以用于原始EDM和枚举类型。

答案 1 :(得分:2)

我担心你不能这样做,因为从根本上说你正在访问界面中声明的属性。 LINQ-to-Entities似乎不支持这一点;你需要在真实实体类型中调用属性。

解决此问题的一种方法是将parcelId属性作为表达式树传递给参数,然后使用该参数中的属性在运行时动态构造lambda表达式:

public bool MakePayment(TPayment payment,
                        Expression<Func<TParcel, int>> parcelIdExpr)
{
    // You can use any expression involving parcelId here
    Expression<Func<int, bool>> expr = parcelId => parcelId == 2;

    // This is the parameter of the new lambda we’re creating
    var parameter = Expression.Parameter(typeof(TParcel));

    // This constructs the lambda expression “p => expr(p.ParcelId)”,
    // where “expr” is the lambda expression declared above
    var lambda = Expression.Lambda(Expression.Invoke(expr,
        Expression.Invoke(parcelIdExpr, parameter)), parameter);

    ParcelRepository.Get((Expression<Func<TParcel, bool>>) lambda);
}

[...]

myFinanceService.MakePayment(myPayment, p => p.ParcelId);

如果您不希望每次调用MakePayment时都必须传递此额外参数,那么理论上您可以使用字符串文字按名称检索属性;但是,这是不安全的,因为它不能确保它是实现接口的正确属性。此外,这是一种非常迂回的方式,所以不能保证:

public bool MakePayment(TPayment payment)
{
    Expression<Func<int, bool>> expr = parcelId => parcelId == 2;

    var parameter = Expression.Parameter(typeof(TParcel));

    // This is the expression “p.ParcelId”, where “p” is the parameter
    var propertyExpression = Expression.Property(parameter, "ParcelId");

    var lambda = Expression.Lambda(Expression.Invoke(expr, propertyExpression),
                                   parameter);

    ParcelRepository.Get((Expression<Func<TParcel, bool>>) lambda);
}

您可以将其分解为通用实用程序方法:

public static class Utils
{
    public static Expression<Func<TParameter, TResult>>
        CombineLambdas<TParameter, T, TResult>(
            Expression<Func<TParameter, T>> lambda1,
            Expression<Func<T, TResult>> lambda2
        )
    {
        var parameter = Expression.Parameter(typeof(TParameter));
        var lambda = Expression.Lambda(Expression.Invoke(lambda2,
            Expression.Invoke(lambda1, parameter)), parameter);
        return (Expression<Func<TParameter, TResult>>) lambda;
    }
}

public bool MakePayment(TPayment payment,
                        Expression<Func<TParcel, int>> parcelIdExpr)
{
    ParcelRepository.Get(Utils.CombineLambdas(
        parcelIdExpr, parcelId => parcelId == 2));
}