2011-09-26 67 views
3

我遇到一個與Windows Phone(SQL Server CE)的LINQ to SQL有關的問題。LINQ to SQL關係不會更新集合

我正在開發一個個人理財應用程序,然後我有Account類和Transaction類。每個交易類都有一個對它所屬賬戶的引用,所以賬戶類具有一系列交易,處於一對多關係。然後我有存儲庫(AccountRepository和TransactionRepository),公開方法插入,刪除,findbykey,並返回每個類的所有實例。 ViewModel引用其存儲庫。好吧,一切正常,但是,當我創建一個事務時,它不會出現在Account.Transactions集合中,直到我停止該軟件並再次運行它。

下面的代碼的一些作品,首先,模型類:

[Table] 
public class Transaction : INotifyPropertyChanged, INotifyPropertyChanging 
{ 
    // {...} 

    [Column] 
    internal int _accountID; 
    private EntityRef<Account> _account; 
    [Association(Storage = "_account", ThisKey = "_accountID")] 
    public Account Account 
    { 
     get {return _account.Entity;} 
     set 
     { 
      NotifyPropertyChanging("Account"); 
      _account.Entity = value; 

      if (value != null) 
      { 
       _accountID = value.AccountID; 
      } 

      NotifyPropertyChanged("Account"); 
     } 
    } 

    // {...} 
} 

[Table] 
public class Account : INotifyPropertyChanged, INotifyPropertyChanging 
{ 
    // {...} 

    private EntitySet<Transaction> _transactions; 

    [Association(Storage = "_transactions", OtherKey = "_accountID")] 
    public EntitySet<Transaction> Transactions 
    { 
     get { return this._transactions; } 
     set { this._transactions.Assign(value); } 
    } 

    public Account() 
    { 
     _transaction = new EntitySet<Transaction>(
      new Action<Transaction>(this.attach_transaction), 
      new Action<Transaction>(this.detach_transaction) 
      ); 
    } 

    private void attach_transaction(Transaction transaction) 
    { 
     NotifyPropertyChanging("Transactions"); 
     transaction.Account = this; 
    } 

    private void detach_transaction(Transaction transaction) 
    { 
     NotifyPropertyChanging("Transactions"); 
     transaction.Account = null; 
    } 

    // {...} 
} 

然後,我有一些倉庫實現返回一個ObservableCollection一個GETALL()方法。該庫必須是視圖模型類中創建的上下文類的引用,就像這樣:

public class AccountRepository 
{ 
    private MyContext _context; 

    public AccountRepository(ref MyContext context) 
    { 
     _context = context; 
    } 

    // {...} 

    public ObservableCollection<Account> GetAll() 
    { 
     return new ObservableCollection(_context.Accounts.Where([some paramethers]).AsEnumerable()); 


    } 

    // {...} 
} 

我的視圖模型初始化構造函數中的資料庫,然後暴露了一些很少的邏輯代碼的方法來插入,刪除,等等,每種類型。

public class MyViewModel : INotifyPropertyChanged, INotifyPropertyChanging 
{ 
    private MyContext _context; 
    private AccountRepository accountRepository; 
    private TransactionRepository transactionRepository; 
    public ObservableCollection<Account> AllAccounts; 
    public ObservableCollection<Transaction> AllTransactions; 

    public MyViewModel(string connectionString) 
    { 
     _context = new MyContext("Data Source=’isostore:/mydatabase.sdf’"); if (!db.DatabaseExists()) db.CreateDatabase(); 

     accountRepository = new AccountRepository(ref _context); 
     transactionRepository = new TransactionRepository(ref _context); 

     // [Some other code] 

     LoadCollections();   
    } 

    // {...} 

    public void LoadCollections() 
    { 
     AllAccounts = accountRepository.GetAll(); 
     NotifyPropertyChanged("AllAccounts"); 
     AllTransactions = transactionRepository.GetAll(); 
     NotifyPropertyChanged("AllTransactions"); 
    } 

    public void InsertTransaction(Transaction transaction) 
    { 
     AllTransactions.Add(transaction); 
     transactionRepository.Add(transaction); 

     LoadCollections(); // Tried this to update the Accounts with newer values, but don't work... 
    } 

    // {...} 
} 

當用戶創建事務,頁面調用視圖模型的InsertTransaction(事務的事務)的方法,即通過交易對象到存儲庫。但Account對象中的Transactions集合不會被更新。然後我試着調用LoadCollections()方法在上下文中強制一個新的查詢,並試圖以某種方式獲得一個新的賬戶對象,但它仍然沒有最近創建的事務。如果我停止我的應用程序並重新啓動它,那麼帳戶是最新的並且具有我在其交易集合中上次運行時創建的所有事務。

如何在運行時更新此事務集合?

更新問題:

我只好向有關通知集合已更改UI的一些反饋。

我認爲這是交易和帳戶之間的關聯問題。一旦我創建了一個事務,它就不會出現在它的account.Transactions集合中,直到我處理我的上下文並再次創建它。

這可能是一些通知錯誤,但我不認爲它是,我做了一些代碼來試圖證明它,我現在不在我的電腦中,但我會嘗試解釋我的測試。

我沒有證明它的代碼是這樣的:

Account c1 = context.Accounts.Where(c => c.AccountID == 1).SingleOrDefault(); 

Transaction t1 = new Transaction() { Account = c1, {...} }; 

context.Transactions.InsertOnSubmit(t1); 
context.SaveChanges(); 

c1 = context.Accounts.Where(c => c.AccountID == 1).SingleOrDefault(); 

// The transaction IS NOT in the c1.Transactions collection right NOW. 

context.Dispose(); 

context = new MyContext({...}); 

c1 = context.Accounts.Where(c => c.AccountID == 1).SingleOrDefault(); 

// The transaction IS in the c1.Transactions after the dispose! 

Account c2 = context.Where(c => c.AccountID == 2).SingleOrDefault(); 

t1 = context.Transactions.Where(t => t.TransactionID == x).SingleOrDefault(); 

t1.Account = c2; 

context.SubmitChanges(); 

c1 = context.Accounts.Where(c => c.AccountID == 1).SingleOrDefault(); 

// The transaction still in the c1 collection!!! 

c2 = context.Accounts.Where(c => c.AccountID == 2).SingleOrDefault(); 

// It should be in the c2 collection, but isn't yet... 

context.Dispose(); 

context = new MyContext({...}); 

c1 = context.Accounts.Where(c => c.AccountID == 1).SingleOrDefault(); 

// The transaction is not in the c1.Transaction anymore! 

c2 = context.Accounts.Where(c => c.AccountID == 2).SingleOrDefault(); 

// The transaction IS in the c2.Transactions now! 
+0

獲取交易或與交易和帳戶之間的關聯存在問題嗎?在線'solennsactions = transactionRepository.GetAll();'是集合中的新事務? –

+0

@Kirk Broadhurst對不起,也許我不清楚,英語不是我的第一個語言。問題在於trans和賬戶之間的關聯。如果我添加一個事務,它將不會出現在account.Transactions集合中,直到我處理上下文並再次查詢該帳戶。如果我查詢上下文而不處理它,它將返回該帳戶而不是最近在該關聯中創建的事務。我想,這不是通知UI的問題,因爲如果我調試它,我無法在對象中看到新的事務,我做了一些代碼來證明這一點,我將在家中更新問題。 – Alaor

+0

@Kirk Broadhurst,關於你關於transactionRepository.GetAll()的問題,是的,它在集合中。我可以在調試中觀察到,當我創建事務(例如)時,它沒有事務ID(當然),並且一旦我調用SubmitChanges,內存中的對象就會更新,我可以通過調試來看到它的ID ,我需要的是與關聯集合類似的行爲,當我調用SubmitChanges時,它不會更新集合,即使我再次查詢上下文。只有在處置後,我才能得到最新的。 – Alaor

回答

2

如果可以發佈更完整的代碼示例來展示具體問題,那將是非常好的。

就父母 - 孩子而言:許多關係正如你期望的那樣工作,這是L2S不直接實現的功能。而是在DataContext中實現。這是在Child.Parent屬性設置器中完成的,讓孩子將自己添加到其父實體集實例中。

只要您在運行時通過使用維護此綁定的setter生成父關係< - >子關係,此機制就會起作用。

由代碼舉例來說,這裏是由Visual Studio中的O/R設計器生成的爲一對孩子分配父實體類型的屬性:一對多的關係:


[global::System.Data.Linq.Mapping.AssociationAttribute(Name="Account_Transaction", Storage="_Account", ThisKey="AccountId", OtherKey="AccountId", IsForeignKey=true)] 
     public Account Account 
     { 
      get 
      { 
       return this._Account.Entity; 
      } 
      set 
      { 
       Account previousValue = this._Account.Entity; 
       if (((previousValue != value) 
          || (this._Account.HasLoadedOrAssignedValue == false))) 
       { 
        this.SendPropertyChanging(); 
        if ((previousValue != null)) 
        { 
         this._Account.Entity = null; 
         previousValue.Transactions.Remove(this); 
        } 
        this._Account.Entity = value; 
        if ((value != null)) 
        { 
         value.Transactions.Add(this); 
         this._AccountId = value.AccountId; 
        } 
        else 
        { 
         this._AccountId = default(long); 
        } 
        this.SendPropertyChanged("Account"); 
       } 
      } 
     } 

請注意,Transaction.Add和Transaction.Remove塊...這是父項上的EntitySet <>的管理 - 它不會自動發生在L2S層內。

是的,這是很多膠水寫的。抱歉。我的建議是,如果你有一個有很多關係的複雜數據模型,用DBML對它建模並讓VS爲你提供一個數據上下文。有幾個目標變化將需要刪除該DataContext對象上的一些構造函數,但99%的設計者吐出的內容只能起作用。

-John Gallardo 開發人員,Windows Phone。

+0

完美!而已!非常感謝你,你搖滾! ;)這就是爲什麼我愛微軟團隊! – Alaor

+0

做得很好。當我爲此尋求解決方案時,我應該採取這種方法。 :-) –

0

在LoadCollection()要更新的屬性AllAccounts和AllTransactions,但你不通知他們已經改變了UI。您可以在設置屬性後通過引發PropertyChanged事件來通知UI - 在屬性設置器中是一個很好的選擇。

+0

感謝您的評論,saus。它仍然無法工作。我實際上是在調試中檢查對象,所以我知道這不是我調用PropertyChanged的原因,我忘了說出來。但是我在視圖模型中使用NotifyPropertyChanged調用更新了帖子中的源代碼。 – Alaor

0

我有類似的問題,並導致重新創建集合,就像你在LoadCollections()中做的那樣。但是,您可以調用NotifyPropertyChanged(.. collectionname ..),必須保證用戶界面令人耳目一新,但也許只是儘量讓這樣的:

private readonly ObservableCollection<Transaction> _allTransactions = new ObservableCollection<Transaction>(); 
public ObservableCollection<Transaction> AllTransactions 
{ get { return _allTransactions; } } 
// same for AllAccounts 

public void LoadCollections() 
{ 
    // if needed AllTransactions.Clear(); 
    accountRepository.GetAll().ToList().ForEach(AllTransactions.Add); 
    // same for other collections 
} 

這種方法保證,你的UI總是難免同一集合在內存中,您不需要使用任何RaisePropertyChanged(「AllTransaction」)放置在你的代碼中,因爲這個屬性不能再被改變。 我總是使用這種方法在視圖模型中定義集合。

+0

感謝您的回答,但我不認爲這是刷新UI,即使調試代碼,我剛剛創建的事務不會出現在account.Transactions,直到我處置上下文並再次創建它。但我會在晚上在家裏測試你的答案。謝謝。 – Alaor

0

我注意到了代碼中我想提出的其他內容。

public ObservableCollection<Account> AllAccounts; 
public ObservableCollection<Transaction> AllTransactions; 

都是公共變量。我寫了一個小應用程序來測試你的問題,我很確定這些值應該是屬性。如果你至少綁定了它們。我這樣說是因爲以下原因。

我寫了一個小的應用程序與屬性和公共變量:

public ObservableCollection<tClass> MyProperty { get; set; } 

public ObservableCollection<tClass> MyPublicVariable; 

一個小的測試UI

<ListBox ItemsSource="{Binding MyProperty}" DisplayMemberPath="Name" Grid.Column="0"/> 
     <Button Content="Add" Grid.Column="1" Click="Button_Click" Height="25"/> 
     <ListBox ItemsSource="{Binding MyPublicVariable}" Grid.Column="2" DisplayMemberPath="Name"/> 

這將增加東西到每個變量的方法:

public void AddTestInstance() 
{ 
    tClass test = new tClass() { Name = "Test3" }; 

    MyProperty.Add(test); 
    MyPublicVariable.Add(test); 
    NotifyPropertyChanged("MyPublicVariable"); 
} 

我發現MyProperty更新了UI很好,但即使是wh我在MyPublicVariable上調用NotifyPropertyChanged,UI未更新。

因此,請嘗試使AllAccountsAllTransactions屬性代替。

+0

兄弟,謝謝你的回答,但我不認爲這是真實的用戶界面,甚至調試代碼的交易不會出現在收集account.Transactions直到我處置我的上下文,看看我對柯克的評論布羅德赫斯特。無論如何,我會在晚些時候在家裏測試你的答案。 – Alaor

0

我遇到過類似的問題,因爲DBML與您的數據庫不同步。你有沒有試圖刪除你的DBML並重新創建它?

+0

我沒有一個DBML文件,我已經創建了所有我的linq類從scretch,Windows Phone VS不允許使用工具創建這些類,我猜。可能在後臺生成DBML? – Alaor

+0

我不熟悉Windows手機部分,只能用Linq2sql .. – Pleun

1

2011-10-07 - 更新:

這確實一直困擾着我,因爲你指出瞭解決方案如何凌亂了,即使它的工作,所以我進一步挖成一個解決方案,並已拿出新的:)或更確切地說,你原來的一個修改版本,我認爲你正在尋找。我不知道該在答案中放什麼,但是我會調出幾個區域,然後在我的FTP問題得到解決後,用最新版本更新我的博客上的代碼示例。

賬戶類 - 把你的代碼放回onAttach和onDetatch方法(我做了lambdas,但它基本上是一樣的)。

public Account() 
{ 
    _transactions = new EntitySet<Transaction>(
     (addedTransaction) => 
     { 
      NotifyPropertyChanging("Account"); 
      addedTransaction.Account = this; 
     }, 
     (removedTransaction) => 
     { 
      NotifyPropertyChanging("Account"); 
      removedTransaction.Account = null; 
     }); 
} 

Account類 - 更新了EntitySet的公會屬性:

[Association(
    Storage = "_transactions", 
    ThisKey = "AccountId", 
    OtherKey = "_accountId")] 

交易類 - 更新了的EntityRef添加缺少的關鍵和IsForeignKey屬性的關聯屬性:

[Association(
    Storage = "_account", 
    ThisKey = "_accountId", 
    OtherKey = "AccountId", 
    IsForeignKey = true)] 

最後,這裏是我測試的更新方法:

// test method 
public void AddAccount() 
{ 
    Account c1 = new Account() { Tag = DateTime.Now }; 
    accountRepository.Add(c1); 
    accountRepository.Save(); 
    LoadCollections(); 
} 

// test method 
public void AddTransaction() 
{ 
    Account c1 = accountRepository.GetLastAccount(); 
    c1.Transactions.Add(new Transaction() { Tag = DateTime.Now }); 
    accountRepository.Save(); 
    LoadCollections(); 
} 

請注意,我將Transaction添加到Account - 我在保存時未將Account的值設置爲Transaction。我認爲這一點,加上IsForeignKey設置是你原來的解決方案嘗試中缺少的。嘗試一下,看看它是否對你更好。

2011-10-05 - 更新:

確定 - 它看起來像我錯過了我原來的答覆東西。根據評論,我認爲這個問題與Linq to SQL的一個怪癖有關。當我對原始項目進行了以下更改時,它似乎有效。

public void AddTransaction() 
{ 
    Account c1 = accountRepository.GetLastAccount(); 
    Transaction t1 = new Transaction() { Account = c1, Tag = DateTime.Now }; 
    c1.Transactions.Add(t1); 
    transactionRepository.Add(t1); 
    accountRepository.Save(); 
    transactionRepository.Save(); 
    LoadCollections(); 
} 

基本上,增加了Transaction對象時,我不得不把新Transaction添加到收藏TransactionsAccount對象。我不認爲你必須這樣做,但它似乎工作。讓我知道如果這不起作用,我會嘗試別的。

原始回答:

我相信這是一個數據綁定的怪癖。我建了,你可以從我的博客下載示例,但我給你提供的代碼所做的最大的變化是與屬性更換的ObservableCollection領域:

private ObservableCollection<Account> _accounts = new ObservableCollection<Account>(); 

public ObservableCollection<Account> Accounts 
{ 
    get { return _accounts; } 

    set 
    { 
     if (_accounts == value) 
      return; 
     _accounts = value; 
     NotifyPropertyChanged("Accounts"); 
    } 
} 

private ObservableCollection<Transaction> _transactions = new ObservableCollection<Transaction>(); 

public ObservableCollection<Transaction> Transactions 
{ 
    get { return _transactions; } 

    set 
    { 
     if (_transactions == value) 
      return; 
     _transactions = value; 
     NotifyPropertyChanged("Transactions"); 
    } 
} 

我還刪除了附加/ detatch代碼,因爲它是不是真的需要。這裏是我的帳戶構造函數現在:

public Account() 
{ 
    _transactions = new EntitySet<Transaction>(); 
} 

我無法從你的樣品出來,但要確保每個表都有一個PK定義:

// Account table 
[Column(
    AutoSync = AutoSync.OnInsert, 
    DbType = "Int NOT NULL IDENTITY", 
    IsPrimaryKey = true, 
    IsDbGenerated = true)] 
public int AccountID 
{ 
    get { return _AccountID; } 
    set 
    { 
     if (_AccountID == value) 
      return; 
     NotifyPropertyChanging("AccountID"); 
     _AccountID = value; 
     NotifyPropertyChanged("AccountID"); 
    } 
} 

// Transaction table 
[Column(
    AutoSync = AutoSync.OnInsert, 
    DbType = "Int NOT NULL IDENTITY", 
    IsPrimaryKey = true, 
    IsDbGenerated = true)] 
public int TransactionID 
{ 
    get { return _TransactionID; } 
    set 
    { 
     if (_TransactionID == value) 
      return; 
     NotifyPropertyChanging("TransactionID"); 
     _TransactionID = value; 
     NotifyPropertyChanged("TransactionID"); 
    } 
} 

您可以從http://chriskoenig.net/upload/WP7EntitySet.zip下載我的版本的應用 - 只需確保在添加交易之前添加一個賬戶:)

+0

兄弟,謝謝你的耐心和時間,我真的很感激,但恐怕它沒有用。對不起,如果這是我的錯誤解釋,但是,如果你調試你的項目,你添加一個帳戶,確定,然後你添加一個交易,那麼,如果你看到帳戶對象內的交易關係,它仍然沒有有你剛剛創建的交易。我拍了一張可能有助於顯示問題出在哪裏的照片,請查看:http://tinyurl.com/62suzjw謝謝您的幫助。 – Alaor

+0

現在我明白你在說什麼了。我沒有看到嵌套計數。讓我看看我能弄清楚並更新我的文章。 –

+0

沒關係,這是我的錯,我的英語技能很差。您的更新答案有效,但在更新一個交易的場景中會非常麻煩,例如,我必須存儲舊帳戶,如果用戶更改了帳戶,我應該將其從舊帳戶中移除,然後將這個事務添加到新的事務中,如果Linq to SQL可以爲我管理它,事情會變得更加容易。但是我假設L2S不提供這種功能,因爲在幾周內我無法通過互聯網找到任何真正的修復(真的很糟糕,MSFT!),或者確定我做錯了什麼。但是,謝謝,無論如何,我會接受它! – Alaor

0

其實,從技術角度看,您所看到的行爲是有道理的。

LinqToSql與大多數其他ORM一樣,使用IdentityMap來跟蹤加載的實體以及對它們的更改。這是默認的L2S模型實現INotifyPropertyChanged的原因之一,因此L2S引擎可以訂閱這些事件並相應地執行操作。

我希望這可以澄清當您更改實體的屬性時會發生什麼。但是當你想將一個實體添加到數據庫時會發生什麼?有兩個選項可以讓L2S知道你想添加它:你明確地告訴它(通過DataContext.Table。InsertOnSubmit),或者將其附加到L2S跟蹤其IdentityMap的現有實體。

我建議您閱讀MSDN上的Object States and Change Tracking article,因爲這會澄清您的理解。

public void LoadCollections() 
{ 
    AllAccounts = accountRepository.GetAll(); 
    NotifyPropertyChanged("AllAccounts"); 
    AllTransactions = transactionRepository.GetAll(); 
    NotifyPropertyChanged("AllTransactions"); 
} 

public void InsertTransaction(Transaction transaction) 
{ 
    AllTransactions.Add(transaction); 
    transactionRepository.Add(transaction); 

    LoadCollections(); // Tried this to update the Accounts with newer values, but don't work... 
} 

雖然效率低下,可能不是最好的設計,但可以使其工作(取決於transactionRepository.Add的實現)。當您將新的Transaction添加到AllTransactions時,L2S不可能知道需要插入該對象,因爲它不在被跟蹤的對象圖中。正確地,您然後撥打transactionRepository.Add,這可能會調用InsertOnSubmit(transaction)SubmitChanges()(最後一部分很重要)。如果您現在調用LoadCollections(並且您的數據綁定設置正確!),您應該看到包含新事務的AllTransactions

作爲最後一點,您應該查看Aggregate Roots的概念。一般來說,你有一個倉庫實施每個聚合(交易和帳戶都可以)。

+0

實際上,這段代碼基於MSDN的一個例子,它們將對象添加到集合和數據庫中,因此它們不必再次加載所有集合。這是我的目標。 LoadColletion存在的唯一原因是因爲我試圖使交易出現在account.Transactions集合中。如果它能夠工作,它將在以後更改。我的煩惱是因爲在實體框架中我得到了我想要的東西,如果我添加一個事務並保存,它會出現在account.Transactions集合中,我希望它在L2S中的工作方式是一樣的,但不會出現這種情況。 – Alaor

+0

關於Aggreagate Roots,如果我理解正確,那麼有些功能會直接處理交易,還有一些與帳戶相關的其他功能。您可以認爲交易在某種程度上是賬戶的「孩子」,但在代碼中有時它是唯一重要的對象(例如,如果我需要按類別劃分費用圖表,我不想穿過賬戶,我想直接查詢交易),我不認爲這是事實。 – Alaor

+0

有時候我恐怕沒有正確地說我想說的話,簡而言之,我想添加一個事務,保存後(SubmitChanges)它應該出現在它所屬的賬戶對象的「Transactions」集合中,而沒有需要處理上下文並再次創建它。這不是一個UI綁定問題。我正在閱讀您提供的鏈接,稍後我會提供反饋。 – Alaor