Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EF 4.3.1 and EF 5.0 DbSet.Local is slower than an actual Database query

I've got a database with a table of about 16,500 cities, and an EF Data Model (Database-First) for that database. I preload them into memory with the code:

Db.Cities.Load()

...then when it's time to use them, I've tried each of the following queries:

Dim cities() As String = Db.Cities.Select(Function(c) c.CityName).ToArray

Dim cities() As String = Db.Cities.Local.Select(Function(c) c.CityName).ToArray

The first query is fast (~10ms), but the second one takes about 2.3 seconds to run the first time (although it's faster than the first query when it's called after that).

This doesn't make sense because SQL Server Profiler verifies that the first query is hitting the database on another machine, but the second isn't!

I've tried turning off Db.Configuration.AutoDetectChangesEnabled, and I've tried pre-generating the views.

What can I do to make .Local faster? (Not all clients running this application are going to be on a fast LAN.)

like image 773
MCattle Avatar asked Aug 31 '12 22:08

MCattle


2 Answers

I walked the source for the Local property using Resharper's handy feature. You'll first see a call to DetectChanges which probably isn't your issue if all you're running is the above three lines. But then EF creates a new ObservableCollection for Local and fills it item by item. Either of those can be costly on the first call.

The query directly against the DbSet will route into the EF database providers which I'm sure directly access the internal local cache.

like image 83
N Jones Avatar answered Oct 17 '22 13:10

N Jones


The following extension method will return an IEnumerable<T> containing the DbSet's local cached entities without the startup overhead incurred by the DbSet.Local() method detecting context changes and creating an ObservableCollection<T> object.

<Extension()>
Public Function QuickLocal(Of T As Class)(ByRef DbCollection As DbSet(Of T)) As IEnumerable(Of T)
    Dim baseType = DbCollection.[GetType]().GetGenericArguments(0)
    Dim internalSet = DbCollection.GetType().GetField("_internalSet", Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance).GetValue(DbCollection)
    Dim internalContext = internalSet.GetType().GetProperty("InternalContext").GetValue(internalSet, Nothing)
    Return DirectCast(internalContext.GetType.GetMethod("GetLocalEntities").MakeGenericMethod(baseType).Invoke(internalContext, Nothing), IEnumerable(Of T))
End Function

Calling .QuickLocal on a DbSet containing 19,679 entities takes 9 ms, whereas calling .Local takes 2121 ms on the first call.

like image 6
MCattle Avatar answered Oct 17 '22 15:10

MCattle