Entity Framework: Concepts and Headaches. Querying

So in the first part of this life changing tutorial, I explained what lazy loading was and how it worked with the Entity Framework. Now I wanted to get into querying/loading and how it’s handled. At least at a very simplistic angle. (Simplistic is what I do best. Day-oh!) Something that has to be understood is how the Entity Framework handles the hydrating/loading of objects and how it relates to the ObjectContext. Say you have the same User/Site example from the first part and you load a site:

    using(SomeEntities context = new SomeEntities ())
    {
       //where SomeEntities  : ObjectContext
       Site neededSite = context.Site.Where(site => site.ID = siteID);
    }

Ok so what happens here? Well basically the context will check itself to see if it holding said Site and if it isn’t, it will grab it from the database. This doesn’t seem like a big deal since it’s pretty obvious it won’t have the given Site yet since the context was just created. Now let’s get a silly example going for the sake of learning.

    using(SomeEntities context = new SomeEntities ())
    {
       //where SomeEntities  : ObjectContext
       Site neededSite = context.Site.Where(site => site.ID = siteID);
       neededSite.SiteName = someString;

       if(context.Site.Any(site => siteName == neededSite.SiteName))
       {
          //return error since the name already exists in the table.
       }

    }

Now as it is, this doesn’t seem too bad right? I change the name of the site and then see if that name is taken already in the database. If these seems ok to you it just means you don’t yet understand how the Entity Framework eh… works. The first time you go to get the Site, it fills it’s Site list with the Sites from the database, then checks the list for the one you want. It will then proceed to give you a reference to the Site in that list. Next time you do a query on the Sites list, it will NOT query the database but rather it will just query the Sites list it has in memory. Next, the line:

    neededSite.SiteName = someString;

Is updating the object in that list. Now you would think (If you are used to say not caching results from database queries) that the next query:

    context.Site.Any(site => siteName == neededSite.SiteName)

Would hit the database again and check the items there and make sure that SiteName doesn’t exist. But far as Entity Framework is concerned it already loaded that list, so it doesn’t need to hit the database again. This is good to save the performance hit of getting from the database but dangerous if you don’t understand this. Fact is, the context is just going to look at it’s cached list of Sites and run that query and guess what’s in that list? The object that you just changed. So basically that query will compare the object to itself at some point as it iterates through the list and yee haw it will find a Site that already has that SiteName… the one you just changed. Solution?

You can include the SiteID in the query:

    context.Sites
            .Any(
                  site => siteName == neededSite.SiteName
                  && site.SiteID != neededSite.SiteID
                 )

This way you know that you won’t compare the same object to itself.

OR You can create a new context if you really want to get a fresh look at the database. This will allow you to have a context with the new changes and a context pre changes so you can be sure you are comparing the object to a list without it.

Now you might look down the list of methods on the context and see the Refresh method. This might be temping because you might think that a refresh would give you what you want, a completely new list to compare against. There are two ways you can handle this:

     context.Refresh(RefreshMode.Store, context.Site);

Seems like this would be good since it basically means I want to completely restore the list from the database and clear out any changes I made in the list. Oh wait, what was that? ” clear out any changes I made in the list”. This means those changes we made to the object, yeah gone. So now the:

    context.Sites
           .Any(
                  site => siteName == neededSite.SiteName
                  && site.SiteID != neededSite.SiteID
                )

Query is hosed because the neededSite.SiteName has been reverted to whatever was in the database originally.

You could try this:

     context.Refresh(RefreshMode.ClientWins, context.Site);

In which any changes made to the objects will overwrite any information being brought in from the database. Hmm, 6 of one and a half dozen of the other. This just brings us back to the same problem. The list will still contain the changes to the object and therefore we will once again compare the object to itself.

Now you might think that maybe you should just check the list BEFORE applying the changes to the object, and that would be a good way of going about it:

    using(SomeEntities context = new SomeEntities ())
    {
       //where SomeEntities  : ObjectContext
       Site neededSite = context.Site.Where(site => site.ID = siteID);

       if(context.Site.Any(site => siteName == someString))
       {
          //return error since the name already exists in the table.
       }
       else
       {
          neededSite.SiteName = someString;
          //Continue or save
       }
    }

This would work since you are checking the list before the object has been changed. This might work if you do your saving in the same layer that you have your UI, but if you do something like this:

    ...
    Site someSite = Site.GetSiteById(id);
    someSite.SiteName = txtSiteName.Text;
    someSite.Save();
    ...

In this example, I have created a Save method on the Site object that will take care of validation and saving all in one. It also means that I either am sharing a context between the GetSiteId method and the Save method, or that I am using two contexts. If it’s the latter, then it won’t matter since the context in the Save method will not “own” the current someSite object. (And therefore will not have the changes to the someSite object reflected in its list) However, if they do share the same context then you will run into the above problem.

In the next post, I’ll talk about how to share a context… which I won’t try to sell you as the right thing to do. Just something to think about.