61

I have the following 2 classes:

public class Reward 
{
    public int Id { get; set; }
    public int CampaignId { get; set;
    public virtual Campaign Campaign { get; set; }
}

public class Campaign 
{
    public int Id { get; set; }
    public virtual ICollection<Reward> Rewards { get; set; }
}

With this I have all the obvious necessary stuff like a DbContext and mappings.

Now let's say I create a Reward entity and insert it like this:

var reward = new Reward { CampaignId = 1 };
context.Set<Reward>().Add(reward);
context.SaveChanges();

reward = context.Set<Reward>().SingleOrDefault(a => a.Id == reward.Id);
//reward.Campaign is null

I obviously have a campaign with Id 1 so the FK constraint is happy. After this insert, my reward entity has it's new Identity Id set. Now the problem is that reward is still just the Reward entity I created. And with this, the reward.Campaign property is null. It seems like EF is keeping the inserted entities in memory, and when I then do a .SingleOrDefault(a => a.Id == reward.Id) it simply returns the entity in memory, and not a new proxy. This is probably a good thing.

So the question is: How does one access or load the navigation properties after an insert or get a new proxy that has the navigation properties as proxies as well.

Am I perhaps inserting in the wrong way?

dav_i
  • 27,509
  • 17
  • 104
  • 136
Justus Burger
  • 655
  • 1
  • 6
  • 6
  • What does your mapping look like? – Maritim Oct 28 '14 at 14:07
  • 1
    possible duplicate of [Entity Framework: I set the foreign key, SaveChanges then access the navigation property, but it doesn't load the related entity. Why not?](http://stackoverflow.com/questions/15552891/entity-framework-i-set-the-foreign-key-savechanges-then-access-the-navigation) – Colin Oct 28 '14 at 16:49

6 Answers6

96

If I understand you correctly, you're trying to eagerly load a complex property after establishing a relationship via a foreign key property.

SaveChanges() does not do anything in the way of loading complex properties. At most, it is going to set your primary key property if you're adding new objects.

Your line reward = context.Set<Reward>().SingleOrDefault(a => a.Id == reward.Id); also does nothing in the way of loading Campaign because your reward object is not attached to the context. You need to explicitly tell EF to load that complex object or attach it then let lazy loading work its magic.

So, after you context.SaveChanges(); you have three options for loading reward.Campaign:

  1. Attach() reward to the context so that Campaign can be lazily loaded (loaded when accessed)

     context.Rewards.Attach(reward);
    

    Note: You will only be able to lazy load reward.Campaign within the context's scope so if you're not going to access any properties within the context lifespan, use option 2 or 3.

  2. Manually Load() the Campaign property

     context.Entry(reward).Reference(c => c.Campaign).Load();
    

    Or if Campaign was a collection, for example Campaigns:

     context.Entry(reward).Collection(c => c.Campaigns).Load();
    
  3. Manually Include() the Campaign property

     reward = context.Rewards.Include("Campaigns")
         .SingleOrDefault(r => r.Id == reward.Id);
    

    Although, I'd suggest Load since you already have reward in memory.

Check out the Loading Related Objects Section on this msdn doc for more information.

Carrie Kendall
  • 11,124
  • 5
  • 61
  • 81
  • 11
    The second option was the best for me as well. – Reuel Ribeiro Jul 09 '16 at 00:30
  • The second works! _db.Entry(product).Reference(b => b.Category).Load(); Finally it loads the category info along with the product. Thank you! – hubert17 Jun 02 '19 at 14:22
  • What assembly is the Reference extension in? context.Entry(reward).Reference(c => c.Campaign).Load(); The Reference() method I see only takes the navigation property name as a string. – Dave Lawrence Jul 16 '19 at 08:21
  • 1
    Thought I'd mention it since I ran into this: when using .Load() for navigation properties that are collections you need to call .Collection() instead of .Reference() otherwise you'll get a runtime error. – ZeRemz Oct 07 '20 at 09:58
  • How would the second option work when I want to load related entities or collections of another related entity, such as `Campaigns`? – Chris Mar 02 '22 at 14:52
11

As you are creating your reward object as new Reward(), EF doesn't have a proxy. Instead, create it using DbSet.Create like this:

var reward = context.Set<Reward>().Create();
reward.CampaignId = 5;
context.SaveChanges();

Next attach it to your DbSet:

context.Rewards.Attach(reward);

Finally, you can now use lazy loading to get related entities:

var campaign = reward.Campaign;
DavidG
  • 113,891
  • 12
  • 217
  • 223
  • I can't modify the way our entities are added. Is there another way to get the proxy version after saving? – xr280xr Mar 16 '22 at 02:55
1

I have a simple Solution around the problem.

instead of adding the CampaignID to the reward, add the campaign Object.. so:

var _campaign = context.Campaign.First(c=>c.Id == 1);//how ever you get the '1'
var reward = new Reward { Campaign = _campaign };
context.Set<Reward>().Add(reward);
context.SaveChanges();

//reward.Campaign is not null

Entity framework does all the heavy lifting here.

You're probably thinking that it's a waste to load the entire Campaign object but if you are going to be using it (from what it looks like, seems you are) then I don't see why not. You can even use the include statement when fetching it above if you need to access navigation properties from the Campaign object...

var _campaign = context.Campaign.include(/*what ever you may require*/).First(c=>c.Id = 1);
Steven
  • 21
  • 5
1

In addition to Carrie Kendall and DavidG (in VB.NET):

Dim db As New MyEntities
Dim r As Reward = = db.Set(Of Reward)().Create()
r.CampaignId = 5
db.Reward.Add(r) ' Here was my problem, I was not adding to database and child did not load
db.SaveChanges()

Then, property r.Campaign is available

Dani
  • 1,825
  • 2
  • 15
  • 29
0

Did you try using Include()? Something like this:

reward = context.Set<Reward>().Include("Campaigns").SingleOrDefault(a => a.Id == reward.Id);
Floremin
  • 3,969
  • 15
  • 20
  • I didn't try that but I definitely have Lazy Loading enabled and everything works fine except the case when an entity is inserted into a database first. Even if that works it will break the consistency of the code. Basically I will have to do this after inserts but I don't need it in other cases. Doing detaching is more consistent because I can do it right after SaveChanges. I don't know when I will need to select the data next time and when I really need that Include and when I don't need it. You can say, do it every time but why I have Lazy Loading enabled in this case? – Alex Jun 09 '20 at 15:50
0

if you have more than one navigation properties or want to add more than one records it may be hard to do it in these ways.

so i suggest if the memory doesn't matter create a new context object after adding your records , and use it instead

  • Yes, I also mentioned that everything works if I do a query on another context but what about detaching the entity approach? Why it worked in one case and why it threw an exception in another case? Maybe this whole issue, returning navigation properties null, should be filed as a bug to the entities dev team? It looks like a bug. The entity tracking breaks after SaveChanges. I believe it should send a query and retrieve the data but it looks like the object is considered to be complete. – Alex Jun 09 '20 at 15:34