2

I have the following C# Entity Framework code first entities defined:

class Program
{
    static void Main(string[] args)

    {
        var context = new MyContext();

        var person = new Person
        {
            FirstName = "Nadege",
                    LastName = "Deroussen",
                    BirthDate = DateTime.Now,
                    AccessCode = new AccessCode { Code = "ABC" }
        };
        context.Persons.Add(person);

        var accessCode = new AccessCode { Code = "MGH" };
        context.AccessCodes.Add(accessCode);
        context.SaveChanges();


        var person = context.Persons.Where(e => e.Id == 1).Single();            
        person.AccessCodeId = 2;

        context.SaveChanges();

        Console.Write("Person saved !");
        Console.ReadLine();
    }
}

public class Person
{
    public int Id { get; set; }
    public string LastName { get; set; }
    public string FirstName { get; set; }
    public DateTime BirthDate { get; set; }


    public int AccessCodeId { get; set; }
    [ForeignKey("AccessCodeId")]
    public virtual AccessCode AccessCode { get; set; }
}

public class AccessCode
{
    public int Id { get; set; }
    public string Code { get; set; }
}

public class MyContext : DbContext
{
    public DbSet<Person> Persons { get; set; }
    public DbSet<AccessCode> AccessCodes { get; set; }
}

In the Main method, after assigning AccessCodeId = 2, if I check the AccessCode reference in the person, it is still pointing to the AccessCode with Id == 1. How can I get this auto-updated?

I am learning EF, so please excuse me if this doesn't make sense.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Babu James
  • 2,740
  • 4
  • 33
  • 50
  • Do the Persons and AccessCodes sets generate their own identity? – Dave Lawrence May 24 '12 at 13:03
  • Person has 2 properties: AccessCode and AccessCodeId. You don't need AccessCodeId in Person because you have AccessCode.Id accessible from Person. Delete that property and EF will generate the relationship between Persons and AccessCodes automatically. Now is not showing the change because you are setting one property and getting other different, so be careful. – David Diez May 24 '12 at 13:23
  • @daveL Since both entities has a property Id, EF automatically makes them the primary key. – Babu James May 24 '12 at 14:08
  • The second `var` in your code causes compilation error. It needs to get removed. – Juraj Suchár May 25 '12 at 04:41

3 Answers3

4

You have to call ChangeTracker's DetectChanges() method after you change the foreign key property value

person.AccessCodeId = 2;
context.ChangeTracker.DetectChanges();

Note: I've tested this behavior on @lucask code and your code too. Your code just needed to correct the second declaration of person variable.

Juraj Suchár
  • 1,107
  • 6
  • 25
  • Thank you for your suggestion. It indeed works. Is there any way that I can trigger this method automatically on assigning the Id? – Babu James May 25 '12 at 04:41
  • 1
    It will work automatically, if you declare all properties within the Person class as `virtual` and replace `var person = new Person()` with `var person = context.Persons.Create();` This will create so-called _Change Tracking Proxy_ instead of a simple POCO class. Some useful information is here [Entity types supported by the Entity Framework](http://blog.oneunicorn.com/2011/12/05/entity-types-supported-by-the-entity-framework) and here [Should you use Entity Framework change-tracking proxies?](http://blog.oneunicorn.com/2011/12/05/should-you-use-entity-framework-change-tracking-proxies) – Juraj Suchár May 25 '12 at 07:12
2

Finally I got the expected behavior with the following code:

   namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            Database.SetInitializer<TestContext>(new DropCreateDatabaseAlways<TestContext>());
            var context = new TestContext();
            var child = new Child { };
            var parent = new Parent { Child = child };
            context.Parents.Add(parent);
            context.Children.Add(new Child { });

            context.SaveChanges();

            context.Parents.First().ChildId = 2;
            context.SaveChanges();
        }
    }

    public class Parent
    {
        public int Id { get; set; }
        public virtual int ChildId { get; set; }
        public virtual Child Child { get; set; }
    }
    public class Child
    {
        public int Id { get; set; }
    }
    public class TestContext : DbContext
    {
        public DbSet<Parent> Parents { get; set; }
        public DbSet<Child> Children { get; set; }
    }
}

Image shows the debug session, in that just after assigning the ChildId (before SaveChanges() is called), the Child property also gets correctly updated. Thanks to all those who made their valuable suggestions.

enter image description here

Babu James
  • 2,740
  • 4
  • 33
  • 50
1

Well, I don't agree with @David. Code First, Database First or Model First are just different methods to accomplish the same goal. And Code First with Data Annotations or Fluent API is very powerful. You just have to learn how to use it.

Your code will work. First of all remove [ForeignKey("AccessCodeId")]. It's redundant and You placed it on the wrong property anyway. Code First will set AccessCodeId as foreign key by convention. AccessCode is navigation property, won't be generated in the database and it's only here to make your life easier. For more information read this article: Code First Conventions.

Here's working code (which I tested):

public class Person
{
    public int Id { get; set; }
    public string LastName { get; set; }
    public string FirstName { get; set; }
    public DateTime BirthDate { get; set; }

    public int AccessCodeId { get; set; }
    public virtual AccessCode AccessCode { get; set; }
}

public class AccessCode
{
    public int Id { get; set; }
    public string Code { get; set; }
}

public class MyContext : DbContext
{
    public DbSet<Person> Persons { get; set; }
    public DbSet<AccessCode> AccessCodes { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        Database.SetInitializer<MyContext>(new DropCreateDatabaseAlways<MyContext>());

        var context = new MyContext();

        var person = new Person
        {
            FirstName = "Nadege",
            LastName = "Deroussen",
            BirthDate = DateTime.Now,
            AccessCode = new AccessCode { Code = "ABC" }
        };
        context.Persons.Add(person);

        var accessCode = new AccessCode { Code = "MGH" };
        context.AccessCodes.Add(accessCode);
        context.SaveChanges();


        var person2 = context.Persons.FirstOrDefault();
        person2.AccessCodeId = 2;
        // or person2.AccessCode = accessCode;

        context.SaveChanges();

        Console.Write("Person saved !");
        Console.ReadLine();
    }
}

Changes will be visible in the database context.

lucask
  • 2,290
  • 24
  • 19
  • First of all, thank you all for finding time to answer/test this. @lucask [ForeignKey("AccessCodeId")] was added because otherwise the EF generated the table with navigation property Id column as AccessCode_Id and I wanted it without the "underscore". – Babu James May 24 '12 at 14:05
  • Coming to the solution @lucask suggested, I tried the code you posted, but it doesn't work as expected. The expected behavior is that the AccessCode property in the person must be updated to that with Id 2 immediately after I make the assignment of the AccessCodeId. Right now, this only happens after the method SaveChanges() is called. I am not sure if this is how it can work. – Babu James May 24 '12 at 14:06
  • Also could you explain the line: Database.SetInitializer(new DropCreateDatabaseAlways()); – Babu James May 24 '12 at 14:06
  • @BabuJames: Comment#1: If You remove ForeignKey attribute and have AccessCodeId property EF will pick that up by convention and generate AccessCodeId foreign key column in database. Btw are You using EF4.1 by any chance? I used EF 4.3.1. – lucask May 24 '12 at 14:48
  • Comment#2: If You need to have both properties updated before calling SaveChanges() You will have to assign both AccessCodeId and AccessCode properties with new values unfortunately. – lucask May 24 '12 at 14:54
  • Comment#3: I added Database.SetInitializer(new DropCreateDatabaseAlways()) for testing purposes only, so my database would be rebuild from scratch on every run/debug. – lucask May 24 '12 at 14:56
  • @BabuJames: Here's screenshot: http://goo.gl/l09k4 with my debug session. I guess that's a behavior you expect. – lucask May 24 '12 at 15:14
  • Using Code First versus generating a model from a created database are completely different methods. Code First manipulates the database structure within your application (which I believe is a bad practice with the way MS has it currently set up. Using a model that lies on top of a database allows the database to act independent of the application itself. This enables the database to be more extensible and flexibile with other applications. – David East May 24 '12 at 17:18
  • 1
    @David: Nothing prevents you to use Code First approach with already existing database. I'm not saying that one is better than other. It really depends on a project you are working on and its requirements. For me personally working with POCO classes instead of a pre-generated code from edmx files is easier, cleaner and gives me more flexibility. – lucask May 24 '12 at 18:46
  • You can get clean POCO classes with an EDMX file. Code First usually means you are creating a database from code. However, in those cases where a current one exists, it can be much more cumbersome than clicking "Update Model From Database". Code First is still young and working though some issues. I would never use it for any production application. – David East May 24 '12 at 19:11