LINQ到SQL关系不会更新集合
本文关键字:更新 集合 关系 SQL LINQ | 更新日期: 2023-09-27 18:09:45
我在Windows Phone (SQL Server CE)的LINQ to SQL中遇到一个问题。
我正在开发一个个人理财应用程序,然后我有帐户类和事务类。每个Transaction类都有一个对其所属帐户的引用,因此account类具有一对多关系的事务集合。然后我有存储库(AccountRepository和TransactionRepository),它们公开用于插入、删除、findbykey的方法,并返回每个类的所有实例。ViewModel有对其存储库的引用。好的,一切都很好,但是,当我创建一个交易时,它没有出现在Account中。事务收集,直到我停止软件并再次运行它。
下面是一些代码片段,首先是模型类:[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;
}
// {...}
}
然后我有一些实现GetAll()方法的存储库,该方法返回一个ObservableCollection。存储库有一个对在ViewModel类中创建的Context类的引用,如下所示:
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());
}
// {...}
}
My ViewModel在构造函数中初始化存储库,然后用一些逻辑代码公开方法来插入、删除等,每个类型。
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 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!
如果能发布一个更完整的代码示例来演示具体的问题,那就太好了。
至于让Parent->Child one:许多关系像你期望的那样工作,这是L2S没有直接实现的功能。而是在DataContext中实现的。这是在圣子里完成的。父属性设置器,让子属性将自己添加到父属性EntitySet实例中。
只要您在运行时使用维护此绑定的setter来生成parent<->子关系,此机制就可以工作。
通过代码示例,下面是由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");
}
}
}
注意事务。添加和事务。删除碎片……这是父项上EntitySet<>的管理-它不会在L2S层中自动发生。
是的,这是很多胶水要写。对不起。我的建议是,如果你有一个复杂的数据模型,有很多关系,用DBML建模,让VS为你吐出一个数据上下文。需要进行一些有针对性的更改以删除该DataContext对象上的一些构造函数,但是设计人员生成的99%的内容将正常工作。
镜头盖拉多开发者,Windows Phone.
2011-10-07 -更新:
自从你指出解决方案有多混乱以来,这真的一直困扰着我,尽管它有效,所以我进一步挖掘了一个解决方案,并提出了一个新的解决方案:)或者更确切地说,是你原来的一个修改版本,我认为它做了你正在寻找的。我不知道该在这里回答什么,但我会指出几个方面,然后在我解决FTP问题后,用最新版本更新我博客上的代码示例。
Account类-把你的代码放回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
-不设置Transaction
的Account
值,当我保存它。我认为这一点,加上添加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
添加到原始Account
对象的Transactions
集合中。我以为你没必要这么做,但似乎很管用。如果这行不通,请告诉我,我将尝试其他方法。
原始回答:
我相信这是一个数据绑定的怪癖。我构建了一个示例,您可以从我的博客下载,但我对您提供的代码所做的最大更改是将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");
}
}
我还删除了附加/分离代码,因为它不是真的需要。下面是我的Account构造函数:
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下载我的这个应用程序的版本-只要确保你添加一个帐户之前,你添加交易:)
在LoadCollection()你正在更新属性AllAccounts和AllTransactions,但你没有通知UI,他们已经改变了。您可以在设置属性后通过引发PropertyChanged事件来通知UI—在属性设置器中是执行此操作的好地方。
我有类似的问题,原因是重新创建集合,就像你在LoadCollections()中做的那样。然而,你调用NotifyPropertyChanged(..collectionname..)必须确保UI刷新,但也许只是尝试这样做:
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"),因为这个属性不能再改变了。我总是使用这种方法来定义视图模型中的集合。
我注意到代码中还有一些我想要提出的问题。
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很好,但即使我在MyPublicVariable
上调用NotifyPropertyChanged, UI也没有更新。
因此,尝试使用AllAccounts
和AllTransactions
属性。
我遇到过类似的问题,因为DBML与数据库不同步。您是否尝试过删除DBML并重新创建它?
实际上,从技术上看,您所看到的行为是有意义的。
LinqToSql,像大多数其他orm一样,使用IdentityMap来跟踪加载的实体和对它们的更改。这就是默认L2S模型实现INotifyPropertyChanged的原因之一,这样L2S引擎就可以订阅这些事件并采取相应的行动。
我希望这能澄清当你改变一个实体的属性时会发生什么。但是,当您想要向数据库添加一个实体时,会发生什么呢?有两个选项L2S可以知道你想要添加它:你显式地告诉它(通过DataContext.Table.InsertOnSubmit),或者你将它附加到L2S用其IdentityMap跟踪的现有实体。
我建议您阅读MSDN上的对象状态和更改跟踪文章,因为这将澄清您的理解。
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
。
最后需要注意的是,您应该查看聚合根的概念。一般来说,每个聚合有一个存储库实现(事务和帐户就可以)。