I am using this Entity class with Entity Framework 5 Code First:
public class Survey
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public string SurveyName { get; set; }
[Required]
public int ClientID { get; set; }
[ForeignKey("ClientID")]
public virtual Client Client { get; set; }
}
And in my Controller's Create method I do this:
Survey entity = new Survey()
{
SurveyName = "Test Name",
ClientID = 4
};
db.Surveys.Add(entity);
db.SaveChanges();
Client c1 = entity.Client; //Why is this null?
Client c2 = db.Clients.Find(entity.ClientID); //But this isn't?
string s2 = c2.ClientName;
string s1 = c1.ClientName; //null reference thrown here
The Client navigation property remains null after SaveChanges. I expected the call to load the Client from the database because the foreign key exists. Why didn't it do that?
EDIT
The code here comes from when my controllers were dependent on DbContext
. Not long after I got this working I re-factored the code to use repositories and a unit of work. Part of that move was driven by the fact that it just felt wrong to use Create
when I wanted to use new
. What happened then was that I hit a problem with how to ensure proxies are created when using the repository pattern.
To ensure that lazy loading of a navigation property will work after you've created the parent you must not create the Survey
with the new
operator but create it by means of the context instance because it will instantiate a dynamic proxy that is capable to lazily load the related Client
. That's what the DbSet<T>.Create()
method is for:
Survey entity = db.Surveys.Create();
entity.SurveyName = "Test Name";
entity.ClientID = 4;
db.Surveys.Add(entity);
db.SaveChanges();
Client c1 = entity.Client;
string s1 = c1.ClientName;
// will work now if a Client with ID 4 exists in the DB
Just to emphasize: It's not the line entity.ClientID = 4;
or db.Surveys.Add(entity);
or db.SaveChanges
that loads the client from the DB, but the line Client c1 = entity.Client;
(lazy loading).
Like @NicholasButler said, calling SaveChanges
does what it says on the tin - you can see this if you debug your code: the Intellitrace output will show the SQL it has generated for the insert/update you are persisting, but there will be no subsequent select.
Keep in mind that unless you are eager loading (using the Include
method), related entities are not loaded when performing a retrieval, so it stands to reason that creating/updating them wouldn't either.
The Entity Framework (from I think versions 4.1 and up) supports lazy loading. What this means is that if it's enabled, code like Client c1 = entity.Client;
should load up that Client
object. To be clear, this operation is not directly related to the SaveChanges
call.
It would pay to check whether db.Configuration.LazyLoadingEnabled
is set to true
. If not, try setting it to be true
and see if Client c1 = entity.Client;
is still null.
In short, calling SaveChanges
does not trigger a load, but if lazy loading is enabled, accessing entity.Client
should trigger a load of the entity if it hasn't already been loaded.
Edit:
I should've though of this earlier, but you aren't going to be getting lazy loading on your Survey entity
object. The reason is that EF works its lazy loading magic by creating a class derived from your one but overriding the properties marked as virtual
to support lazy loading. It does this when you perform a retrieval, so your entity
object will not lazy load anything as it stands.
Try this just after your call to SaveChanges
:
Survey entity2 = db.Surveys.Find(entity.ID);
Client c1 = entity2.Client;
This should exhibit the behaviour you are after.
You need to define all the properties on the Survey
class as virtual to enable lazy-loading.
See http://msdn.microsoft.com/en-us/library/vstudio/dd468057(v=vs.100).aspx for more information.
I expected the call to load the Client from the database because the foreign key exists. Why didn't it do that?
It didn't do that because you haven't asked it to. After the call to SaveChanges()
, EF doesn't have the data in the referenced row and it won't make a potentially redundant database call to get it.
Calling db.Clients.Find(...
tells EF to go and fetch the row from the database, which is why it returns the object.