Repositories, contexts and performance

Jun 1, 2013 at 4:44 PM
In ASP.NET MVC NO, SQL Server-backed project, I'm using injected repositories (SimpleRepository and derived from AbstractFactoryAndRepository) or (ObjectQuery)Container.Instances<type>()).Context.

My concern is with the way LINQ operators (Where, FirstOrDefault) are executed.
When I use LINQ for SQL (with a dbContext), I assume the LINQ expression is translated to an SQL query which in turn is executed optimally using the indexes defined on the involved tables.
With the repository or context, I'm not sure this is the case.

I guess that what I want to hear is either of the following:
  1. As long as the EF data provider is SQL Server, Dynamic LINQ queries are executed against the server, which use its optimizations.
  2. There's a LINQ for SQL port for NO/RO.
Thanks,
Ury
Coordinator
Jun 2, 2013 at 10:19 AM
Ury

Your posting appears to confuse a number of different terms, but I think I can tell you what you want to know.

When you write a LINQ query on Container.Instances<T>() (whether directly in your code, or via one of the helper methods in AbstractFactoryAndRepository) then Naked Objects simply passes this query on to Entity Framework (i.e. as 'LINQ to Entities'), which compiles the query tree in an efficient manner and executes it as sql instructions to the database. This is the correct way to do querying, for a high performance enterprise systems. Naked Objects does not touch the query. The query executes just as efficiently as if you had written a native .NET application that invoked the query on the appropriate DbSet.

'LINQ to SQL' ia a different technology, which was developed by an independent group at Microsoft (apparently the two groups didn't know about each other - an interesting story!) It is pretty clear that Microsoft sees LINQ to Entities as the strategic option. We have no plans to build a LINQ to SQL implementation, for that reason. If your concern is about the relative efficiency of LINQ to Entities and LINQ to SQL, that question is best directed to the Microsoft forums. I recommend, though, that you read Julie Lerman's book on EF, if you haven't already.

'Dynamic LINQ' is something altogether different - see http://nuget.org/packages/DynamicLINQ/. It is a library to allow you to build LINQ queries dynamically, at run-time, using strings. It is nothing to do with how/where the query is executed. Naked Objects uses Dynamic LINQ only for the methods DynamicQuery and DynamicQueryWithType on AbstractFactoryAndRepository - which are intended to allow a power-user to enter a custom query definition as a string via the UI, and execute it. Dynamic LINQ seems to be a bit of a dead project though - it has not been maintained much by Microsoft. It makes for a nice demo, but I can't say I've ever used it for real on an enterprise system. Ordinary LINQ to Entities queries, written on Container.Intances<T>() do not use Dynamic LINQ.

I recommend against accessing the context directly (e.g. via (ObjectQuery)Container.Instances<type>()).Context. ) unless you absolutely need to - e.g. to invoke a stored proc. It is no more efficient for ordinary queries.

The only overhead added by going through Container.Instances() is if you are working with multiple DbContexts, in which case NO has to figure out which DbContext to forward the query on to. For people who do a lot of this, we recently introduced the AssociateTypes method, to make that look-up more efficient - but this is of no relevance if you have a single DbContext. Also, that was only ever an overhead before the query was executed: there was no impact on the efficiency of query execution.

Hope that helps.

Richard
Jun 2, 2013 at 1:37 PM
Edited Jun 2, 2013 at 1:37 PM
Richard - thanks again! You sorted things out for me.
I just happened to fall on DynamicQuery and I guess I extrapolated too much.
LINQ for Entities will hopefully do its work for me (find an entry in an indexed 100K-row table), but how do I get to it without DynamicQuery?
When you say:
I recommend against accessing the context directly (e.g. via (ObjectQuery)Container.Instances<type>()).Context. )
do you mean I shouldn't use (or abuse) it within the repository (derived from AbstractFactoryAndRepository)?
If so, I guess there's a third option I missed. I'll try to illustrate what I mean with an example:
  • A "root" entity of my model is a Customer, and therefore I use an injected Customer repository in my services.
  • A Customer has a list of Folders.
  • A Folder contains sub-folders (of type Folder) and a list of Contents.
Now, suppose I receive a request for a content, with an identifier of it (it's "name", which is not the Content's primary key). Now I need to look for the Content without knowing to which Customer it belongs or in which Folder it resides.

What are my options?
  • With pre-historic SQL, I simply added an Index on Content.Name to the table, and no matter how big the Contents table became, I couldn't care less (insertion is not an issue).
  • With "plain" LINQ for Entities, I hope that myContext.Contents.FirstOrDefault(c => c.Name == name) is just as efficient.
  • With the Customer repository... ?
Should I just create a repository for each set I need to query (i.e. ContentRepository.Instances().FirstOrDefault...)? I might be under the (wrong?) impression that I should keep the injected services to a minimum (or limit it to the "root" entities in the main menu). I'm not feeling very comfortable with injecting a lot of repositories to a lot of services.

What am I missing here?
Coordinator
Jun 3, 2013 at 8:04 AM
Edited Jun 3, 2013 at 8:18 AM
Ury

I think you have formed a mental model of what's going on that is more complex than the reality - which is very simple.
I might be under the (wrong?) impression that I should keep the injected services to a minimum
  • There is virtually no overhead to creating and/or injecting services, whether repositories or not - so you can have as many as you want. That said, I wouldn't normally want to end up with a large number of them - for style and code management reasons.
  • You don't have to use a Repository at all. In any object or service you can just do a query for any type of object via an injected Container e.g.:
Container.Instances<Content>().FirstOrDefault(c => c.Name == name) 
This is no less efficient than any other way,
  • This is an efficient way of doing it. Adding an Index in the dB on that column should improve performance, but that is a transparent process - and you can add such refinements when they prove necessary.
  • Repositories are not restricted to one type. You can write a CustomerRepository service and add actions to it for retrieving Products also - there's no loss/gain in efficiency. Splitting up query methods into multiple repositories is merely in the interests of keeping code intelligible and manageable. Don't be mislead by the SimpleRepository<T> - that is just a mechanism to give you a repository with some ready made methods very simply. It is only really intended for early stage prototyping.
  • There are three reasons (in no particular order) for putting a query method on an injected repository rather than on the domain object itself:
i. Because you want that query action to be available to the user on the Main Menu i.e. to be able to retrieve an object without coming from another object.
ii. Because you want to re-use the same query programmatically from several different places. (Note that with the power of deferred-execution - you can return an IQueryable<T> that has done some filtering and then filter it further within the method that calls it - all very efficiently.
iii. Because you want to make it easy to mock-out the querry functioinality for unit testing (though you can do that just by mocking out the Container also).

Hope that helps. Two further recommentations:
  1. You need to read up more on EF and build your understanding of how that works. It is really quite elegant.
  2. Have a thorough browse of our AdventureWorks sample app - and particularly the repositories.
Jun 3, 2013 at 1:49 PM
This was very clear and helpful, and I'll follow.
...and I'll try to keep up with technology :)