WP8 и Linq to SQL с отношением «один ко многим»: SubmitChanges() удаляет неверный объект

У меня есть следующие объекты/таблицы:

  • Board: На одной плате может быть много контактов.
  • Pin: One pin is assigned to one board. This entity is abstract and does have childs with different implementations. All childs belonging to the parent pin entity with InheritanceMapping and will be saved into the pin table and distinguished by a Discriminator column
    • TaskPin: This is one child implementations of pin. It can have many Tasks.
  • Task: Одна задача назначается одному TaskPin

Вот некоторый код, чтобы сделать мою структуру более понятной:

[Table]
public class Board : ModelBase
{
    private int _boardId;

    [Column(IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL Identity"
                         ,CanBeNull = false, AutoSync = AutoSync.OnInsert)]
    public int BoardId
    {
        get { return _boardId; }
        set { SetProperty(ref _boardId, value); }
    }

    private EntitySet<Pin> _pins;

    [Association(Storage = "_pins", OtherKey = "_boardId"
    ,ThisKey = "BoardId", DeleteRule = "CASCADE")]
    public EntitySet<Pin> Pins
    {
        get { return _pins; }
        set { _pins.Assign(value); }
    }

    public Board()
    {
        _pins = new EntitySet<Pin>(new Action<Pin>(this.addPin)
            ,new Action<Pin>(this.removePin));
    }

    private void addPin(Pin pin)
    {
        NotifyPropertyChanging("Pin");
        pin.Board = this;
    }

    private void removePin(Pin pin)
    {
        NotifyPropertyChanging("Pin");
        pin.Board = null;
    }
}

[Table]
[InheritanceMapping(Code = PinType.TaskPin, Type = typeof(TaskPin)
             ,IsDefault = true)]
public abstract class Pin : ModelBase
{
    private int _pinId;

    [Column(IsPrimaryKey = true, IsDbGenerated = true
         ,DbType = "INT NOT NULL Identity", AutoSync = AutoSync.OnInsert)]
    public int PinId
    {
        get { return _pinId; }
        set { SetProperty(ref _pinId, value); }
    }

    [Column]
    internal int _boardId;

    private EntityRef<Board> _board;

    [Association(Storage = "_board", ThisKey = "_boardId"
        ,OtherKey = "BoardId", IsForeignKey = true, DeleteOnNull = true)]
    public Board Board
    {
        get { return _board.Entity; }
        set
        {
            if (SetProperty(ref _board, value) != null)
            {
                _boardId = value.BoardId;
            }
        }
    }

    [Column(IsDiscriminator = true)]
    public PinType Type { get; set; }


    public Pin()
    {

    }
}

public class TaskPin : Pin
{
    private EntitySet<Task> _tasks;

    [Association(Storage = "_tasks", OtherKey = "_pinId"
        ,ThisKey = "PinId", DeleteRule = "CASCADE")]
    public EntitySet<Task> Tasks
    {
        get { return _tasks; }
        set { _tasks.Assign(value); }
    }

    public TaskPin()
    {
        _tasks = new EntitySet<Task>(new Action<Task>(this.addTask)
               ,new Action<Task>(this.removeTask));
    }

    private void addTask(Task task)
    {
        NotifyPropertyChanging("Task");
        task.Pin = this;
    }

    private void removeTask(Task task)
    {
        NotifyPropertyChanging("Task");
        task.Pin = null;
    }
}

[Table]
public class Task : ModelBase
{
    private int _taskId;

    [Column(IsPrimaryKey = true, IsDbGenerated = true
                       ,DbType = "INT NOT NULL Identity"
                       ,CanBeNull = false, AutoSync = AutoSync.OnInsert)]
    public int TaskId
    {
        get { return _taskId; }
        set { SetProperty(ref _taskId, value); }
    }

    [Column]
    internal int _pinId;

    private EntityRef<Pin> _pin;

    [Association(Storage = "_pin", ThisKey = "_pinId"
                         ,OtherKey = "PinId"
                         ,IsForeignKey = true
                         ,DeleteOnNull=true)]
    public Pin Pin
    {
        get { return _pin.Entity; }
        set
        {
            if (SetProperty(ref _pin, value) != null)
            {
                _pinId = value.PinId;
            }
        }
    }

    public Task()
    {

    }
}

Я создаю TaskPin и назначаю его доске. Затем я создаю две задачи и назначаю их TaskPin. Это работает нормально. Проблема возникает, когда я пытаюсь получить один или несколько Tasks из TaskPin:

    private void OnDeleteTasks(object sender, EventArgs e)
    {
        TaskPin taskPin = pin as TaskPin;
        var completedTasks = taskPin.Tasks
                            .Where(x => x.IsDone == true)
                            .ToList();

        foreach (var task in completedTasks)
        {
            taskPin.Tasks.Remove(task);
        }
    }

Если я вызову затем SubmitChanges() для моего объекта DataContext, он установит Board property TaskPin (унаследованного от Pin) на null.

    public void Save(Pin pin)
    {
        // This is empty so no modified members are identified => Correct
        var modifiedMembers = db.Pins.GetModifiedMembers(pin);

        // Contains just one entry for the deleted Task entity => Correct
        var changeSet = db.GetChangeSet();

        // This call will immediately set Board property of Pin to null => Wrong!
        db.SubmitChanges();
    }

Я ожидаю, что Task будет удален, потому что DeleteOnNull имеет значение true, но я не знаю, почему Board property PIN-кода также имеет значение null, что приведет к NullPointerExceptio или к тому, что Pin также будет удален.

Я выполнил поиск в Google по этой теме, но не нашел ничего, что решило бы мою проблему. Альтернативой может быть предотвращение обнуления свойства Board и вызов DeleteOnSubmit() для задачи вручную.


person zrkl    schedule 06.10.2014    source источник
comment
Как вы используете datacontext — один раз создаете или каждый раз пересоздаете? Вы знакомы с профилировщиком sql? Кажется, у вас есть две транзакции, и я могу просматривать их с помощью профилировщика.   -  person Danila Polevshikov    schedule 16.10.2014
comment
Содержит ли ваш changeSet ненулевую ссылку на Board? Я подозреваю, что ваш граф объектов не загружается из-за других факторов.   -  person Mrchief    schedule 16.10.2014
comment
@Mrchief, что вы имеете в виду под графом объектов? ChangeSet содержит одну запись для удаляемого TaskPin.   -  person zrkl    schedule 16.10.2014
comment
Board — это ассоциация внешнего ключа в вашем TaskPin. Это может или не может загружаться автоматически на основе других параметров, которые вы можете указать в своем контексте данных. Если он не загружается автоматически, он будет нулевым, и когда вы его сохраните, он будет стерт, поскольку контекст данных не знает, является ли он нулевым, потому что он не был загружен, или нулевым, потому что вы хотите удалить его.   -  person Mrchief    schedule 16.10.2014
comment
В дополнение к комментарию MrChief вы можете использовать (и, возможно, следует) метод расширения Include, где вы указываете, что еще следует загрузить. Связанный вопрос: stackoverflow.com/questions/6761104/   -  person Krzysztof Skowronek    schedule 01.12.2014
comment
@user3512524 user3512524 Метод расширения Include доступен только в Entity Framework. Я просто использую LINQ to SQL.   -  person zrkl    schedule 29.12.2014


Ответы (1)


Мне кажется, что поведение предназначено.

Если вы посмотрите на свойство Pin в классе Task, оно будет удалено при нулевом значении (DeleteOnNull = true), после чего >Pin в Task будет удален в соответствии с removeTask() в классе TaskPin. Затем посмотрите на метод removePin() в классе Board, который устанавливает для Board Pin значение null. Затем посмотрите на свойство Board в классе Pin, для DeleteOnNull установлено значение true, что удалит Board из Board. strong>Закрепить экземпляр, если установлено значение null.

person workaholic    schedule 14.01.2015