4

I'm using EF Code First over WCF. So when I save an entity it's using a new context.

If I retrieve an entity, then update it so it references a different entity, I'm finding that it saves with the original foreign key value.

For example, I retrieve a Company class, where the country is UK. I then change it to USA and pass it back to the service. When I check the table the FK is still set to that of UK.

How can I make it update the foreign key?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity.ModelConfiguration;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
using System.Data;

namespace CodeFirstExistingDatabase
{

    class Program
    {
        private const string ConnectionString = @"Server=.\sql2005;Database=CodeFirst2;integrated security=SSPI;";

        static void Main(string[] args)
        {

            // Firstly, create a new country record.
            Country country = new Country();
            country.Code = "UK";
            country.Name = "United Kingdom";

            // Create aother new country record.
            Country country2 = new Country();
            country2.Code = "USA";
            country2.Name = "US of A";

            // Now create an instance of the context.
            MyContext myContext = new MyContext(ConnectionString);
            myContext.Entry(country).State = EntityState.Added;
            myContext.Entry(country2).State = EntityState.Added;
            myContext.SaveChanges();
            Console.WriteLine("Saved Countries");

            // Now insert a Company record
            Company company = new Company();
            company.CompanyName = "AccessUK";
            company.HomeCountry = myContext.Countries.First(e => e.Code == "UK");
            myContext.Companies.Add(company);
            myContext.SaveChanges();
            Console.WriteLine("Saved Company");

            Company savedCompany = myContext.Companies.First(e => e.CompanyName == "AccessUK");
            Country usCountry = myContext.Countries.First(e => e.Code == "USA");
            savedCompany.HomeCountry = usCountry;

            // Create another context for the save (as if we're passing the entity back over WCF and thus
            // creating a new context in the service)
            MyContext myContext2 = new MyContext(ConnectionString);
            myContext2.Entry(savedCompany).State = EntityState.Modified;
            myContext2.Entry(savedCompany.HomeCountry).State = EntityState.Modified;
            myContext2.SaveChanges();

            // When I check the company table, it has the foreign key of the UK Country.  It should have 
            // that of USA.

            Console.WriteLine("Finished");
            Console.ReadLine();

        }
    }

        public class MyContext
            : DbContext
        {
            public DbSet<Company> Companies { get; set; }
            public DbSet<Country> Countries { get; set; }

            public MyContext(string connectionString)
                : base(connectionString)
            {
            }
            protected override void OnModelCreating(DbModelBuilder modelBuilder)
            {
                modelBuilder.Configurations.Add(new CountryConfiguration());
                modelBuilder.Configurations.Add(new CompanyConfiguration());

                base.OnModelCreating(modelBuilder);
            }
        }

    public class CompanyConfiguration
        : EntityTypeConfiguration<Company>
    {

        public CompanyConfiguration()
            : base()
        {

            HasKey(p => p.Id);
            Property(p => p.Id)
                .HasColumnName("Id")
                .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
                .IsRequired();

            Property(p => p.CompanyName)
                .HasColumnName("Name")
                .IsRequired();
            HasRequired(x => x.HomeCountry).WithMany()
                .Map(x => x.MapKey("HomeCountryId"));

            ToTable("Companies");
        }

    }

    public class CountryConfiguration
        : EntityTypeConfiguration<Country>
    {

        /// <summary>
        /// Initializes a new instance of the <see cref="CountryConfiguration"/> class.
        /// </summary>
        public CountryConfiguration()
            : base()
        {

            HasKey(p => p.Id);
            Property(p => p.Id)
                .HasColumnName("Id")
                .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
                .IsRequired();
            Property(p => p.Code)
                .HasColumnName("Code")
                .IsRequired();
            Property(p => p.Name)
                .HasColumnName("Name")
                .IsRequired();

            ToTable("Countries");
        }

    }

    public class Company
    {
        public int Id { get; set; }
        public string CompanyName { get; set; }
        public Country HomeCountry { get; set; }
    }

    public class Country
    {
        public int Id { get; set; }
        public string Code { get; set; }
        public string Name { get; set; }
    }
}

Many thanks,

Paul.

Ladislav Mrnka
  • 349,807
  • 56
  • 643
  • 654
P2l
  • 875
  • 2
  • 11
  • 24

1 Answers1

2

This is well known problem of detached entities and independent associations. The problem is that setting entity to modified does not set the relation to modified. The long description about all related issues is here. The answer is related to EFv4 and ObjectContext API but EFv4.1 is just a wrapper around it so the meaning is the same.

The source of this problem is that if you use detached entities you must say EF that relation has changed. EF will not do any assumption for you. The problem with DbContext API is even worse because DbContext API doesn't offer methods to change state of independent association so you must revert back to ObjectContext API and use ObjectStateManager.ChangeRelationshipState.

There are other ways how to deal with it if you don't use remote calls as WCF - it is just order of commands you must do to make this work but when using WCF you can't attach the entity before you set the relation etc. Ok theoretically you can but it will completely change your service contract and messaging choreography.

So generally the easiest solution in this scenario is using foreign key association instead of independent association. Foreign key association is not tracked as separate object with a state (as independent association is) so changing entity's state to modified will just work.

public class Company
{
    public int Id { get; set; }
    public string CompanyName { get; set; }
    public int HomeCountryId { get; set; }
    public Country HomeCountry { get; set; }
}


HasRequired(x => x.HomeCountry)
    .WithMany()
    .HasForeignKey(x => x.HomeCountryId);

Any other solution is about loading the old object from DB first and merge changes into attached object - that is what I do all the time and you will have to do it once you start to work with many-to-many relations or deleting in one-to-many relations.

Community
  • 1
  • 1
Ladislav Mrnka
  • 349,807
  • 56
  • 643
  • 654
  • Regarding using a HomeCountryId property does this mean that when assigning a Country instance to the Company that I also need to manually assign the HomeCountryId value? That's the only way I'm finding I can get this approach to work. Thanks. – P2l Apr 20 '11 at 12:59
  • @Paul: Yes, actually setting HomeCountryId is enough. – Ladislav Mrnka Apr 20 '11 at 13:00