6

I have the following entites/tables:

  • Board: One board can have many pins
  • 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: One task is assigned to one TaskPin

Here is some code to make my structure more clear:

[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()
    {

    }
}

I create a TaskPin and assign it to a board. Then I create two Tasks and assign them to the TaskPin. This does works fine. The problem occurrs when I try to the one or more Tasks from the 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);
        }
    }

If I call then SubmitChanges() on my DataContext object, it will set the Board property of the TaskPin (inherited from Pin) to 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();
    }

I expect that the Task will be deleted because DeleteOnNull is set to true but I don't know why the Board property of Pin is also set to null which will result in a NullPointerExceptio or that the Pin is also deleted.

I did a google search on this topic but I don't find anything which solved my problem. An Alternative would be to prevent the nulling of the Board property and call DeleteOnSubmit() for Task manually.

Yuri
  • 2,820
  • 4
  • 28
  • 40
zirkelc
  • 1,451
  • 1
  • 23
  • 49
  • How do you use datacontext - once create or recreate everytime? Do you familiar with sql profiler? It seems that you have two transaction and i can be viewer with profiler. – Danila Polevshchikov Oct 16 '14 at 08:07
  • Does your `changeSet` contain a non-null reference to `Board`? I'm suspecting that your object graph is not getting loaded due to other factors. – Mrchief Oct 16 '14 at 13:47
  • @Mrchief what do you mean with object graph? The changeSet contains one entry for the TaskPin to be deleted. – zirkelc Oct 16 '14 at 18:36
  • `Board` is a foreign key association in your `TaskPin`. This may or may not get automatically loaded based on other options that you can specify on your data context. If it doesn't get loaded automatically, it will be null and when you save it, it'll get wiped out since the datacontext doesn't know whether it is null because it wasn't loaded or whether it is null because you want to delete it. – Mrchief Oct 16 '14 at 18:58
  • In addidtion to MrChief comment, you can use (and maybe should) Include extension method, where you specify what else should be loaded. Related question: http://stackoverflow.com/questions/6761104/how-does-linq-expression-syntax-work-with-include-for-eager-loading – Krzysztof Skowronek Dec 01 '14 at 14:09
  • @user3512524 The Include extension method seems to be available only with the Entity Framework. I'm just using LINQ to SQL. – zirkelc Dec 29 '14 at 18:00

1 Answers1

0

To me it appears that the behavior is intended.

If you look at the Pin property in Task class, it is set to be deleted on Null (DeleteOnNull = true) which then the Pin in Task will be deleted as per removeTask() in TaskPin class. Then look at the removePin() method in Board class which sets Board of the Pin to null. Then look at the Board property in Pin class, DeleteOnNull is set to true which will delete the Board from Pin instance when set to null.

workaholic
  • 31
  • 3