Why, in Entity Framework 6, when copying a domain model to another domain model, does the original object get updated when changing the copy's values? - asp.net-mvc-4

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;
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?
And what is the work-around for this?
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));
}

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;

Related

Unable to save off DB changes using Entity Framework

I am pretty new to the EF world and am trying to understand why I am unable to save off changes to my phone record. I realize that I need to modify my entity but it doesn't seem to make a difference. I have tried using context.xxxx but I am getting an error that context is not defined. What is the using for context? If I change the phone type for the same user I want to update the record with the new info.
Phone definition:
namespace NewSMS.DATA.EF
{
using System;
using System.Collections.Generic;
public partial class Phone
{
public int Phone_ID { get; set; }
public string Phone_Number { get; set; }
public int User_PK1 { get; set; }
public int LU_Phone_Type_PK1 { get; set; }
public Nullable<bool> Available { get; set; }
public virtual LU_Phone_Type LU_Phone_Type { get; set; }
public virtual User User { get; set; }
}
}
Code:
if (phnMems != null && phnMems.Count > 0)
{
var smsPhn = new Phone { };
foreach (var item in phnMems)
{
smsPhn.Phone_Number = item.Phone_Number;
smsPhn.LU_Phone_Type_PK1 = item.Phone_Type_ID;
smsPhn.User_PK1 = obj.User_ID;
Phone phoneInfo = db.Phone.FirstOrDefault(n => n.User_PK1 == obj.User_ID);
if (phoneInfo == null)
{
db.Phone.Add(smsPhn);
db.SaveChanges();
}
else
{
smsPhn.Phone_ID = phoneInfo.Phone_ID; //keep userID
db.Entry(phoneInfo).State = EntityState.Modified;
phoneInfo = smsPhn;
db.SaveChanges();
}
}
} //End IF
smsPhn.Phone_ID = phoneInfo.Phone_ID; //keep userID
db.Entry(phoneInfo).State = EntityState.Modified;
In above code, you are assigning phone_ID in smsPhn object but updating phoneInfo object.
Just a Suggestion:
Move this declaration
var smsPhn = new Phone { };
inside foreach loop and call savechanges outside the loop.

RavenDb changes to Property (nullable) on Entity that is a class doesn't save

I have a class like this:
public class Content {
public string Id {get; set;} = "content/"
public ContentJob? Job {get; set;}
}
public class ContentJob {
public string JobId {get; set;} = string.Empty;
}
I can create the content and if the job is there it will persist. with Store/SaveChanges.
But what I can't do is update (or more accurately set) ContentJob on Content and have it detect that there was a change. (HasChange(instance) == false) and SaveChanges doesn't update the database.
Why? And how do I make it detect the change?
(incidentally I tried using C# 9 records in hopes that because it automatically does deep object equality that this would solve it and no, it doesn't)
I created an unit-test based on your question, and it works as expected.
[Fact]
public void ShouldWork()
{
using (var store = GetDocumentStore())
{
string id = string.Empty;
using (var session = store.OpenSession())
{
var c = new Content();
session.Store(c);
session.SaveChanges();
id = session.Advanced.GetDocumentId(c);
var entity = session.Load<Content>(id);
entity.Job = new ContentJob()
{
JobId = "123"
};
Assert.True(session.Advanced.HasChanged(entity));
session.SaveChanges();
}
Assert.False(string.IsNullOrEmpty(id));
using (var session = store.OpenSession())
{
var entity = session.Load<Content>(id);
Assert.NotNull(entity.Job);
Assert.Equal("123", entity.Job.JobId);
}
}
}
public class Content
{
public string Id { get; set; } = "content/";
public ContentJob? Job { get; set; }
}
public class ContentJob
{
public string JobId { get; set; } = string.Empty;
}

How to override the ef core to write data automatically or log to file?

I am using asp.net core + ef core. I can add delete get or update data to the database.
But can it be true that when I do these operations it can auto log to file or database the userinfo and datetime?
If I am updating data the data will auto update the LastModifiedDateTime and ModifyUserId property.
or log it to file?
for example:
If I update the database
await _context.SaveChangesAsync(cancellationToken);
I can do this:
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
{
foreach (var entry in ChangeTracker.Entries<AuditableEntity>())
{
switch (entry.State)
{
case EntityState.Added:
entry.Entity.CreatedBy = _currentUserService.UserId;
entry.Entity.Created = _dateTime.Now;
break;
case EntityState.Modified:
entry.Entity.LastModifiedBy = _currentUserService.UserId;
entry.Entity.LastModified = _dateTime.Now;
break;
}
}
return base.SaveChangesAsync(cancellationToken);
}
no need I specify the property but override it to log it automatically?
how to set the property of each entity?
Then I can know when and who do the update?
Create new base model class. Every models must have at least one or more columns that are same such as ID, CreatedBy, etc. Then link base model with all your models.
BaseModel.cs
public class BaseModel
{
public int ID { get; set; }
public DateTime? Created { get; set; }
public string CreatedBy { get; set; }
public DateTime? LastModified { get; set; }
public string LastModifiedBy { get; set; }
}
User.cs
public class User : BaseModel
{
public string Name { get; set; }
public int Age {get; set; }
}
YourDbContext.cs
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
{
var entities = ChangeTracker.Entries().Where(x => x.Entity is BaseModel && (x.State == EntityState.Added || x.State == EntityState.Modified));
DateTime dt = DateTime.Now;
foreach (var entity in entities)
{
if (entity.State == EntityState.Added)
{
((BaseModel)entity.Entity).CreatedBy = _currentUserService.UserId;
((BaseModel)entity.Entity).Created = dt;
}
else if (entity.State == EntityState.Modified)
{
((BaseModel)entity.Entity).ModifiedBy = _currentUserService.UserId;
((BaseModel)entity.Entity).LastModified = dt;
}
}
return base.SaveChangesAsync(cancellationToken);
}

MVC An entity object cannot be referenced by multiple instances of IEntityChangeTracker

I've got a model that represents a joint table (with payload) in my database:
public class UserHasCar
{
// Foreign keys
[Key, Column(Order = 0)]
public string ApplicationUserId { get; set; }
[Key, Column(Order = 1)]
public int CarId { get; set; }
// Navigation properties
[Required]
public virtual ApplicationUser ApplicationUser { get; set; }
[Required]
public virtual Car Car{ get; set; }
// Additional fields
public int YearsRidden { get; set; }
}
public class Car
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<UserHasCar> UserHasCars { get; set; }
}
public class ApplicationUser : IdentityUser
{
public int BirthYear{ get; set; }
public virtual ICollection<UserHasCar> UserHasCars { get; set; }
}
I have a form that includes multiple select boxes, and upon submitting I want to clear out all records related to that user who submitted the form in the UserHasCar table and replace them with the new updated information. I'm getting a An entity object cannot be referenced by multiple instances of IEntityChangeTracker. because I am doing something wrong, but I don't see where I am using more than one context. This code happens in my controller:
public ApplicationUser GetCurrentUser()
{
return UserManager.FindById(User.Identity.GetUserId());
}
public string GetUserId()
{
string id = User.Identity.GetUserId();
var user = UserManager.FindById(id);
return user.Id;
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ManageCars(FormCollection form)
{
string id = GetUserId();
// Remove cars records with my id from database
var queryCars = (from m in db.UserHasCars where m.ApplicationUserId == id select m).ToList();
foreach (var record in queryCars )
{
// Could the problem be here?
db.UserHasCars.Remove(record)
}
// Add user-submitted cars to the database
string carval = form["Cars[0]"];
Car car = (from m in db.Cars where m.Name == carval select m).First();
int carid = car.ID;
// I get the abovementioned title error here
db.UserHasCars.Add(
new UserHasCar()
{
ApplicationUser = GetCurrentUser(),
ApplicationUserId = id,
Car = car,
CarId = carid,
YearsRidden = 0
}
);
db.SaveChanges();
}
I've seen many SO posts, but can't seem the problem as why my code doesn't want to save the new database entries.
EDIT
The solution was to remove the call to get the user and replace it with a query. Why? I was making database conflict errors by having both types of calls (database and DataManager calls in the same controller action). I ended up using a modified GetUser() function instead of GetCurrentUser()
Code:
public ApplicationUser GetUser()
{
// As opposed to:
// UserManager.FindById(User.Identity.GetUserId())
// We make a database call to grab our user instead
// So we don't get database context conflicts by using UserManager
string id = GetUserId();
return db.Users.Where(m => m.Id == id).First();
}
public string GetUserId()
{
return User.Identity.GetUserId();
}
// snip
// in ManageCars(FormCollection form)
ApplicationUser user = GetUser();
// snip
var newRow = db.UserHasCars.Create();
newRow.ApplicationUser = user;
// snip
db.UserHasCars.Add(newRow);
Try removing this line:
ApplicationUser = GetCurrentUser(),
from your object instantiation when adding.
Entity populates this object automatically once you set the foreign key ApplicationUserId. If UserManager.FindById(User.Identity.GetUserId()) uses a different db context that's where your exception is coming from.
Also to save yourself further trouble down the line, you should always call db.SaveChanges() in between the two operations. If you're worried about the atomicity of the db operation, just wrap the whole thing in a Transaction.
And when adding new rows to a table, I usually prefer to use something like:
var newRow = db.SomeTable.Create();
newRow.SomeColumn1 = "something";
newRow.SomeColumn2 = 5;
db.SomeTable.Add(newRow);
db.SaveChanges();
In order to delete entries from UserHasCars you need to change their EntityState to Deleted.
Example:
foreach (var record in queryCars )
{
db.ObjectStateManager.ChangeObjectState(record, EntityState.Deleted);
}
Hope this will fix your issue.

MVC4 DTO's and Many to Many Relationships with extension methods for WebAPI

I need some assistance in learning how DTO's and Extension methods work with Many to Many relationships in EF5 Code First MVC4.
I have the following DTO's
using System;
using System.Collections.Generic;
namespace Mirtol.Web.Models
{
public class TaskDetail
{
public DateTime DueDate {get;set;}
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Status { get; set; }
public string Phase { get; set; }
public string Project { get; set; }
public DateTime StartDate { get; set; }
public string overdue { get; set; }
public List<GroupDetail> Groups { get; set; }
}
}
using System;
namespace Mirtol.Web.Models
{
public class GroupDetail
{
public string GroupName {get;set;}
public string GroupDescription { get; set; }
}
}
On top of these I have The following extensions;
using Mirtol.Web.Models;
using System.Collections;
using System.Collections.Generic;
namespace Mirtol.Web.Extensions
{
public static class TaskDetailExtensions
{
public static TaskDetail ToTaskDetail(this Task task)
{
var success = "success";
var warning = "warning";
var error = "error";
return new TaskDetail
{
Id = task.Id,
Name = task.Name,
Description = task.Description,
Status = task.Status,
Phase = task.Phase != null ? task.Phase.Name : string.Empty,
Project = task.Project != null ? task.Project.ShortName : string.Empty,
StartDate = task.StartDateTime,
DueDate = task.DueDate,
overdue = task.DueDate > System.DateTime.Now ? success : task.DueDate < System.DateTime.Now.AddDays(-7) ? error : warning,
};
}
}
}
using Mirtol.Entities.Mir;
using Mirtol.Web.Models;
namespace Mirtol.Web.Extensions
{
public static class GroupDetailExtensions
{
public static GroupDetail ToGroupDetail(this Group group)
{
return new GroupDetail
{
GroupName = group.Name != null ? group.Name: string.Empty,
GroupDescription = group.Description !=null ? group.Description: string.Empty,
};
}
}
}
So my tasks controller has an action;
public IEnumerable<TaskDetail> GetUserProjectTasks(int id, string ustr, int uid)
{
var projectUTasks = taskRepository.GetUserProjectTasks(id, ustr, mirtolSecurity.CurrentUserId).Distinct();
return projectUTasks.OrderByDescending(x => x.DueDate).Select(x => x.ToTaskDetail());
}
which returns toTaskDetail. and includes "Groups"
What I am struggling with is in TaskDetailExtension and TaskDetaail, how do I reflect the fact that a task can belong to many groups and groups can have many tasks? CF takes care of this on my entity classes and the relationships seem to be set up fine there.
My thought was something like
Groups = task.Groups(x => x.Groups.ToGroupDetail()),
in the TaskDetailExtension ?
Any help appreciated.
John
I am going to assume that your Task has a IEnumerable called Groups.
You need two extension methods in these kind of situations. One that will convert your child model and another that will convert the list of your child models.
public static GroupDetail ToGroupDetail(this Group group)
{
return new GroupDetail { .. your mappings };
}
public static IEnumerable<GroupDetail> ToGroupDetailList(this IEnumerable<Group> groups)
{
return groups.Select(g => g.ToGroupDetail());
}
Then include following line in your ToTaskDetail after other properties.
Groups = task.Groups.ToGroupDetailList()
If the task.Groups is a IQueriable you need to change the ToGroupDetailList extension method to take a IQueriable instead of IEnumerable. Hope this helps