Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Problem deleting items from child collections #183

Open
bobgodfrey opened this issue Jul 25, 2017 · 3 comments
Open

Problem deleting items from child collections #183

bobgodfrey opened this issue Jul 25, 2017 · 3 comments

Comments

@bobgodfrey
Copy link

Tony,

I really like the framework and just getting started so I am assuming that I am doing something incorrect. I have an issue removing items from child collections and getting them deleted the database. It seems to be isolated to the ApplyChanges method as it is not attaching the deleted items to the DB context??? In the example below, I am inserting a new order with details, loading the order (and details) from the DB, and then updating the loaded order/details, finally saving the order. The issue is that I am removing the second order detail item from the order.OrderDetails collection but it does not get deleted. It looks like the change tracking is working correctly as the trackable entities state is correct but again it never attaches the deleted child item to the DB context to set the EF state so the record still exists in the database after the save. It may be that you didn’t intend for this use case as I am interested in server side only tracking of disconnected entities.

See the example below used with the northwindslim DB. I am running the latest and greatest from Nuget for all and specifically the TrackableEntities.EF.6 project - 2.5.4. I can send you my sample project if it’s easier?

Also, if I was to debug this what branch are your nugget release processed on?

Thanks
Bob

The output looks like this –
Insert new Order

Loaded the new Order from DB with no tracking

After db.ApplyChanges the second order detail state is Detached and should be Deleted

First order detail - EF state: Modified Trackable Entity State: Modified
Second order detail - EF state: Detached Trackable Entity State: Deleted
third order detail - EF state: Unchanged Trackable Entity State: Unchanged
fouth order detail - EF state: Added Trackable Entity State: Added

Press any key to terminate

The code is below.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
using TrackableEntities.Client;
using TrackableEntities.Common;
using TrackableEntities.EF6;

namespace TE_DeleteIssue
{
class Program
{
static void Main(string[] args)
{
Test1_SaveParentChild Test1_SaveParentChild = new Test1_SaveParentChild();
Test1_SaveParentChild.Test();
Console.WriteLine();
Console.WriteLine();
Console.WriteLine("Press any key to terminate");
Console.ReadLine();
}
}
public class Test1_SaveParentChild
{

    public void Test()
    {
        DBHelper dbHelper = new DBHelper();

        Console.WriteLine();
        Console.WriteLine("Insert new Order");
        //Save a new order
        Order InsertedOrder = dbHelper.OrderInsert();



        // Get the new order with No tracking 
        Console.WriteLine();
        Console.WriteLine("Loaded the new Order from DB with no tracking");
        var orderFromDB = dbHelper.GetOrder(InsertedOrder.OrderId)[0];


        // ModifyOrderNoSave - modify the order and remove the second orderDetail            
        // Start Tracking Order
        var changeTracker = new ChangeTrackingCollection<Order>(orderFromDB);
        OrderDetailUpdateHelper OrderDetailUpdateHelper = new OrderDetailUpdateHelper();
        OrderDetailUpdateHelper.ModifyOrderNoSave(orderFromDB);

        // Save the order
        using (var db = new NorthwindSlim())
        {
            db.ApplyChanges(orderFromDB);

            OrderDetailUpdateHelper.firstState = db.Entry(OrderDetailUpdateHelper.first).State;

            //second state is Detached and should be Deleted
            OrderDetailUpdateHelper.secondState = db.Entry(OrderDetailUpdateHelper.second).State;

            OrderDetailUpdateHelper.thirdState = db.Entry(OrderDetailUpdateHelper.third).State;
            OrderDetailUpdateHelper.forthState = db.Entry(OrderDetailUpdateHelper.forth).State;

            db.SaveChanges();

        }


        Console.WriteLine();
        Console.WriteLine();
        Console.WriteLine("After db.ApplyChanges the second order detail state is Detached and should be Deleted");
        Console.WriteLine();
        Console.WriteLine("first oder detail - EF state: {0}  Trackable Entity State: {1} ", OrderDetailUpdateHelper.firstState, OrderDetailUpdateHelper.first.TrackingState);
        Console.WriteLine("second oder detail -EF state: {0}  Trackable Entity State: {1} ", OrderDetailUpdateHelper.secondState, OrderDetailUpdateHelper.second.TrackingState);
        Console.WriteLine("third oder detail - EF state: {0}  Trackable Entity State: {1} ", OrderDetailUpdateHelper.thirdState, OrderDetailUpdateHelper.third.TrackingState);
        Console.WriteLine("fouth oder detail - EF state: {0}  Trackable Entity State: {1} ", OrderDetailUpdateHelper.forthState, OrderDetailUpdateHelper.forth.TrackingState);


      
        orderFromDB.AcceptChanges();
   
    }
}


public class OrderDetailUpdateHelper
{
    public OrderDetail first;
    public OrderDetail second;
    public OrderDetail third;
    public OrderDetail forth;

    public EntityState firstState;
    public EntityState secondState;
    public EntityState thirdState;
    public EntityState forthState;

    public void ModifyOrderNoSave(Order orderFromDB)
    {

        // Modify order parent record
        orderFromDB.Freight = 2;

        // increment first order unit price
        first = orderFromDB.OrderDetails.ElementAt(0);
        second = orderFromDB.OrderDetails.ElementAt(1);
        third = orderFromDB.OrderDetails.ElementAt(2);


        // update the first item
        first.UnitPrice++;

        //delete second order            
        orderFromDB.OrderDetails.Remove(second);

        // nothing for the third

        // add a forth
        forth = new OrderDetail
        {
            OrderId = orderFromDB.OrderId,
            ProductId = 4,
            Quantity = 20,
            UnitPrice = 40
        };

        // create a 4th order
        orderFromDB.OrderDetails.Add(forth);

    }

}

public class DBHelper
{
    public Order OrderInsert()
    {
        string customerId = "ALFKI";

        var newOrder = new Order
        {
            CustomerId = customerId,
            OrderDate = DateTime.Today,
            ShippedDate = DateTime.Today.AddDays(1),
            Freight = 1,
            OrderDetails = new ChangeTrackingCollection<OrderDetail>
                {
                    new OrderDetail { ProductId = 1, Quantity = 5, UnitPrice = 10 },
                    new OrderDetail { ProductId = 2, Quantity = 10, UnitPrice = 20 },
                    new OrderDetail { ProductId = 3, Quantity = 15, UnitPrice = 30 }
                }
        };


        using (var db2 = new NorthwindSlim())
        {
            db2.Entry(newOrder).State = EntityState.Added;
            db2.SaveChanges();
        }



        return newOrder;


    }

    public List<Order> GetOrder(int OrderId)
    {
        using (var db = new NorthwindSlim())
        {

            List<Order> orderList = db.Orders
                        .Where(x => x.OrderId == OrderId)
                        .Include(od => od.OrderDetails)
                        .AsNoTracking()
                        .ToList();

            return orderList;

        }

    }

    public static void PrintOrderWithDetails(Order o)
    {
        Console.WriteLine("{0} {1} {2}",
            o.OrderId,
             o.Freight,
            o.OrderDate.GetValueOrDefault().ToShortDateString());
        foreach (var od in o.OrderDetails)
        {
            Console.WriteLine("\t{0} {1} {2} {3} {4}",
                od.TrackingState,
                 od.OrderId,
                od.ProductId,
                od.Quantity,
                od.UnitPrice.ToString("c"));


        }
    }

}




public partial class NorthwindSlim : DbContext
{
    static NorthwindSlim()
    {
        Database.SetInitializer(new NullDatabaseInitializer<NorthwindSlim>());
    }

    public NorthwindSlim()
        : base("name=NorthwindSlim")
    {
        Configuration.ProxyCreationEnabled = false;
    }

    public DbSet<Order> Orders { get; set; }
    public DbSet<OrderDetail> OrderDetails { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Order>()
            .Property(e => e.CustomerId)
            .IsFixedLength();

        modelBuilder.Entity<Order>()
            .Property(e => e.Freight)
            .HasPrecision(19, 4);

        modelBuilder.Entity<Order>()
            .HasMany(e => e.OrderDetails)
            .WithRequired(e => e.Order)
            .WillCascadeOnDelete(false);

        modelBuilder.Entity<OrderDetail>()
            .Property(e => e.UnitPrice)
            .HasPrecision(19, 4);

        ModelCreating(modelBuilder);
    }

    partial void ModelCreating(DbModelBuilder modelBuilder);
}




[Table("Order")]
public partial class Order : EntityBase
{
    public Order()
    {
        OrderDetails = new ChangeTrackingCollection<OrderDetail>();
    }

    public int OrderId
    {
        get { return _OrderId; }
        set
        {
            if (Equals(value, _OrderId)) return;
            _OrderId = value;
            NotifyPropertyChanged();
        }
    }
    private int _OrderId;

    [StringLength(5)]
    public string CustomerId
    {
        get { return _CustomerId; }
        set
        {
            if (Equals(value, _CustomerId)) return;
            _CustomerId = value;
            NotifyPropertyChanged();
        }
    }
    private string _CustomerId;

    public DateTime? OrderDate
    {
        get { return _OrderDate; }
        set
        {
            if (Equals(value, _OrderDate)) return;
            _OrderDate = value;
            NotifyPropertyChanged();
        }
    }
    private DateTime? _OrderDate;

    public DateTime? ShippedDate
    {
        get { return _ShippedDate; }
        set
        {
            if (Equals(value, _ShippedDate)) return;
            _ShippedDate = value;
            NotifyPropertyChanged();
        }
    }
    private DateTime? _ShippedDate;

    public int? ShipVia
    {
        get { return _ShipVia; }
        set
        {
            if (Equals(value, _ShipVia)) return;
            _ShipVia = value;
            NotifyPropertyChanged();
        }
    }
    private int? _ShipVia;

    [Column(TypeName = "money")]
    public decimal? Freight
    {
        get { return _Freight; }
        set
        {
            if (Equals(value, _Freight)) return;
            _Freight = value;
            NotifyPropertyChanged();
        }
    }
    private decimal? _Freight;

    public ChangeTrackingCollection<OrderDetail> OrderDetails
    {
        get { return _OrderDetails; }
        set
        {
            if (Equals(value, _OrderDetails)) return;
            _OrderDetails = value;
            NotifyPropertyChanged();
        }
    }
    private ChangeTrackingCollection<OrderDetail> _OrderDetails;
}



[Table("OrderDetail")]
public partial class OrderDetail : EntityBase
{
    public int OrderDetailId
    {
        get { return _OrderDetailId; }
        set
        {
            if (Equals(value, _OrderDetailId)) return;
            _OrderDetailId = value;
            NotifyPropertyChanged();
        }
    }
    private int _OrderDetailId;

    public int OrderId
    {
        get { return _OrderId; }
        set
        {
            if (Equals(value, _OrderId)) return;
            _OrderId = value;
            NotifyPropertyChanged();
        }
    }
    private int _OrderId;

    public int ProductId
    {
        get { return _ProductId; }
        set
        {
            if (Equals(value, _ProductId)) return;
            _ProductId = value;
            NotifyPropertyChanged();
        }
    }
    private int _ProductId;

    [Column(TypeName = "money")]
    public decimal UnitPrice
    {
        get { return _UnitPrice; }
        set
        {
            if (Equals(value, _UnitPrice)) return;
            _UnitPrice = value;
            NotifyPropertyChanged();
        }
    }
    private decimal _UnitPrice;
    public short Quantity
    {
        get { return _Quantity; }
        set
        {
            if (Equals(value, _Quantity)) return;
            _Quantity = value;
            NotifyPropertyChanged();
        }
    }
    private short _Quantity;

    public float Discount
    {
        get { return _Discount; }
        set
        {
            if (Equals(value, _Discount)) return;
            _Discount = value;
            NotifyPropertyChanged();
        }
    }
    private float _Discount;


    public Order Order
    {
        get { return _Order; }
        set
        {
            if (Equals(value, _Order)) return;
            _Order = value;
            OrderChangeTracker = _Order == null ? null
                : new ChangeTrackingCollection<Order> { _Order };
            NotifyPropertyChanged();
        }
    }
    private Order _Order;
    private ChangeTrackingCollection<Order> OrderChangeTracker { get; set; }
}

}

@bobgodfrey bobgodfrey changed the title Problem dDeleting items from child collections Problem deleting items from child collections Jul 25, 2017
@tonysneed
Copy link
Collaborator

@bobgodfrey Thanks for posting the issue, and I'm glad you like TE! It would be easier if you could create a GitHub repo for this and paste the link here. Then I can clone and run it to see what's happening. Cheers!

@bobgodfrey
Copy link
Author

Wow you were fast to comment but the good news is that I see the error of my ways, sorry user error. I was not using the object returned from the collection tracker in the apply changes. See code changes with the //IMPORTANT comment and the use of the trackedOrder object.

Sorry I have a few questions and don’t know the best forum to ask?

My use case is to have an object graph stored in local or external session that is fully disconnected, basically a serialized object that is a temporary working copy. The object will get multiple updates over multiple post backs and eventually should be committed at the end of a long running process. This is really all server side changes. Is this some that I can/should do with trackable entities?

I am not sure how to handle the change tracker. I would assume that I would need to maintain a reference to the change tracker as well during the post backs? I also noticed that the change tracker is cloning the objects during the GetChanges() and if I can get away with it would rather not have the extra work to clone the objects. If I was to design around deleting objects and moved to a soft delete were the status of the object “deleted” was persisted to the DB that would be ok with me. Given my first example everything worked with the exception of the deletes, that would work for me. I also prototyped the web version with local session and multiple post backs and everything else worked on my simple parent/child test with the exception of deletes. I don’t know if you had this in mind with the design of trackable entities?

The other question is how do you use the server side only objects, the ones that use theses interfaces - ITrackable, IMergeable, (vs the shared TE/EF objects that derive from EntityBase) to track and save changes if all the changes are done in server side in process? I looked in the samples and it was not clear.

I will see if I can set up a repo later today if you still want to see the original example

 //IMPORTANT – use the tracked object here need to be passed to the apply changes

var trackedOrder = changeTracker.GetChanges().FirstOrDefault();

        // Save the order
        using (var db = new NorthwindSlim())
        {
           // db.ApplyChanges(orderFromDB);

//IMPORTANT – use the tracked object in the apply changes
            db.ApplyChanges(trackedOrder);

            OrderDetailUpdateHelper.firstState = db.Entry(OrderDetailUpdateHelper.first).State;

            //second state is Detached and should be Deleted
            OrderDetailUpdateHelper.secondState = db.Entry(OrderDetailUpdateHelper.second).State;

            OrderDetailUpdateHelper.thirdState = db.Entry(OrderDetailUpdateHelper.third).State;
            OrderDetailUpdateHelper.forthState = db.Entry(OrderDetailUpdateHelper.forth).State;

            db.SaveChanges();

        }

@bobgodfrey
Copy link
Author

Here is the repo with the original problem if you still need it.

https://github.com/bobgodfrey/TrackableEntity_DeleteIssue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants