2012-04-17 36 views
1

我在使用codefirst實體框架時遇到了很多麻煩。在實體框架中添加+修改對象時出現奇怪的困難

我有兩個表的數據庫 - UsersCountriesUsers有一個字段,其外鍵爲CountryId。當用戶首次添加到數據庫時,FK更新正常。但是,如果我嘗試更新附加到新DbContext的用戶的FK,則不會發生更改。

我可以在我最初創建在用戶的上下文更新FK。另外,我可以在我從數據庫中檢索用戶上下文更新FK。然而,在我剛剛Attach用戶的上下文中,FK字段不更新,即使是正常的nvarcharint領域做更新。

這裏是我的DbContext:

class DataContext : DbContext 
{ 
    public DbSet<User> Users { get; set; } 
    public DbSet<Country> Countries { get; set; } 
} 

這裏是我的模型:

public class User 
{ 
    public int UserId { get; private set; } 
    public string Name { get; set; } 
    public virtual Country Country { get; set; } 

} 

public class Country 
{ 
    public int CountryId { get; private set; } 
    public string Name { get; set; } 
} 

這種方法演示了此問題。我將3個用戶添加到我的數據庫中,然後嘗試以3種不同的方式修改它們 - 在用戶添加的原始上下文中,在Attach的新上下文中,以及在用戶從數據庫中恢復的新上下文中修改:

static void Main(string[] args) 
{ 
    Country[] countries; 

    using (var dc = new DataContext()) 
    { 
     countries = dc.Countries.ToArray(); 
    } 

    var u = new User(); 

    // FIRST ROW 
    using (var dc = new DataContext()) 
    { 
     u.Name = "First"; 
     u.Country = countries[0]; 
     dc.Users.Attach(u); 
     dc.Entry(u).State = System.Data.EntityState.Added; 
     dc.SaveChanges(); 

     u.Name = "FirstChanged"; 
     u.Country = countries[1]; 
     // defaults to 'added' once u is tracked. 
     dc.Entry(countries[1]).State = EntityState.Unchanged; 
     dc.SaveChanges(); 
    } 

    // SECOND ROW 
    u = new User(); 
    using (var dc = new DataContext()) 
    { 
     u.Name = "Second"; 
     u.Country = countries[0]; 
     dc.Users.Attach(u); 
     dc.Entry(u).State = System.Data.EntityState.Added; 
     dc.SaveChanges(); 
    } 

    using (var dc = new DataContext()) 
    { 
     u.Name = "SecondChanged"; 
     u.Country = countries[1]; 
     dc.Users.Attach(u); 
     dc.Entry(u).State = System.Data.EntityState.Modified; 
     dc.SaveChanges(); 
    } 

    // THIRD ROW 
    u = new User(); 
    using (var dc = new DataContext()) 
    { 
     u.Name = "Third"; 
     u.Country = countries[0]; 
     dc.Users.Attach(u); 
     dc.Entry(u).State = System.Data.EntityState.Added; 
     dc.SaveChanges(); 
    } 

    using (var dc = new DataContext()) 
    { 
     u = dc.Users.Find(u.UserId); 
     u.Name = "ThirdChanged"; 
     u.Country = countries[1]; 
     dc.Entry(countries[1]).State = EntityState.Modified; 
     dc.SaveChanges(); 
    } 
} 

這個應用產生的數據庫,結果如下:

UserId Name Country_CountryId 
------------------------------------------------------------------------ 
1 FirstChanged 2 
2 SecondChanged 1 
3 ThirdChanged 2 

正如你所看到的,在所有情況下Name更新成功。在第一種情況和第三種情況下,CountryId已更新。但是,在第二種情況下,CountryId不會更新。不幸的是,這種情況與我最爲相關,因爲我希望能夠在DataContext之外持續使用模型進行編輯。

我不明白爲什麼會這樣 - 如果Name場沒有更新我能理解,但由於其只是CountryId我不明白爲什麼。在這兩種情況下,Country對象都被跟蹤並標記爲Unchanged。我不明白爲什麼只有第二種情況不起作用。

請幫我弄清楚這裏有什麼。我能想到的唯一解決方案就是在需要修改時使用第三種情況,這樣當我保存時,我可以將數據從持久對象複製到從數據庫中檢索到的新對象中,但是顯然這是愚蠢的解決方案。

下面,我包括演示此問題的一個控制檯應用程序。要運行它,你將需要的EntityFramework使用的NuGet(或類似)和一個連接字符串這樣的檢索:

<add name="DataContext" connectionString="DataSource=.\DataContext.sdf" providerName="System.Data.SqlServerCe.4.0" /> 

代碼:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Data.Entity; 
using System.Data; 

namespace ConsoleApplication1 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      Country[] countries; 

      using (var dc = new DataContext()) 
      { 
       countries = dc.Countries.ToArray(); 
      } 

      var u = new User(); 

      // FIRST ROW 
      using (var dc = new DataContext()) 
      { 
       u.Name = "First"; 
       u.Country = countries[0]; 
       dc.Users.Attach(u); 
       dc.Entry(u).State = System.Data.EntityState.Added; 
       dc.SaveChanges(); 

       u.Name = "FirstChanged"; 
       u.Country = countries[1]; 
       // defaults to 'added' once u is tracked. 
       dc.Entry(countries[1]).State = EntityState.Unchanged; 
       dc.SaveChanges(); 
      } 

      // SECOND ROW 
      u = new User(); 
      using (var dc = new DataContext()) 
      { 
       u.Name = "Second"; 
       u.Country = countries[0]; 
       dc.Users.Attach(u); 
       dc.Entry(u).State = System.Data.EntityState.Added; 
       dc.SaveChanges(); 
      } 

      using (var dc = new DataContext()) 
      { 
       u.Name = "SecondChanged"; 
       u.Country = countries[1]; 
       dc.Users.Attach(u); 
       dc.Entry(u).State = System.Data.EntityState.Modified; 
       dc.SaveChanges(); 
      } 

      // THIRD ROW 
      u = new User(); 
      using (var dc = new DataContext()) 
      { 
       u.Name = "Third"; 
       u.Country = countries[0]; 
       dc.Users.Attach(u); 
       dc.Entry(u).State = System.Data.EntityState.Added; 
       dc.SaveChanges(); 
      } 

      using (var dc = new DataContext()) 
      { 
       u = dc.Users.Find(u.UserId); 
       u.Name = "ThirdChanged"; 
       u.Country = countries[1]; 
       dc.Entry(countries[1]).State = EntityState.Modified; 
       dc.SaveChanges(); 
      } 
     } 
    } 

    class DataContext : DbContext 
    { 
     public DbSet<User> Users { get; set; } 
     public DbSet<Country> Countries { get; set; } 

     public DataContext() 
     { 
      Database.SetInitializer<DataContext>(new ModelInitializer()); 
      Database.Initialize(false); 
     } 
    } 

    class ModelInitializer : DropCreateDatabaseAlways<DataContext> 
    { 
     protected override void Seed(DataContext context) 
     { 
      var jsds = new List<Country> 
       { 
        new Country("United Kingdom"), 
        new Country("United States Of America") 
       }; 

      jsds.ForEach(jsd => context.Countries.Add(jsd)); 
     } 
    } 

    public class User 
    { 
     public int UserId { get; private set; } 
     public string Name { get; set; } 
     public virtual Country Country { get; set; } 

    } 

    public class Country 
    { 
     public int CountryId { get; private set; } 
     public string Name { get; set; } 

     public Country() { } 

     public Country(string name) 
     { 
      Name = name; 
     } 
    } 
} 

感謝您看這個問題 - 任何幫助,將不勝感激。我在試圖修復它的第二天,我慢慢地快要瘋了......

回答

5

這是一個基於foreign key association vs independent association之間的差別比較棘手的問題。如果您使用外鍵關聯(您的User實體中將有CountryId屬性),那麼您的第二種情況也會起作用,但是您正在使用獨立關聯,而關聯本身具有自己的狀態,必須將其設置爲Added以使其工作。 DbContextdoesn't offer API要更改關聯的狀態,所以您必須使用ObjectContext

u = new User(); 
using (var dc = new DataContext()) 
{ 
    u.Name = "Second"; 
    u.Country = countries[0]; 
    dc.Users.Attach(u); 
    dc.Entry(u).State = System.Data.EntityState.Added; 
    dc.SaveChanges(); 
} 

using (var dc = new DataContext()) 
{ 
    u.Name = "SecondChanged"; 
    u.Country = countries[1]; 
    dc.Users.Attach(u); 
    ObjectContext oc = ((IObjectContextAdapter)dc).ObjectContext; 
    oc.ObjectStateManager.ChangeObjectState(u, EntityState.Modified); 
    oc.ObjectStateManager.ChangeRelationshipState(u, countries[1], x => x.Country, EntityState.Added); 
    dc.SaveChanges(); 
} 

當您使用獨立的關聯時,您可以找到a lot of complications

+0

我只想說,你對許多EF相關問題的答案對我來說是無價的。謝謝。 – 2012-04-17 12:31:56

+0

感謝您的回答。我嘗試了你發佈的代碼,但遇到了一些麻煩(我得到一個錯誤,說刷新ObjectStateManager,但這樣做意味着u.Name沒有更新,儘管FK沒有更新),所以我將我的用戶類更改爲使用外鍵關聯。然後,我改變了我的代碼來指定這樣的國家:'u.CountryId = countries [0] .CountryId;'。它是否正確?它似乎工作,但在某些情況下,「Country」屬性爲空,而不是反映用戶的「CountryId」屬性。 – Oliver 2012-04-17 13:15:20

+0

是的,這是使用FK關聯的正確方法。該名稱未更新,因爲代碼缺少將用戶的狀態更改爲已修改的行。我會更新代碼。 – 2012-04-17 13:51:58