使用EF TPT

时间:2017-03-06 00:33:04

标签: c# entity-framework linq expression-trees table-per-type

我有一个带有TPT继承映射的项目,现在我需要添加一个搜索功能,它将在几个表中找到记录。这就是我现在所拥有的:

public abstract class Customer
{
    public int      Id      { get; set; }
    public string   Memo    { get; set; }
...
}

public class Person : Customer
{
    public string   GivenName   { get; set; }
    public string   Surname     { get; set; }
...
}
public class Company : Customer
{
    public string       Name     { get; set; }
...
}

我还有一堆工作单元和一堆存储库,我需要将过滤功能添加到CustomerRepository的几个方法中。我们假设我有一个带有以下签名的Count方法

public int Count(System.Linq.Expressions.Expression<Func<Customer, bool>> filter = null)

现在我需要获得GiveNameSurname包含searchTerm的客户数量,以防客户为PersonsearchTerm如果是公司,则为Name字段。

TL; DR
如何实施包含Customers(包含PersonCompany类型)的单个可搜索分页列表的视图?我的意思是在签名方法方面,如public IHttpActionResult Get(string searchTerm, int pageSize, int pageNumber) ...

那是我尝试过的:
我在每个类中添加了一个静态方法,该方法将生成一个Expression来搜索该特定类,以及它如何查找Person类:

public static System.Linq.Expressions.Expression<Func<Person, bool>> GetFilter(string searchTerm)
{
    if (String.IsNullOrWhiteSpace(searchTerm))
    {
        return null;
    }
    var parameterExpression = System.Linq.Expressions.Expression.Parameter(typeof(Person));
    System.Reflection.MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });

    return System.Linq.Expressions.Expression.Lambda<Func<Person, bool>>(
            System.Linq.Expressions.Expression.OrElse(
                System.Linq.Expressions.Expression.Call(
                    System.Linq.Expressions.Expression.PropertyOrField(parameterExpression, "GivenName"),
                    method,
                    System.Linq.Expressions.Expression.Constant(searchTerm, typeof(string))
                ),
                System.Linq.Expressions.Expression.Call(
                    System.Linq.Expressions.Expression.PropertyOrField(parameterExpression, "Surname"),
                    method,
                    System.Linq.Expressions.Expression.Constant(searchTerm, typeof(string))
                )
            ), parameterExpression);
}

并尝试构建一个Expression来检查客户的类型,然后进行适当的数据检查,但在这里我难倒......这就是我现在所拥有的:

    var parameterExpression = System.Linq.Expressions.Expression.Parameter(typeof(Customer));
    var typeIsPerson = System.Linq.Expressions.Expression.TypeIs(parameterExpression, typeof(Person));
    var typeIsCompany = System.Linq.Expressions.Expression.TypeIs(parameterExpression, typeof(Company));

    var q = System.Linq.Expressions.Expression.Block(
        System.Linq.Expressions.Expression.IfThen(typeIsPerson, Person.GetFilter(searchTerm)),
        System.Linq.Expressions.Expression.IfThen(typeIsCompany, Company.GetFilter(searchTerm)),
        System.Linq.Expressions.Expression.Constant(false));

    var a = System.Linq.Expressions.Expression.Lambda<Func<Customer, bool>>(
            q, parameterExpression);

这里我有两个问题(至少?),首先当我尝试调用Count时,我得到了一个非常不愉快的NotSupportedException异常,表示Unknown LINQ expression of type 'Block'。第二个是我不知道如何返回每个GetFilters的执行结果,我怀疑我会得到false任何记录,因为它是最后一个默认值Expression中的Block ... 可能是我走错了路,这应该以完全不同的方式完成?

2 个答案:

答案 0 :(得分:2)

LINQ to Entities中通常不支持表达式块。通常你不需要它们,因为你可以使用C#条件运算符? :构建几乎任何表达式(映射到Expression.Condition)。

但在尝试动态构建表达式之前,您需要找到一个EF支持的构造,使用TPT(以及其他EF继承模型)多态查询。这并不容易,因为所有示例都使用OfType方法,该方法仅在您需要过滤具体派生实体时才适用。经过一些试验和错误,幸运的是有两个受支持的构造 - isas(重要:as,而非强制转换!)。

所以静态构建的谓词表达式可能是这样的:

Expression<Func<Customer, bool>> predicate = c =>
    c is Person ? 
        ((c as Person).GivenName.Contains(searchTerm) || (c as Person).Surname.Contains(searchTerm)) :
    c is Company ? 
        (c as Company).Name.Contains(searchTerm) :
    false;

(坦率地说,你不想看看生成的SQL,但它有效)

现在您可以根据需要动态构建它。您已找到is表达式方法(Expression.TypeIs),对于as运算符,相应的表达式metod为Expression.TypeAs

答案 1 :(得分:0)

您不需要做所有这些。只需创建一个通用方法,在调用它时关闭。您的通用方法可以是这样的:

public static int Count<T>(Expression<Func<T, bool>> filter = null)
{
    var ctx = new StackContext();
    return ctx.Customers.OfType<T>().Where(filter).Count();
}

您可以这样称呼:

// Here you are closing the generic to be of type Person
var personsCount = Count<Person>(x => x.GivenName == "George");
// Here you are closing the generic to be of type Customer
var companyCount = Count<Company>(x => x.Name == "Test");