EF 4.3.1 и EF 5.0 DbSet.Local медленнее, чем фактический запрос к базе данных

У меня есть база данных с таблицей около 16 500 городов и модель данных EF (Database-First) для этой базы данных. Я предварительно загружаю их в память с помощью кода:

Db.Cities.Load()

... тогда, когда пришло время их использовать, я пробовал каждый из следующих запросов:

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

Первый запрос выполняется быстро (~ 10 мс), но второй занимает около 2,3 секунды для запуска в первый раз (хотя он быстрее, чем первый запрос, когда он вызывал после этого).

Это не имеет смысла, потому что SQL Server Profiler проверяет, что первый запрос попадает в базу данных на другой машине, а второй - нет!

Я попытался отключить Db.Configuration.AutoDetectChangesEnabled, и я попытался предварительно создать представления.

Что делать, чтобы сделать .Local быстрее? (Не все клиенты, запускающие это приложение, будут находиться в быстрой локальной сети.)

Ответы

Ответ 1

Почему бы вам просто не сохранить список строк из первого запроса и использовать это вместо этого.

List<string> cities = db.Cities.Select( x=>x.CityName).ToList();

Локальный может быть медленнее из-за выбора, который может выполнять некоторые проверки согласованности.

Ответ 2

Я использовал источник Local для источника Resharper. Сначала вы увидите вызов DetectChanges, который, вероятно, не является вашей проблемой, если все, что вы используете, это три строки. Но затем EF создает новый ObservableCollection для Local и заполняет его по элементам. Любой из них может быть дорогостоящим при первом вызове.

Запрос непосредственно против DbSet будет маршрутизироваться в поставщиков баз данных EF, которые, я уверен, напрямую получают доступ к внутреннему локальному кешу.

Ответ 3

Следующий метод расширения вернет IEnumerable<T>, содержащий локальные кешированные объекты DbSet, без служебных издержек запуска, вызванных тем, что метод DbSet.Local() обнаруживает изменения контекста и создает объект ObservableCollection<T>.

<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

Вызов .QuickLocal на DbSet, содержащий 19 679 объектов, занимает 9 мс, тогда как вызов .Local занимает 2121 мс при первом вызове.