将linq作为参数传递给实体查询,并针对不同的对象执行

时间:2014-11-25 14:23:28

标签: c# linq entity-framework linq-to-entities

我正在寻找实现缓存,以便检查是否已经执行了特定查询,如果已经执行,则使用内存缓存返回数据。我可以利用EntityFramework通过DBSet的Local属性公开的内存视图,而不是实现缓存本身。

因此,我想将整个查询传递给一个方法,该方法将针对数据库和/或内存缓存运行它。但是,我让我的Expression / Func / IQueryable混淆了,并且无法看到如何传递查询/表达式:

所以基类会是这样的(这显然还没有......):

public abstract class ServiceBase<TEntity> where TEntity : class
{
    Entities _context; //instantiated in the constructor

    protected List<TEntity> GetData<TEntity>(Expression<Func<TEntity, TEntity>> query)
    {
        if (!HasQueryExecuted(query))
        {
            _context.Set<TEntity>().Select(query).Load();
            AddQueryToExecutedList(query); 
        }
        return _context.Set<TEntity>().Local.AsQueryable().Select(query).ToList();
    }
}

我会有多个类来定义他们的查询:

public class CustomersService : ServiceBase<Customer>
{
    public List<Customer> GetCustomersWithOrders()
    {
        return GetData(c => c.Where(c => c.OrderCount > 0));
    }

    public List<Customer> GetLargestCustomersByOrder(int TopCount)
    {
        return GetData(c => c.OrderBy(c=>c.OrderCount).Take(TopCount));
    }
}
public class ProductsService : ServiceBase<Product>
{
    public List<Customer> GetAllProducts()
    {
        return GetData(p => p);
    }
    public List<Customer> GetMostOrderedProducts(int minimumOrders)
    {
        return GetData(p => p.Where(p=> p.OrderCount > minimumOrders)
                   .OrderByDescending(p=>p.OrderCount));
    }

    public List<Customer> GetAllProducts()
    {
        return GetData(p => p);
    }
}

这些只是人为的例子,但重点是查询可以变化,不仅限于where子句,而是利用IQueryable上的所有标准扩展方法来查询底层数据。

首先,这可能吗? GetData方法可以定义一个可以接受任何查询的参数吗?在上面的例子中,我声明query参数接受与IQueryable扩展Select方法相同的类型,但是当我尝试使用简单的Where子句或OrderBy子句调用它时,我会遇到各种编译错误,例如as:

无法隐式转换类型&#39; bool&#39;到&#39; Entities.Customer

无法隐式转换类型&#39; System.Linq.IOrderedEnumerable&#39;到&#39; Entities.Customer&#39;

然而,如果我直接针对上下文运行相同的查询,它编译得很好。那么我做错了什么?

1 个答案:

答案 0 :(得分:0)

您的问题是Expression<Func<TEntity, TEntity>> query

简单地说,你的表达式是一个函数类型,它采用TEntity并返回TEntity。由于您的课程使用Entities.Customer键入,因此预计会有一个Entities.Customer的函数并返回Entities.Customer

如果您查看服务类p => p将正常工作,但p => p.Where()不会,因为Where会返回IEnumerable。

我认为你应该采取不同的方法并利用延迟加载。

public IQueryable<TEntity> GetData<TEntity>() where TEntity : class
{
    return _context.Set<TEntity>();
}

public IQueryable<Customer> GetAllProducts()
{
    return GetData();
}

public IQueryable<Customer> GetMostOrderedProducts(int minimumOrders)
{
    return GetData().Where(p => p.OrderCount > minimumOrders)
        .OrderByDescending(p=>p.OrderCount));
}

您不需要将查询构建到GetData,因为查询可以在任何时候构建,直到它被评估为止。如果你真的想要你可以从这里返回List<T>,但你不应该这样做。

否则,这应该可以解决当前代码的问题。

protected List<TEntity> GetData<TEntity>(Func<IEnumerable<TEntity>,
                                         IEnumerable<TEntity>> query) where TEntity : class
{
    if (!HasQueryExecuted(query))
    {  
        AddQueryToExecutedList(query); 
        return query(_context.Set<TEntity>()).ToList();
    }
    return query(_context.Set<TEntity>().Local).ToList();
}