Removing object from virtual collection or Moving to a different one in Entity Framework Code First - entity-framework

I have an object with a self referencing parent child relationship:
[Table("Content")]
public class Content
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ContentID { get; set; }
public string Text { get; set; }
public int? ParentID { get; set; }
public virtual Content Parent { get; set; }
private ICollection<Content> _contents { get; set; }
public virtual ICollection<Content> Contents
{
get { return _contents ?? (_contents = new HashSet<Content>()); }
set { _contents = value; }
}
}
I am trying to work with the edit function so that if the parent ID changes, the object is correctly removed from the OLD parent's children and added to the new one (or is correctly set to null)
I have tried a number of combinations in order to make the code correctly change the parent ID but I am just not able to crack the correct thing to do here.
[HttpPost]
public ActionResult Edit(Content content)
{
if (ModelState.IsValid)
{
Content oldContent = context.Contents
.Where<Content>(c => c.ContentID == content.ContentID)
.Single<Content>();
// If the parent has changed.
if (content.ParentID != oldContent.ParentID)
{
// if the old parent is not NULL remove from collection
if (oldContent.ParentID != null) {
Content oldParent = context.Contents
.Where<Content>(c => c.ContentID == oldContent.ParentID)
.Single<Content>();
oldParent.Contents.Remove(content);
context.Entry(oldParent).State = EntityState.Modified;
context.SaveChanges();
}
// if the new parent is not NULL add to the new collection
if (content.ParentID != null) {
Content parent = context.Contents
.Where<Content>(c => c.ContentID == content.ParentID)
.Single<Content>();
parent.Contents.Add(content);
context.Entry(parent).State = EntityState.Modified;
context.SaveChanges();
}
}
context.Entry(oldContent).CurrentValues.SetValues(content);
context.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.PossibleParents = context.Contents;
return View(content);
}
The problem is that the data table has two fields related to ParentID - the first ParentID is correctly changing, the second, Parent_ContentID is not. The second one is used for looping through the .Contents property from the parent.
What am I missing? How can I remove the current object from the "related objects" collection of the parent?

oldParent.Contents.Remove(oldContent);
This worked for me. Thanks, #Slauma!

Related

EF Core Updating Entity Property with JSON Type

I have a 1 to many relationship of Parent and Child to store some data. I want to store this data in PostgreSQL using Npgsql.EntityFrameworkCore.PostgreSQL package. Parent maps to a table, but Child is stored as a json column of Parent table in the database.
public class Parent
{
public int Id { get; set; }
[Column(TypeName = "json")]
public ICollection<Child> Children { get; set; }
}
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
}
When I try to add a Child entity to an existing Parent instance with the code below, SaveChangesAsync doesn't produce an update command on the database.
var child = new Child(){Id = 0, Name = "Name"};
var parent = await DataContext.Parent.SingleOrDefaultAsync(f => f.Id == 1);
parent.Children.Add(child);
await DataContext.SaveChangesAsync();
In order to trigger an update command, I have to set parent entity's State to Modified before calling SaveChangesAsync.
var entry = DataContext.Entry<Parent>(parent);
entry.State = EntityState.Modified;
Is this the expected behavior or am I missing something?
Update:
As #SvyatoslavDanyliv suggested, instead of using ICollection when I use a class EqualityCollection derived from List and override Object.Equals as follows :
public class EqualityCollection<T> : List<T>
{
public override bool Equals(object? obj)
{
if (obj != null && obj.GetType() == typeof(EqualityCollection<T>))
return this.Equals(obj as EqualityCollection<T>);
return false;
}
public bool Equals(EqualityCollection<T> obj)
{
return this.SequenceEqual(obj ?? throw new InvalidOperationException());
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}
Change detection doesn't detect the change in the property and update command is not triggered.
You have to define ValueComparer when defining conversion via HasConversion as described in documentation: Value Comparers

How to write an audit log entry per changed property with Audit.NET EntityFramework.Core

I'm trying to get the Audit:NET EntityFramework.Core extension to write an AuditLog entry per changed property.
For this purpose I've overidden the EntityFrameworkDataProvider.InsertEvent with a custom DataProvider.
The problem is, using DbContextHelper.Core.CreateAuditEvent to create a new EntityFrameworkEvent returns null.
The reason seems to be, at this point in the code execution DbContextHelper.GetModifiedEntries determines all EF Entries have State.Unmodified, even if they are clearly included in the EventEntry changes.
I'm trying to circumvent CreateAuditEvent by manually creating the contents is impossible due to private/internal properties.
Maybe there is an alternative solution to this problem I'm not seeing, i'm open to all suggestions.
Audit entity class
public class AuditLog
{
public int Id { get; set; }
public string Description { get; set; }
public string OldValue { get; set; }
public string NewValue { get; set; }
public string PropertyName { get; set; }
public DateTime AuditDateTime { get; set; }
public Guid? AuditIssuerUserId { get; set; }
public string AuditAction { get; set; }
public string TableName { get; set; }
public int TablePK { get; set; }
}
Startup configuration
Audit.Core.Configuration.Setup()
.UseCustomProvider(new CustomEntityFrameworkDataProvider(x => x
.AuditEntityAction<AuditLog>((ev, ent, auditEntity) =>
{
auditEntity.AuditDateTime = DateTime.Now;
auditEntity.AuditAction = ent.Action;
foreach(var change in ent.Changes)
{
auditEntity.OldValue = change.OriginalValue.ToString();
auditEntity.NewValue = change.NewValue.ToString();
auditEntity.PropertyName = change.ColumnName;
}
}
Custom data provider class
public class CustomEntityFrameworkDataProvider : EntityFrameworkDataProvider
{
public override object InsertEvent(AuditEvent auditEvent)
{
var auditEventEf = auditEvent as AuditEventEntityFramework;
if (auditEventEf == null)
return null;
object result = null;
foreach (var entry in auditEventEf.EntityFrameworkEvent.Entries)
{
if (entry.Changes == null || entry.Changes.Count == 0)
continue;
foreach (var change in entry.Changes)
{
var contextHelper = new DbContextHelper();
var newEfEvent = contextHelper.CreateAuditEvent((IAuditDbContext)auditEventEf.EntityFrameworkEvent.GetDbContext());
if (newEfEvent == null)
continue;
newEfEvent.Entries = new List<EventEntry>() { entry };
entry.Changes = new List<EventEntryChange> { change };
auditEventEf.EntityFrameworkEvent = newEfEvent;
result = base.InsertEvent(auditEvent);
}
}
return result;
}
}
Check my answer here https://github.com/thepirat000/Audit.NET/issues/323#issuecomment-673007204
You don't need to call CreateAuditEvent() you should be able to iterate over the Changes list on the original event and call base.InsertEvent() for each change, like this:
public override object InsertEvent(AuditEvent auditEvent)
{
var auditEventEf = auditEvent as AuditEventEntityFramework;
if (auditEventEf == null)
return null;
object result = null;
foreach (var entry in auditEventEf.EntityFrameworkEvent.Entries)
{
if (entry.Changes == null || entry.Changes.Count == 0)
continue;
// Call base.InsertEvent for each change
var originalChanges = entry.Changes;
foreach (var change in originalChanges)
{
entry.Changes = new List<EventEntryChange>() { change };
result = base.InsertEvent(auditEvent);
}
entry.Changes = originalChanges;
}
return result;
}
Notes:
This could impact performance, since it will trigger an insert to the database for each column change.
If you plan to use async calls to DbContext.SaveChangesAsync, you should also implement the InsertEventAsync method on your CustomDataProvider
The Changes property is only available for Updates, so if you also want to audit Inserts and Deletes, you'll need to add the logic to get the column values from the ColumnValues property on the event

How to deep clone/copy in EF Core

What I would like to do is duplicate/copy my School object and all of its children/associations in EF Core
I have something like the following:
var item = await _db.School
.AsNoTracking()
.Include(x => x.Students)
.Include(x => x.Teachers)
.Include(x => x.StudentClasses)
.ThenInclude(x => x.Class)
.FirstOrDefaultAsync(x => x.Id == schoolId);
I have been reading up on deep cloning and it seems that I should be able to do just add the entity...so pretty much the next line.
await _db.AddAsync(item);
Then EF should be smart enough to add that entity as a NEW entity. However, right off the bat I get a conflict that says "the id {schoolId} already exists" and will not insert. Even if I reset the Id of the new item I am trying to add, I still get conflicts with the Ids of the associations/children of the school iteam.
Is anyone familiar with this and what I might be doing wrong?
I had the same problem too, but in my case EF core was throwing exception "the id already exists".
Following the answer of #Irikos so I have created method which clones my objects.
Here's example
public class Parent
{
public int Id { get; set; }
public string SomeProperty { get; set; }
public virtual List<Child> Templates { get; set; }
public Parent Clone()
{
var output = new Parent() { SomeProperty = SomeProperty };
CloneTemplates(output);
return output;
}
private void CloneTemplates(Parent parentTo, Child oldTemplate = null, Child newTemplate = null)
{
//find old related Child elements
var templates = Templates.Where(c => c.Template == oldTemplate);
foreach (var template in templates)
{
var newEntity = new Child()
{
SomeChildProperty = template.SomeChildProperty,
Template = newTemplate,
Parent = parentTo
};
//find recursivly all related Child elements
CloneTemplates(parentTo, template, newEntity);
parentTo.Templates.Add(newEntity);
}
}
}
public class Child
{
public int Id { get; set; }
public int ParentId { get; set; }
public virtual Parent Parent { get; set; }
public int? TemplateId { get; set; }
public virtual Child Template { get; set; }
public string SomeChildProperty { get; set; }
}
Then I just call DbContext.Parents.Add(newEntity) and DbContext.SaveChanges()
That worked for me. Maybe this will be useful for someone.
I had the same problem, but in my case, ef core was smart enough save them as new entities even with existing id. However, before realising that, I just made a copy constructor for all the items, created a local task variable containing only the desired properties and returned the copy.
Remove certain properties from object upon query EF Core 2.1

Ignore a collection member in EF Core

I have class defined like this:
public class Subject
{
public int Id { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set;
[ForeignKey("LevelId")]
public Level Level { get; set; }
[ForeignKey("ParentId")]
public Subject Parent { get; set; }
public ICollection<Subject> Children { get; set; }
}
And then a method to create:
public int Create(Subject newSubject)
{
var subjectToAdd = _mapper.Map<dataModels.Subject>(newSubject);
_context.Add(subjectToAdd);
SaveChildren(subjectToAdd.Id, newSubject.LevelId, newSubject.Children);
_context.SaveChanges();
return subjectToAdd.Id;
}
My problem is that children are twice saved, once in:
_context.Add(subjectToAdd);
because during mapping, the Children are mapped, and then once again in
SaveChildren();
What I want to achieve is for EF to ignore changes in the Children collection and then I will save the changes manually in SaveChildren().
I tried adding this:
var subjectToAdd = _mapper.Map<dataModels.Subject>(newSubject);
_context.Add(subjectToAdd);
_context.Entry(user).Property(x => x.Children).IsModified = false; // this line
but it did not work. Also tried making it null but it said something about severed error. How do I make EF ignore the Children collection without deleting that property and then I just do my own save logic?
You could just change the entry state of the Children as Unchanged to avoid insert them when SaveChanges has been applied.
You should save Subject before inserting the children manually. newSubject.Id should be generated.
Also, newSubject is being mapped and it is unnecessary. So, I modified the Create method like this;
public int Create(Subject newSubject)
{
// The newSubject entry state has been changed as 'Added'
_context.Subjects.Add(newSubject);
// The children of newSubject entry state has been changed as 'Unchanged' and children will not be inserted
_context.Entry(newSubject.Children).State = EntityState.Unchanged;
//Save the newSubject entity
_context.SaveChanges();
//Save the children
SaveChildren(newSubject.Id, newSubject.LevelId, newSubject.Children);
return newSubject.Id;
}

Cannot get relationship to update for navigation properties in entity framework

I am currently using EF4.3 and Code First. Creation of my objects works (via my views - just using the auto-generated Create), but when I attempt to edit an object, it does not save any changes that, utlimately, tie back to my navigation properties. I have been reading on relationships, but I don't understand how to tell my context that the relationship has changed.
Here is some example code of my implementation.
#* Snippet from my view where I link into my ViewModel. *#
<div class="row">
<div class="editor-label">
#Html.LabelFor(model => model.ManagerID)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.ManagerID, ViewBag.Manager as SelectList, String.Empty)
#Html.ValidationMessageFor(model => model.ManagerID)
</div>
</div>
Here is my Controller implementation (POST of my Edit):
[HttpPost]
public ActionResult Edit(ProjectViewModel projectViewModel)
{
if (ModelState.IsValid)
{
Project project = new Project();
project.ProjectID = projectViewModel.ProjectID;
project.Name = projectViewModel.Name;
project.ProjectManager = repository.GetUser(projectViewModel.ManagerID);
repository.InsertOrUpdateProject(project);
repository.Save();
return RedirectToAction("Index");
}
ViewBag.Manager = new SelectList(repository.GetUsers(), "UserID", "FullName", projectViewModel.ManagerID);
return View(projectViewModel);
}
Within my Project object:
public class Project
{
public int ProjectID { get; set; }
[Required]
public string Name { get; set; }
// Navigation Properties
public virtual User Manager { get; set; }
}
Here is the corresponding method from the repository (where my context resides):
public void InsertOrUpdateProject(Project project)
{
if (program.ProjectID == default(int))
{
context.Projects.Add(project);
}
else
{
context.Entry(project).State = EntityState.Modified;
}
}
Just to be clear, this does work to update my properties, but it does not update my navigation properties (in this case, Manager). Appreciate any help.
Setting the state to Modified only marks scalar properties as modified, not navigation properties. You have several options:
A hack (you won't like it)
//...
else
{
var manager = project.Manager;
project.Manager = null;
context.Entry(project).State = EntityState.Modified;
// the line before did attach the object to the context
// with project.Manager == null
project.Manager = manager;
// this "fakes" a change of the relationship, EF will detect this
// and update the relatonship
}
Reload the project from the database including (eager loading) the current manager. Then set the properties. Change tracking will detect a change of the manager again and write an UPDATE.
Expose a foreign key property for the Manager navigation property in your model:
public class Project
{
public int ProjectID { get; set; }
[Required]
public string Name { get; set; }
public int ManagerID { get; set; }
public virtual User Manager { get; set; }
}
Now ManagerID is a scalar property and setting the state to Modified will include this property. Moreover you don't need to load the Manager user from the database, you can just assign the ID you get from your view:
Project project = new Project();
project.ProjectID = projectViewModel.ProjectID;
project.Name = projectViewModel.Name;
project.ManagerID = projectViewModel.ManagerID;
repository.InsertOrUpdateProject(project);
repository.Save();
There are several options here, I will list 3 of them:
Option 1: Using GraphDiff
*This needs the Configuration.AutoDetectChangesEnabled of your context set to true.
Just install GraphDiff with NuGet
Install-Package RefactorThis.GraphDiff
Then
using (var context = new Context())
{
var customer = new Customer()
{
Id = 12503,
Name = "Jhon Doe",
City = new City() { Id = 8, Name = "abc" }
};
context.UpdateGraph(customer, map => map.AssociatedEntity(p => p.City));
context.Configuration.AutoDetectChangesEnabled = true;
context.SaveChanges();
}
For more details about GraphDiff look here.
Option 2: Find and Edit
Searching your entity with EF to track it to the context. Then edit the properties.
*This needs the Configuration.AutoDetectChangesEnabled of your context set to true.
var customer = new Customer()
{
Id = 12503,
Name = "Jhon Doe",
City = new City() { Id = 8, Name = "abc" }
};
using (var context = new Contexto())
{
var customerFromDatabase = context.Customers
.Include(x => x.City)
.FirstOrDefault(x => x.Id == customer.Id);
var cityFromDataBase = context.Cities.FirstOrDefault(x => x.Id == customer.City.Id);
customerFromDatabase.Name = customer.Name;
customerFromDatabase.City = cityFromDataBase;
context.Configuration.AutoDetectChangesEnabled = true;
context.SaveChanges();
}
Option 3: Using a scalar property
In a matter of performance this is the best way, but it mess your class with database concerns. Because you will need to create a scalar (primitive type) property to map the Id.
*In this way there is no need to set the Configuration.AutoDetectChangesEnabled to true. And also you won't need to do a query to the database to retrieve the entities (as the first two options would - yes GraphDiff does it behind the scenes!).
var customer = new Customer()
{
Id = 12503,
Name = "Jhon Doe",
City_Id = 8,
City = null
};
using (var contexto = new Contexto())
{
contexto.Entry(customer).State = EntityState.Modified;
contexto.SaveChanges();
}
I am not sure exactly what you mean by navigation properties? Do you mean like a foreign key relationship? If so then try the following data annotation:
public class Project
{
public int ProjectID { get; set; }
[Required]
public string Name { get; set; }
[ForeignKey("YourNavigationProperty")]
public virtual UserManager { get; set; }
}
Update your EF Context, and see what happens?
UPDATE
public class Project
{
public int ProjectID { get; set; }
[Required]
public string Name { get; set; }
[ForeignKey("ManagerId")]
public ManagerModel UserManager { get; set; }
}
public class ManagerModel
{
[Key]
public int ManagerId { get; set; }
public String ManagerName { get; set; }
}
See if that works?