Entity Framework: I set the foreign key, SaveChang

2019-01-10 08:54发布

问题:

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.

回答1:

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).



回答2:

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.



回答3:

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.



回答4:

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.