Entity Framework: Re-finding objects recently added to context

Entity Framework

Entity Framework Problem Overview


I am using the entity framework and I'm having a problem with "re-finding" objects I just created... basically it goes like this:

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???");
  }
}

(no guarantee the code works btw - it's all from my head)

Why doesn't the query "find" the EntityObject that was just created?

If I call SaveChanges() after the AddToEntityObject it works (which doesn't surprise me) but why doesn't it pull from the cache properly?

I'm still green on this stuff so I'm hoping that there's some really easy thing that I'm just overlooking...

Thanks

Entity Framework Solutions


Solution 1 - Entity Framework

The newly added object is in the local DataSource, since it's not persisted yet in the database,

so you may say:

EntityObject search = ents.EntityObject.FirstOrDefault(o => o.Id == theId) ??
                      ents.EntityObject.Local.FirstOrDefault(o => o.Id == theId);

Solution 2 - Entity Framework

This happens because ents.EntityObject.WhatEver always queries the datasource. This is a design decision. They do it this way, because else they would have to execute the query against the datasource, against the local cache and then merge the results. As one of the developers pointed out in a blog (cannot remember where exactly) they were unable to handle this consistently.

As you can imagine there are a lot of corner an edge cases you have to handle properly. You could just find a id you created locally, created by someone else in the database. This would force you to be prepared to handle conflicts on (almost) every query. Maybe they could have made methods to query the local cache and methods to query the datasource, but that is not to smart, too.

You may have a look at Transparent Lazy Loading for Entity Framework. This replaces the normal code generator and you get entities that populate their related entity collections and entity references automatically on access. This avoids all the

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

code fragments. And you can query the collections because they are always implicitly loaded. But this solution is not perfect, too. There are some issues. For example, if you create a new entity and access a collection of related entities, you will get an exception because the code is unable to retrieve the related entities from the database. There is also an issue concerning data binding and may be some more I am not aware of.

The good thing is that you get the source code and are able to fix the issues yourself and I am going to examine the first issue if I find some time. But I am quite sure that it will not be that easy to fix, because I expect some case were just not hitting the database if the entity has just been created is not the expected behavior.

Solution 3 - Entity Framework

I was in the same situation. I wrote this extension method that at least for me solves the problem (I don't have issues with i.e conflicts in my context...)

    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);
    }

Solution 4 - Entity Framework

The extension method bellow is to 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;
}

How to use:

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

btw: I love generics!

Solution 5 - Entity Framework

I have recently struggled with this same question. I'm posting this answer 2 years after the question was asked in hopes that this bit of code may help someone searching for an answer.

I have basically implemented an extension method (as suggested by Alex James) called "Find" that operates in the same way that "Where" does, but "Find" also checks the ObjectContext to see if there are any Added entities that satisfy the given predicate. This allows you to find an entity even if it hasn't been saved to the database yet.

Find returns an IQueryable(of T) so that you can use it just like any other LINQ operator.

<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

Solution 6 - Entity Framework

You have a number of options. You could extend the ObjectContext with another partial class to make your own mechanism for retrieving recently Added information.

Or you could just put an extension method on the ObjectContext that looks through the ObjectContext.ObjectStateManager looking for 'added' ObjectStateEntries, and then use LINQ to Objects to find what you are looking for.

Solution 7 - Entity Framework

Entity Framework 6

As per EF Docs Dbset always query against the database.

> Note that DbSet and IDbSet always create queries against the database > and will always involve a round trip to the database even if the > entities returned already exist in the context. A query is executed > against the database when: > > It is enumerated by a foreach (C#) or For Each (Visual Basic) > statement. It is enumerated by a collection operation such as ToArray, > ToDictionary, or ToList. LINQ operators such as First or Any are > specified in the outermost part of the query. The following methods > are called: the Load extension method on a DbSet, > DbEntityEntry.Reload, and Database.ExecuteSqlCommand. When results are > returned from the database, objects that do not exist in the context > are attached to the context. If an object is already in the context, > the existing object is returned (the current and original values of > the object's properties in the entry are not overwritten with database > values). > > When you perform a query, entities that have been added to the context > but have not yet been saved to the database are not returned as part > of the result set. To get the data that is in the context, see Local Data > > If a query returns no rows from the database, the result will be an > empty collection, rather than null.

Below is a simple snippet with local data:

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();

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiondovholukView Question on Stackoverflow
Solution 1 - Entity FrameworkRadyView Answer on Stackoverflow
Solution 2 - Entity FrameworkDaniel BrücknerView Answer on Stackoverflow
Solution 3 - Entity FrameworkJan ChristensonView Answer on Stackoverflow
Solution 4 - Entity FrameworkAlexandreView Answer on Stackoverflow
Solution 5 - Entity FrameworkCasey WilkinsView Answer on Stackoverflow
Solution 6 - Entity FrameworkAlex JamesView Answer on Stackoverflow
Solution 7 - Entity Frameworkpritesh agrawalView Answer on Stackoverflow