Я пытаюсь смоделировать следующие отношения, используя LINQ to SQL на Windows Phone 7.1:
create table x (id INTEGER PRIMARY KEY AUTOINCREMENT, text STRING);
create table y (id INTEGER PRIMARY KEY AUTOINCREMENT, text STRING);
create table z (id INTEGER PRIMARY KEY AUTOINCREMENT,
x_id INTEGER NOT NULL,
y_id INTEGER NOT NULL,
FOREIGN KEY(x_id) REFERENCES x(id) ON DELETE CASCADE,
FOREIGN KEY(y_id) REFERENCES y(id) ON DELETE CASCADE);
Поэтому, если у меня есть запись в таблице z, ее следует удалять всякий раз, когда я удаляю либо запись x с идентификатором x_id, либо запись y с идентификатором y_id.
У меня есть следующий код C # (на примере http://msdn.microsoft.com/en-us/library/hh286405(v=vs.92).aspx):
namespace linq2sql.test.program
{
public class MyDatabase : DataContext
{
public MyDatabase(string connectionString)
: base(connectionString)
{ }
public Table<X> Xs;
public Table<Y> Ys;
public Table<Z> Zs;
}
public class BaseTable : INotifyPropertyChanged, INotifyPropertyChanging
{
// boring interface implementation omitted for brevity
}
[Table]
public class X : BaseTable
{
private int _id;
[Column(IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL Identity", CanBeNull = false, AutoSync = AutoSync.OnInsert)]
public int Id
{
get { return _id; }
set
{
if (_id != value)
{
NotifyPropertyChanging("Id");
_id = value;
NotifyPropertyChanged("Id");
}
}
}
private EntitySet<Z> _zs;
[Association(Storage = "_zs", OtherKey = "_xId", ThisKey = "Id", DeleteRule="CASCADE")]
public EntitySet<Z> Zs
{
get { return this._zs; }
set { this._zs.Assign(value); }
}
public X()
{
_zs = new EntitySet<Z>(
new Action<Z>(this.attach_z),
new Action<Z>(this.detach_z)
);
}
private void attach_z(Z z)
{
NotifyPropertyChanging("Z");
z.X = this;
}
private void detach_z(Z z)
{
NotifyPropertyChanging("Z");
z.X = null;
}
}
[Table]
public class Y : BaseTable
{
// exactly the same as table X
}
[Table]
public class Z : BaseTable
{
private int _id;
[Column(IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL Identity", CanBeNull = false, AutoSync = AutoSync.OnInsert)]
public int Id
{
get { return _id; }
set
{
if (_id != value)
{
NotifyPropertyChanging("Id");
_id = value;
NotifyPropertyChanged("Id");
}
}
}
[Column]
internal int _xId;
private EntityRef<X> _x;
[Association(Storage = "_x", ThisKey = "_xId", OtherKey = "Id", IsForeignKey = true)]
public X X
{
get { return _x.Entity; }
set
{
if (_x.Entity != value)
{
NotifyPropertyChanging("X");
_x.Entity = value;
if (value != null)
{
_xId = value.Id;
}
NotifyPropertyChanged("X");
}
}
}
[Column]
internal int _yId;
private EntityRef<Y> _y;
[Association(Storage = "_y", ThisKey = "_yId", OtherKey = "Id", IsForeignKey = true)]
public Y Y
{
get { return _y.Entity; }
set
{
if (_y.Entity != value)
{
NotifyPropertyChanging("Y");
_y.Entity = value;
if (value != null)
{
_yId = value.Id;
}
NotifyPropertyChanged("Y");
}
}
}
}
}
Я начал тестировать его и столкнулся с двумя, возможно, связанными, проблемами. Работает следующий код вставки:
using (MyDatabase db = new MyDatabase(KDBConnectionString))
{
if (!db.DatabaseExists())
{
db.CreateDatabase();
}
X x = new X();
db.Xs.InsertOnSubmit(x);
Y y = new Y();
db.Ys.InsertOnSubmit(y);
Z z = new Z();
x.Zs.Add(z);
y.Zs.Add(z);
db.SubmitChanges();
Assert.IsTrue(db.Xs.Count() == 1, "no x");
Assert.IsTrue(db.Ys.Count() == 1, "no y");
Assert.IsTrue(db.Zs.Count() == 1, "no z");
Assert.IsTrue(db.Xs.First().Zs.Count() == 1, "no z in x");
Assert.IsTrue(db.Ys.First().Zs.Count() == 1, "no z in y");
}
Но этот код, который, как я ожидал, будет эквивалентен, не:
using (MyDatabase db = new MyDatabase(KDBConnectionString))
{
if (!db.DatabaseExists())
{
db.CreateDatabase();
}
X x = new X();
db.Xs.InsertOnSubmit(x);
Y y = new Y();
db.Ys.InsertOnSubmit(y);
Z z = new Z() { X = x, Y = y };
db.Zs.InsertOnSubmit(z);
db.SubmitChanges();
Assert.IsTrue(db.Xs.Count() == 1, "no x");
Assert.IsTrue(db.Ys.Count() == 1, "no y");
Assert.IsTrue(db.Zs.Count() == 1, "no z");
Assert.IsTrue(db.Xs.First().Zs.Count() == 1, "no z in x"); // fails here
Assert.IsTrue(db.Ys.First().Zs.Count() == 1, "no z in y");
}
Каскадное удаление не работает, как я ожидал. Это работает:
using (MyDatabase db = new MyDatabase(KDBConnectionString))
{
if (!db.DatabaseExists())
{
db.CreateDatabase();
}
X x = new X();
db.Xs.InsertOnSubmit(x);
Y y = new Y();
db.Ys.InsertOnSubmit(y);
Z z = new Z();
x.Zs.Add(z);
y.Zs.Add(z);
db.SubmitChanges();
db.Zs.DeleteOnSubmit(z);
db.SubmitChanges();
Assert.IsTrue(db.Zs.Count() == 0, "z not deleted");
Assert.IsTrue(x.Zs.Count() == 0, "z not removed from x");
Assert.IsTrue(y.Zs.Count() == 0, "z not removed from y");
}
Это не:
using (MyDatabase db = new MyDatabase(KDBConnectionString))
{
if (!db.DatabaseExists())
{
db.CreateDatabase();
}
X x = new X();
db.Xs.InsertOnSubmit(x);
Y y = new Y();
db.Ys.InsertOnSubmit(y);
Z z = new Z();
x.Zs.Add(z);
y.Zs.Add(z);
db.SubmitChanges();
db.Xs.DeleteOnSubmit(x);
db.SubmitChanges();
Assert.IsTrue(db.Zs.Count() == 0, "z not deleted"); // ...this works, so "CASCADE" parameter has some effect
Assert.IsTrue(y.Zs.Count() == 0, "z not removed from y"); // ... but fails here, so something is off
}
Полагаю, у меня есть какая-то простая ошибка в определении внешнего ключа, но я сам не смог ее найти, и мой Google-Fu сегодня слабый. Вся помощь приветствуется.