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 мс при первом вызове.