Compartir a través de


“Local” Queries

Quite some time ago I wrote a blog post about the fact that EF queries execute at the database rather than locally which means that if you add an object to the context or you modify an object already attached to the context, then a query won’t be based on the local data.

Earlier today another blogger called this a “disappointing behavior” . Since I hate for the EF to disappoint folks, I thought I’d whip up a small extension method to help:

public static IEnumerable<T> Local<T>(this ObjectContext context,

                                     string entitySetName) where T : class

{

   return from stateEntry in context.ObjectStateManager

                            .GetObjectStateEntries(EntityState.Added |

              EntityState.Modified |

                                                           EntityState.Unchanged)

           where stateEntry.Entity != null && stateEntry.EntitySet.Name == entitySetName

  select stateEntry.Entity as T;

}

To use this, you just need to supply the type and the entity set name:

foreach (Customer c in ctx.Local<Customer>("Customers"))

{

    Console.WriteLine(c.ContactName);

}

In .Net 4.0 we will be adding a new type ObjectSet<T> which inherits from ObjectQuery<T> and provides methods for things like adding, attaching, deleting, etc. entities to the entity set which it represents. The default codegen will put instances of ObjectSet<T> on the context rather than ObjectQuery<T>. So, once .Net 4.0 ships we can change the extension method to:

public static IEnumerable<T> Local<T>(this ObjectSet<T> objectSet) where T : class

{

   return from stateEntry in objectSet.Context.ObjectStateManager

  .GetObjectStateEntries(EntityState.Added |

                                                              EntityState.Modified |

                                                              EntityState.Unchanged)

           where stateEntry.Entity != null && stateEntry.EntitySet == objectSet.EntitySet

           select stateEntry.Entity as T;

}

And then you won’t need to specify either the type or the entity set name but rather call the extension method on the object set so that you get a nicer usage pattern:

foreach (Customer c in ctx.Customers.Local())

{

    Console.WriteLine(c.ContactName);

}

- Danny

Comments

  • Anonymous
    February 20, 2009
    PingBack from http://www.anith.com/?p=12335

  • Anonymous
    February 22, 2009
    Danny Simmons posted small helper method for runnning queries against local cache (in ObjectContext).

  • Anonymous
    February 22, 2009
    Hey Danny Is checking the entitySet name really necessary? Isn't comparing the type sufficient? if (entry.Entity != null && entry.Entity.GetType() == typeof(T)) {     entries.Add((T)entry.Entity); } regards, Jowen

  • Anonymous
    February 23, 2009
    @Jowen, For many scenarios comparing against the type is sufficient, but it's not really sufficient in all cases.  The issue is that the EF supports the ability to have multiple entitysets for the same type.  Normally the wizard which generates models from the database and the EF designer will not ever produce a model with more than one entityset for the same type, but they can be produced by hand, and in some scenarios are very important. So the extensions I've written above work on any EF model, even one with multiple entitysets for the same type, but you could change the extension to be like you describe and thus get rid of the need to pass an entityset name in as an argument--as long as you are sure that you will always only run with models which have one entityset pre type.

  • Danny
  • Anonymous
    February 23, 2009
    Ok, thats what I thought. Tnx for the clear explanation!

  • Anonymous
    March 31, 2009
    The extension method Local() will still be required in 4.0 ? The ObjectSet won't include local changes (like added entities) ? If it allow add, remove operations, I thought it would consider the local changes for future queries.  I also find this a disappointing behavior. Thanks.

  • Anonymous
    March 31, 2009
    The comment has been removed

  • Anonymous
    April 02, 2009
    Thanks for your answer. What I find incoherent is: If you use query (default MergeOption) you get the current values of modified / deleted entities, but you don`t see the one added.  You are half local, half remote.   Why isnt there a MergeOption that allow you to see the locally added entities, like it is possible to see the current modified values ?  What I would have expected is that added entities that fit the query would also be returned.  I am concerned about performance if we need to filter out added entities from the state manager, there could be a lot of entities there all mixed up(types)... Again thanks for your answer.

  • Anonymous
    April 02, 2009
    @Frederic, The default MergeOption (AppendOnly) gives the standard identity resolution pattern which is common to many object/relational systems.  This is a little counter-intuitive at first, but there is a lot to be said for it when you get used to it. One reason we can't create a merge option for local queries is that merge option doesn't affect the way the query is executed--only the way that values are merged with entities already found in the context (or not merged at all in the case of NoTracking).  The merge option isn't even considered until after the query has been executed. Another reason is that not every kind of query that can be executed against the database can be executed locally.  Locally all we have is LINQ to Objects.  We don't have Entity SQL or the various kinds of database functions and things which are available on the server.  Further, some aspects of LINQ to Objects semantics are different from the way queries run on the server (things like the handling of nulls come to mind). So the Local mechanism above can be useful for some cases, but it's not interchangeable with real database queries which is why we want to explicitly opt-in to it when appropriate.

  • Danny
  • Anonymous
    June 19, 2013
    Would further queries against Customers.Local() make trips to the database (assuming LazyLoading is disabled)? For example, would Customers.Local().Addresses.ToList() (assuming there's a navigation property to an address table) just look within the local customer's data? I would assume that it would, and if LazyLoading is disabled you might get an exception if you haven't manually loaded the address data. Is that correct?