3

This works

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

It is implemented as:

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
    }
}

But I want this

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

If I implement an extension method on the IQueryable (DbSet), seems like I have to pass the MyContext as a parameter, which I don't like.

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
}

How can I implement an IQueryable extension which doesn't require me to pass the context as parameter?

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?

hyankov
  • 4,049
  • 1
  • 29
  • 46

3 Answers3

2

You could do this but it does rely on passing in the context BUT you do not have to pass in the collection. This assumes that you are filtering on the root DbSet<T> and not an already filtered IQueryable instance.

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
}

Call it:

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

If you really want to do this on a DbSet directly and get the DbContext from that DbSet then see this previous question/answer. Can you get the DbContext from a DbSet?

Community
  • 1
  • 1
Igor
  • 60,821
  • 10
  • 100
  • 175
1

Is that possible at all?

Yes.

Invoice should be a DbSet<TEntity> which derives from IQueryable<TEntity> so:

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;
}

where the interface has a minimum of:

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

Usage:

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

where

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.

Yes (sorta, the extension method is then off the dbcontext), but it's ugly:

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;
}

Usage:

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

It's ugly because the it's not exactly clear in this statement what is returned.

Erik Philips
  • 53,428
  • 11
  • 128
  • 150
  • 1
    Sorry, maybe I didn't make the question clear enough: I have already implemented it as you describe, HOWEVER, the implementation detail queries other entities from the context, so an instance of the context is required in the method. – hyankov Feb 10 '17 at 15:53
  • You just asked how to do it without the context, then in this comment you state the context is required... which is it? – Erik Philips Feb 10 '17 at 15:54
  • 1
    I have never said that the context is not required. Quite the opposite, my question asks how to structure the extension method without the necessity of PASSING the context (I still need it). – hyankov Feb 10 '17 at 15:55
  • Updated per your request. – Erik Philips Feb 10 '17 at 15:58
  • OK, saw it, thanks. It is one idea better than my current implementation, because at least I don't have to do twice `this.myContext`, which is error-prone. – hyankov Feb 10 '17 at 16:02
-1

Don't shoehorn this into an extension method. It looks like a repository pattern or whatever you want to name it.

So you'll need to separate it into its own class anyway. Then you can either inject the DbContext as well, using it like this:

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(...);
    }
}

Or alternatively, you inject the necessary IQueryable<T>s into the 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(...);
    }
}
CodeCaster
  • 147,647
  • 23
  • 218
  • 272