4

I've implemented a generic repository for Entity Framework 4. Here's a dumbed down version, where AllAppContainer is the EF4 object context:

public class Repository<T> where T : class
{
    protected AllAppContainer objectContext;
    protected ObjectSet<T> entitySet;

    public Repository()
    {
        objectContext  = new AllAppContainer();
        entitySet = objectContext.CreateObjectSet<T>();
    }

    public int QueryCount(Func<T, bool> predicate)
    {
        int queryCount = entitySet.Count(predicate);
        return queryCount;
    }
}

The one method is QueryCount(), which I want to act as a select Count(*) ... where line of SQL (not returning the actual records).

Straight-forward? You'd think... First, let's do a non-Repository version of the same thing, performing a count on Item entities:

AllAppContainer allAppContainer = new AllAppContainer();
int nonRepCount = allAppContainer.Items.Count(item => item.Id > 0);

SQL Server Profiler says the generated SQL is:

SELECT 
[GroupBy1].[A1] AS [C1]
FROM ( SELECT 
    COUNT(1) AS [A1]
    FROM [dbo].[Items] AS [Extent1]
    WHERE [Extent1].[Id] > 0
)  AS [GroupBy1]

Woo-hoo! Score!

Now let's call the same using my Repository QueryCount:

Repository<Item> repository = new Repository<Item>();
int repCount = repository.QueryCount(item => item.Id > 0);

Here's the generated SQL:

SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[SmallField] AS [SmallField]
FROM [dbo].[Items] AS [Extent1]

Yep, EF is returning the full set of data, then calling Count() on it in-memory.

For fun I tried changing the relevant line in Repository QueryCount to:

int queryCount = new AllAppContainer().CreateObjectSet<T>().Count(predicate);

and the non-repository line to:

int nonRepCount = allAppContainer1.CreateObjectSet<Item>().Count(item => item.Id > 0);

but the generated SQL for each is the same as before.

Now why would all this repository-returns-all-matching-records-then-counts be happening, when it doesn't for non-repository? And is there any way to do what I want via my generic repository i.e. count at db. I can't take the in-memory count performance hit.

user603563
  • 374
  • 3
  • 15

1 Answers1

7

you need to use Expression<Func<TSource, bool>> predicate for your Count otherwise the framework uses the Enumerable.Count<TSource> Method (IEnumerable<TSource>, Func<TSource, Boolean>) which gets the whole collection from DB to be able to call for each item, so your method should be:

public int QueryCount(Expression<Func<T, Boolean>> predicate)
{
    int queryCount = entitySet.Count(predicate);
    return queryCount;
}
Kris Ivanov
  • 10,476
  • 1
  • 24
  • 35
  • 1
    +1 see also http://stackoverflow.com/questions/4855399 and http://stackoverflow.com/questions/2675536 - I would almost call this a bug. – BlueRaja - Danny Pflughoeft Feb 15 '11 at 18:41
  • Wow, I'm not at work now, but I'll be running in tomorrow to check this out. Thanks for the fast response K. And thanks for the pointers BlueRaja - the frustrating thing was not knowing the terms to search for on this problem: mine and the other two questions have completely different titles/subjects, but apparently stem from the same thing. – user603563 Feb 15 '11 at 22:55
  • 2
    @BlueRaja: What do you want to call a bug? Not fully understanding difference between IEnumerable and IQueryable is not a bug. It is a key knowledge you must have to correctly use Linq. – Ladislav Mrnka Feb 16 '11 at 00:06
  • 1
    @Ladislav: This has nothing to do with the difference between `IQueryable` and `IEnumerable` - this is a bug in the Entity Framework. I have yet to meet one person who would expect at first glance for `.Where(MyFunc)` and `.Where(o => MyFunc(o))` to have such completely different behavior. When an API is incredibly and needlessly counter-intuitive like that, I call it a bug. – BlueRaja - Danny Pflughoeft Feb 16 '11 at 02:33
  • 2
    @BlueRaja: I don't agree. Moreover it has nothing to do with EF. – Ladislav Mrnka Feb 16 '11 at 07:09