3

I am migrating my project from EF 4.0 to EF 6.0. I am trying to get properties from navigated entity after insert new entity. That code runs without any problem in EF 4.0 but when I try to run in EF 6.0 I am gettin NullReference Exception.

User Entity:

public partial class users
{
    public int RID { get; set; }
    public string Name { get; set; }
    public Nullable<int> LangRef { get; set; }

    public virtual language language { get; set; }
}

Language Entity:

public partial class language
{
    public language()
    {
        this.users = new HashSet<users>();
    }

    public int RID { get; set; }
    public string Name { get; set; }

    public virtual ICollection<users> users { get; set; }
}

Test Code:

test1Entities testEnt = new test1Entities();
users user = new users();
user.Name = "asd";
user.LangRef = 1;//That ID refer to English record in Language entity
testEnt.users.Add(user);
testEnt.SaveChanges();

string lang = user.language.Name;//Should return "English". However language property seem null

Model:

Model:

kjbartel
  • 10,381
  • 7
  • 45
  • 66
Dreamcatcher
  • 798
  • 13
  • 31
  • Where do you set language property for the new user? You can set an explicit foreign key like languageId or set the language property to a language entity that is being tracked by the DbContext. – Despertar Aug 17 '14 at 22:48
  • Have you looked at the documentation on [msdn](http://msdn.microsoft.com/en-us/data/jj713564.aspx)? It looks to me like the language and LangRef properties aren't being correctly mapped. – kjbartel Aug 18 '14 at 04:13
  • @kjbartel I am using Database first appoach. I do not any editing in auto generated code. That model should works fine. I could not find where do I mistake. – Dreamcatcher Aug 18 '14 at 13:14
  • I'd try regenerating the model then. Not sure if you've customised any of the templates, but as a test you could create a new project and create a new model from scratch to see if it works. I'd imagine that the templates have changed a bit between 4 and 6 so you probably can't just use the old ones as is. – kjbartel Aug 19 '14 at 08:24

2 Answers2

2

You say that user.language.Name should return English, you are not setting that anywhere. You have to set the language when you create a new user.

test1Entities testEnt = new test1Entities();
users user = new users();
user.Name = "asd";
user.LangRef = 1;

// grab the english language entity from the database
language englishLanguage = entities.First(o => o.Name == "English");

// set the users language to english
user.language = englishLanuage;

testEnt.users.Add(user);
testEnt.SaveChanges();

Imagine if you were inserting a user with raw SQL, you would run something like this

INSERT INTO users (name, languageId) VALUES ('Foo', 1);

Where 1 is the RID of the record in the language table which maps to the english language. The 1 is a Foreign Key in the users table which maps a user to their language. Same as with the raw SQL you must explicitly tell Entity Framework which language the user has. This is done by querying the DbContext for the language (which returns a tracked entity), then setting that entity to the navigation property.


Alternatively, if you know the primary key of the language record in advance, you can create a new language entity (with the correct RID) and attach it to the DbContext, then set it to the navigation property. This would make it so there is one less query against the database. While this would be a little more effecient, you should do whatever is easier/makes the most sense to you. Get it working, then you can optimize bottlenecks later.

language englishLanguage = new language() { RID = 1, Name = "English" };
testEnt.languages.Attach(language);

user.language = englishLanguage;
testEnt.users.Add(user);

Edit

Given your recent edits, it is now more clear what you are trying to do. I didn't realize that LangRef was your foreign key since it was a decimal and doesn't follow normal naming conventions.

The solution is described in this answer, https://stackoverflow.com/a/18251442/1160036. The problem arises from Entity Framework caching. After SaveChanges() (so that the RID is updated), you can detach and re-pull the user to refresh it.

testEnt.users.Add(user);
testEnt.SaveChanges();
((IObjectContextAdapter)testEnt).ObjectContext.Detach(user);
users user = testEnt.First(o => o.RID == user.RID);
Console.WriteLine(user.language.Name);

Opening a new DbContext works as well, but this does cause another connection to be made to the database.

As described in the above answer, testEnt.Entry<users>(user).Reload() unfortunately doesn't work.

Community
  • 1
  • 1
Despertar
  • 21,627
  • 11
  • 81
  • 79
  • 1
    If you look Model Images they have already navigated each other. If I do that query everythink work fine: users newUser = testEnt.users.Include("language").FirstOrDefault(x => x.RID == user.RID); However this called Eager loading. Normal condition when I try to access Language.Name property EF should do new query for language. I think I missed some configuration details. – Dreamcatcher Aug 18 '14 at 13:13
  • @Dreamcatcher I was able to replicate and solve your issue. I've edited my answer. – Despertar Aug 19 '14 at 01:54
  • Thank you your answer resolve problem also, **.Include("language")** and **DatabaseObjectContext.Entry(member).Reload();** solving. However I mentioned that before I am migration project from Entity Framework 4.0 to Entity Framework 6.0. And there are hundreds and hundreds line of code. If I will start editing code, it consume lots of time. I think Lazy loading help to like that situations. Is there a any way disable EF caching? Thank you... – Dreamcatcher Aug 19 '14 at 02:40
  • @Dreamcatcher No, oddly there does not appear to be a global way of disabling cache other than the ways already discussed (eager loading, new DbContext, or detaching). My best advice would be to try to abstract away the database layer so that when you need to make a change you only have to make it in one centralized place instead of all over your application. – Despertar Aug 19 '14 at 03:51
0

Instead of using new users() try testEnt.users.Create(). The difference is that the object returned will be a proxy class which is needed for lazy loading of navigation properties.

As an aisde, doing the opposite and setting the language navigation property itself and not the LangRef foreign key value should actually correctly set the LangRef property when you call SaveChanges().

See DbSet.Create method on MSDN.

kjbartel
  • 10,381
  • 7
  • 45
  • 66