无需将 DbContext 作为参数传递的 IQueryable 扩展方法

IQueryable extension method without passing the DbContext as a parameter

这个有效

var invoices = this.myContext.FilterByCustomer(this.myContext.Invoices, customerId);

实现为:

public partial class MyContext : DbContext
{
    public IQueryable<T> FilterByCustomer<T>(IQueryable<T> queryableEntityCollection, int customerId) where T : class, ICustomerEntity
    {
        // I need to query entities from MyContext here
        // This implementation already works
    }
}

但我想要这个

var invoices = this.myContext.Invoices.FilterByCustomer(customerId);

如果我在 IQueryable (DbSet) 上实现扩展方法,似乎我必须将 MyContext 作为参数传递,我不喜欢这样。

public static IQueryable<T> FilterByCustomer<T>(this IQueryable<T> queryableEntityCollection, MyContext context, int customerId) where T : class, ICustomerEntity
{
    // I need to query entities from MyContext here
    // This WOULD work, I would be able to query other tables on 'context', but I don't like passing the context as parameter here
    // I don't want this implementation
}

如何实现不需要我将上下文作为参数传递的 IQueryable 扩展?

public IQueryable<T> FilterByCustomer<T>(IQueryable<T> queryableEntityCollection, int customerId) where T : class, ICustomerEntity
{
    // I need to query entities from MyContext here, without passing MyContext as a parameter
    // I want such implementation
}

这可能吗?

Is that possible at all?

是的。

Invoice 应该是 DbSet<TEntity> 派生自 IQueryable<TEntity> 所以:

public static IQueryable<T> FilterByLogin<T>(
  this IQueryable<T> query, 
  int customerId) 
  where T : ICustomerEntity
{
  var result = query.Where(cu => cu.CustomerId == customerId);

  return result;
}

接口至少有:

public interface ICustomerEntity
{
  public int CustomerId { get; }
}

用法:

var customers = this.myContext.Invoices
  .FilterByLogin(customerId)
  .ToList();

哪里

public class Invoice : ICustomerEntity
{
  // etc
}

HOWEVER, the implementation detail queries other entities from the context, so an instance of the context is required in the method.

是的(有点,扩展方法然后脱离了 dbcontext),但它很丑陋:

public static IQueryable<T> FilterByLogin<T>(
  this MyContextType context
  Func<IQueryable<T>> query, 
  int customerId) 
  where T : ICustomerEntity
{
  var result = query(context)
    .Where(cu => cu.CustomerId == customerId);

  return result;
}

用法:

var customers = this.myContext
  .FilterByLogin(c => c.Invoices, customerId)
  .ToList();

这很丑陋,因为在此语句中返回的内容并不十分清楚。

不要硬塞进扩展方法中。它看起来像一个存储库模式或任何你想命名的。

因此,无论如何您都需要将其拆分成自己的 class。然后你也可以注入 DbContext,像这样使用它:

public class CustomerRepository<TCustomer>
    where TCustomer : class, ICustomerEntity
{
    public CustomerRepository(IYourContext context)
    {
        _context = context;
    }

    public IQueryable<TCustomer> FilterByCustomer(int customerId) 
    {
        var customer = _context.Customers.Where(...);

        var anotherEntity = _context.OtherEntities.Where(...);
    }
}

或者,您将必要的 IQueryable<T> 注入 class:

public class CustomerRepository
{
    public CustomerRepository(IQueryable<Customer> customers, IQueryable<OtherEntity> otherEntities)
    {           
        _customers = customers;
        _otherEntities = otherEntities;
    }

    public IQueryable<TCustomer> FilterByCustomer(int customerId) 
    {
        var customer = _customers.Where(...);

        var anotherEntity = _otherEntities.Where(...);
    }
}

您可以这样做,但它确实依赖于传递上下文,但您不必传递集合。这假设您正在过滤根 DbSet<T> 而不是已经过滤的 IQueryable 实例。

public static IQueryable<T> FilterByCustomer<T>(this DbContext context, int customerId) where T : class, ICustomerEntity
{
    var queryableEntityCollection = context.Set<T>();
    // rest of code that filters and returns something
}

称呼它:

this.myContext.FilterByCustomer<Invoice>(customerId);

如果您真的想直接在 DbSet 上执行此操作并从该 DbSet 获取 DbContext,请参阅前面的 question/answer。 Can you get the DbContext from a DbSet?