2

Below are the necessary models for this example.

public class OrderDetailPackageVM
{
    public OrderDetail OrderDetail { get; set; }
    public Package Package { get; set; }
}

public class Package
{
    public Package()
    {
        this.PackageProducts = new List<PackageProduct>();
    }

    public int PackageId { get; set; }

    public int WebsiteId { get; set; }

    public virtual List<PackageProduct> PackageProducts { get; set; }

}

public class PackageProduct
{
    public int PackageProductId { get; set; }

    public int PackageId { get; set; }

    public virtual Package Package { get; set; }

    public int ProductId { get; set; }

    public virtual Product Product { get; set; }

    public int ProductCategoryId { get; set; } // not a FK but data only

    public virtual ProductCategory ProductCategory { get; set; }
}

In the following code snippet, you should see the problem illustrated.

List<OrderDetailPackageVM> pkgs = (from odx in db.OrderDetails
                                               from pax in db.Packages
                                               where odx.OrderId == orderId
                                               && pax.PackageId == odx.PackageId
                                               && odx.PricelistProduct.Product.isStandalone == true
                                               && pax.WebsiteId == websiteId
                                               select new OrderDetailPackageVM
                                               {
                                                   Package = pax,
                                                   OrderDetail = odx
                                               }).AsNoTracking().ToList();


List<OrderDetailPackageVM> packages = new List<OrderDetailPackageVM>();
packages.AddRange(pkgs);

//also tried packages = pkgs;
//also tried packages.injectFrom(pkgs) //from omu valueInjector - similar to automapper

At this point in my watch we see: pkgs.Package.PackageProducts.Count = 6; packages.Package.PackageProducts.Count = 6;

foreach (OrderDetailPackageVM pac in packages)
{
    pac.Package.PackageProducts.RemoveAll();
}

At this point in my watch we see:

pkgs.Package.PackageProducts.Count = 0;
packages.Package.PackageProducts.Count = 0;

When I was expecting to see:

pkgs.Package.PackageProducts.Count = 6;
packages.Package.PackageProducts.Count = 0;
  1. So why is the original object changing when the changes are applied to the copy. I do not remember this behavior in earlier versions of EF?

  2. And what is the work-around for this?

  3. I thought doing a select with NoTracking was supposed to 'Free' the data in the model from EF change tracking?

Thanks so much for helping me understand this behavior.

THE FOLLOWING IS THE METHOD I USED TO SOLVE THIS ISSUE BASED ON FEEDBACK BELOW:

public static T DeepClone<T>(this T source)
    {
        // Don't serialize a null object, simply return the default for that object
        if (Object.ReferenceEquals(source, null))
        {
            return default(T);
        }

        return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source));
    }
Julian Dormon
  • 1,767
  • 4
  • 32
  • 58

1 Answers1

1

You do not create new objects. You place the existing objects in a new list. You would need to create completely new objects and copy the values manually. This is also known as a deep copy or doing a clone ( see ICloneable ).

If omu valueInjector assigns property for property it assigns the list of object a to the list of object b. As it is a reference type, it's actually the same. If you want to have new objects you have to make a deep copy. See Deep cloning objects for more info.

The behavior has actually nothing with tracking the changes from an EF view. You work with reference types.

A small sample program:

using System;
using System.Collections.Generic;

namespace _28637405 {

  class Outer {
    public string MyProperty { get; set; }
  }

  class Program {
    static void Main( string[] args ) {
      var listOne = new List<Outer>();
      for ( int i = 0; i < 10; i++ ) {
        listOne.Add( new Outer { MyProperty = "obj #" + (i + 1) } );
      }
      // first line
      Console.WriteLine( listOne[0].MyProperty );
      var listTwo = new List<Outer>();
      listTwo.AddRange( listOne );
      // second line
      Console.WriteLine( listTwo[0].MyProperty );
      listTwo[0].MyProperty = "Changed";
      // third and fourth line
      Console.WriteLine( listOne[0].MyProperty );
      Console.WriteLine( listTwo[0].MyProperty );
      var listThree = new List<Outer>();
      foreach ( var obj in listOne )
        listThree.Add( new Outer { MyProperty = obj.MyProperty } );
      listThree[0].MyProperty += " again";
      // lines 5,6,7
      Console.WriteLine( listOne[0].MyProperty );
      Console.WriteLine( listTwo[0].MyProperty );
      Console.WriteLine( listThree[0].MyProperty );
    }
  }
}

The ouutput it produces:

obj #1
obj #1
Changed
Changed
Changed
Changed
Changed again

The class Outer would look like this if it would implement ICloneable:

class Outer : ICloneable {
  public string MyProperty { get; set; }

  public object Clone() {
    return new Outer { MyProperty = this.MyProperty };
  }
}

Usage would be (including a cast to Outer ):

var newObject = existingObject.Clone() as Outer;
Community
  • 1
  • 1
Sascha
  • 10,231
  • 4
  • 41
  • 65
  • Thank you for the response. It definitely made sense and put me on the right course. Though I did not use your solution. I will award the points to you. – Julian Dormon Feb 23 '15 at 13:20
  • I edited the post to reflect the method I used. I found the method here: http://stackoverflow.com/questions/78536/deep-cloning-objects – Julian Dormon Feb 23 '15 at 13:21