Entity Framework: повторный поиск объектов, недавно добавленных в контекст - PullRequest
35 голосов
/ 31 марта 2009

Я использую структуру сущностей, и у меня возникла проблема с "повторным поиском" объектов, которые я только что создал ... в основном это выглядит так:

string theId = "someId";

private void Test()
{
  using(MyEntities entities = new MyEntities())
  {
    EntityObject o = new EntityObject();
    o.Id = theId;
    entities.AddToEntityObject(o);
    CallSomeOtherMethod(entities);
  }
}

void CallSomeOtherMethod(MyEntities ents)
{
  EntityObject search = ents.EntityObject.FirstOrDefault(o => o.Id == theId);
  if(search == null) 
  {
    Console.WriteLine("wha happened???");
  }
}

(нет гарантии, что код работает между прочим - это все из моей головы)

Почему запрос не "находит" объект EntityObject, который был только что создан?

Если я вызываю SaveChanges () после AddToEntityObject, он работает (что меня не удивляет), но почему он не извлекает должным образом из кэша?

Я все еще зеленый на этом материале, так что я надеюсь, что есть кое-что действительно простое, что я просто упускаю из виду ...

Спасибо

Ответы [ 7 ]

23 голосов
/ 27 июня 2013

вновь добавленный объект находится в локальном источнике данных, так как он еще не сохранен в базе данных так что вы можете сказать EntityObject search = ents.EntityObject.FirstOrDefault(o => o.Id == theId) ?? ents.EntityObject.Local.FirstOrDefault(o => o.Id == theId);

18 голосов
/ 30 ноября 2012

Я был в такой же ситуации. Я написал этот метод расширения, который, по крайней мере, для меня решает проблему (у меня нет проблем с конфликтами в моем контексте ...)

    public static IEnumerable<T> WhereInclAdded<T>(this ObjectSet<T> set, Expression<Func<T, bool>> predicate)  where T : class
    {
        var dbResult = set.Where(predicate);

        var offlineResult = set.Context.ObjectStateManager.GetObjectStateEntries(EntityState.Added).Select(entry => entry.Entity).OfType<T>().Where(predicate.Compile());

        return offlineResult.Union(dbResult);
    }
18 голосов
/ 31 марта 2009

Это происходит потому, что ents.EntityObject.WhatEver всегда запрашивает источник данных. Это дизайнерское решение. Они делают это таким образом, потому что иначе им придется выполнить запрос к источнику данных, к локальному кешу, а затем объединить результаты. Как отметил один из разработчиков в блоге (не могу вспомнить, где именно), они не смогли справиться с этим последовательно.

Как вы можете себе представить, есть много угловых случаев, с которыми вы должны обращаться правильно. Вы можете просто найти идентификатор, который вы создали локально, созданный кем-то еще в базе данных. Это заставит вас быть готовым к обработке конфликтов (почти) на каждый запрос. Возможно, они могли бы создать методы для запроса локального кэша и методы для запроса источника данных, но это не слишком умно.

Вы можете взглянуть на Прозрачная отложенная загрузка для Entity Framework . Это заменяет обычный генератор кода, и вы получаете сущности, которые автоматически заполняют связанные коллекции сущностей и ссылки на сущности при доступе. Это позволяет избежать всего

if (!Entity.ReleatedEntities.IsLoaded)
{
   Entity.RelatedEntities.Load();
}

фрагменты кода. И вы можете запросить коллекции, потому что они всегда загружаются неявно. Но и это решение не идеально. Есть некоторые проблемы. Например, если вы создаете новую сущность и получаете доступ к коллекции связанных сущностей, вы получите исключение, потому что код не может извлечь связанные сущности из базы данных. Существует также проблема, связанная с привязкой данных, и, возможно, я еще не знаю о ней.

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

4 голосов
/ 12 июля 2014

Метод расширения ниже - DbSet <>

public static T TryAttach<T>(this DbSet<T> dbSet, T entity, Expression<Func<T, bool>> predicate) where T : class
{
     T found = dbSet.Local.SingleOrDefault(predicate.Compile());
     if (found == null) dbSet.Attach(entity);
     return found ?? entity;
}

Как использовать:

contextInstance.MyEntity.TryAttach(entityInstance, e => e.ID == entityInstance.ID);

Кстати: я люблю дженерики!

2 голосов
/ 03 февраля 2012

Я недавно боролся с этим же вопросом. Я публикую этот ответ через 2 года после того, как вопрос был задан в надежде, что этот фрагмент кода может помочь кому-то в поиске ответа.

Я в основном реализовал метод расширения (как предложено Алексом Джеймсом) под названием «Найти», который работает так же, как «Где», но «Найти» также проверяет ObjectContext, чтобы увидеть, есть ли какие-либо добавленные объекты, удовлетворить данный предикат. Это позволяет найти объект, даже если он еще не был сохранен в базе данных.

Find возвращает IQueryable (из T), чтобы вы могли использовать его так же, как любой другой оператор LINQ.

<Extension()>
Public Function Find(Of T As Class)(ByVal OSet As ObjectSet(Of T), _
       ByVal predicate As Expression(Of Func(Of T, Boolean))) _
       As System.Linq.IQueryable(Of T)

    'Check the object context for Added objects first.
    Dim AddedContextObjects = OSet.Context.ObjectStateManager _
                        .GetObjectStateEntries(EntityState.Added) _
                        .Select(Function(entity) entity.Entity).OfType(Of T)()


    Dim Cpredicate = predicate.Compile
    Dim MatchingObjects As New List(Of T)

    For Each TObj As T In AddedContextObjects
        If Cpredicate.Invoke(TObj) Then
            MatchingObjects.Add(TObj)
        End If
    Next

    'Now include a query to retrieve objects from the DB.
    Dim DBObjects = OSet.Where(predicate)

    If MatchingObjects.Count > 0 Then
        'We found some added objects in the context.
        'We want to return these objects as well as any Objects in DB
        'that satisfy the predicate.
        Return MatchingObjects.Union(DBObjects).AsQueryable
    Else
        'We didn't find any added objects in the context,
        'so we just return the DB query.
        Return DBObjects
    End If

End Function
1 голос
/ 18 марта 2019

Entity Framework 6

Согласно EF Docs Dbset всегда запрашивает базу данных.

Обратите внимание, что DbSet и IDbSet всегда создают запросы к базе данных. и всегда будет включать в себя поездку в оба конца в базу данных, даже если возвращенные объекты уже существуют в контексте. Запрос выполнен по базе данных, когда:

Перечисляется через foreach (C #) или For Each (Visual Basic). заявление. Это перечисляется операцией коллекции, такой как ToArray, ToDictionary или ToList. Операторы LINQ, такие как First или Any, указывается в самой внешней части запроса. Следующие методы называются: метод расширения Load на DbSet, DbEntityEntry.Reload и Database.ExecuteSqlCommand. Когда результаты возвращаются из базы данных объекты, которые не существуют в контексте привязаны к контексту. Если объект уже находится в контексте, существующий объект возвращается (текущие и исходные значения свойства объекта в записи не перезаписываются базой данных значения).

Когда вы выполняете запрос, объекты, которые были добавлены в контекст но еще не были сохранены в базе данных не возвращаются как часть из набора результатов. Чтобы получить данные в контексте, см. Локальные данные

.

Если запрос не возвращает строк из базы данных, результатом будет пустая коллекция, а не ноль.

Ниже приведен простой фрагмент с локальными данными:

await dbContext.Entity
      .Where(e => e.Title.Contains("Text"))
      .LoadAsync();

var locaEntities = dbContext.Entity.Local;

dbContext.Entity.Add(new Entity {});

// call save post atomic operation is finished.
await dbContext.SaveChangesAsync();
1 голос
/ 08 мая 2009

У вас есть несколько вариантов. Вы можете расширить ObjectContext другим частичным классом, чтобы создать собственный механизм для получения недавно добавленной информации.

Или вы можете просто добавить метод расширения на ObjectContext, который просматривает ObjectContext.ObjectStateManager в поисках «добавленного» * ​​1006 *, а затем использовать LINQ to Objects, чтобы найти то, что вы ищете.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...