I have looked at several similar questions on deleting items from child collections in EF Core, and most do not have an accepted or answer relevant for EF Core:
https://stackoverflow.com/a/51349007
https://stackoverflow.com/a/49679247
I have a class called "Albums" and a property on it called "Musicians".
When I load Albums from the database with EF, I include the Musicians property so that the collection is already tracked by EF. If I add an item to the Musicians list and save changes on the Album entity, I notice it is added to the junction table for Musicians and Albums, and this works great. For deletes, this does not seem to have an effect.
Example:
var albums = repository.LoadAlbumsWithMusicians();
var musicians = CreateNewMusiciansForAlbumOne();
var test = albums.First().Musicians;
test.Clear();
test.AddRange(musicians);
With this code, if a composite primary key (MusicianId, MusicianType, and AlbumId) for a musician matches one which EF already knows about, even if it is actually a different object, it will do nothing as it knows this relationship already exists. That's what I expect.
If the primary key does not match an existing one then it will be treated as a new object in the collection and a new row is added to the database. That's also what I expect.
However, I can't seem to do anything that will make EF recognize that the items which were previously in the collection [and have been removed] should be deleted. In EF6, I simply used .Clear() and .AddRange() to handle these relationships, and EF handled the inserts and deletes for child relationships. Without explicitly removing these objects from the context as in this suggested answer, https://stackoverflow.com/a/49679247, how can I ensure these deleted child items are removed from the database?
Using the example below lets say our "selectedAppender" contains a record by ID and all junction table data. In order to add items and remove items from that junction table all we need to do is account for them in a separate object (delete and add object).
Once we have a list of items to delete it's as simple as calling the "remove" method.
Let me know if this isn't clear enough. Hopefully it helps.
file_appender selectedAppender = context.file_appender.Find(dto.Id);
int[] ids = dto.Loggers.Where(x => !x.Selected).Select(x => x.Id).ToArray();
var loggers_to_delete = selectedAppender.logger.Where(x => ids.Contains(x.id));
loggers_to_delete.ToList().ForEach(x =>
{
selectedAppender.logger.Remove(x);
});
ids = dto.Loggers.Where(x => x.Selected).Select(x => x.Id).ToArray();
var loggers_to_add = context.logger.Where(x => ids.Contains(x.id));
loggers_to_add.ToList().ForEach(x =>
{
selectedAppender.logger.Add(x);
});
One downside to linq is you have to approach these types of problems iteratively. There is no way I know of to do a bulk delete. If you know of a way and can improve on it please share it.
Related
Is it possible to add an entity in connected scenario in entity framework. I can update a data, but for add and delete, it does not work in connected mode.
md.Configuration.AutoDetectChangesEnabled = true;
var studentList = md.MyStudent.ToList<Student>();
studentList.Add(new Student { Stdname = "nm" });
md.SaveChanges();
the new item ("nm") wont be added!!!
EDITED
Why Does this work , an update operation and Add does not work, what is the difference?
the database will be updated
Student studentToUpdate = studentList.Where(s => s.Stdname == "stdname1").FirstOrDefault<Student>();
studentToUpdate.Stdname = "Edited stdname3";
Your code doesn't work because your ToList<Student>() returns a new list unrelated to your context. This is similar to the following code:
IList<Student> studentList = new List<Student>();
studentList.Add(new Student { Stdname = "nm" });
Your context from the entity framework don't care if you have a list somewhere in your program and add an object to it.
Editing existing objects in your list works because they were loaded from the context, therefore they are "registered" in the context.
If you want to add a new object you have to add it to md.MyStudent so the context sees that a new object should be added.
Try to use the md.MyStudent.Add() method.
A bit of explanation
EF has a change tracking mechanism so that it can automatically persist the changes you make to your entities. However, EF has to know about your object/entity in order to be able to detect the changes.
In your second example EF knows about the student entity because it was created by the framework, you applied the Where() extension method on a DbSet instance which is part of the EF context.
In your first example you also retrieved the entities from a DbSet, however this time you applied the ToList() extension method, which creates a new List<Student> instance containing all the students stored in your database, but EF doesn't have a reference to that list, only it knows about the entities in the list. As a result, if you add/remove items from that list EF won't know about your changes. You could still make changes to the entities in the list and get the modifications saved to the database.
EF 6.1.3.
I have a domain which contains many instances of a "Header/ Item" type pattern, where the a Header can have many Items (1 to many), and also has a "current" or "latest" item.
This is represented as follows:
Header
Guid Id
Guid CurrentItemId
Item CurrentItem
ICollection<Item> AllItems
Item
HeaderId
Id
The PK of the Items is always the HeaderID + ItemID. The reason being that, by far, the most common access pattern for items is to list all items related to a given header, and having HeaderID be the first part of the PK/clustered index means we get that data with clustered index seeks.
Our problem is that when we use the CurrentItem navigation property, it only ever uses the ItemID to do the lookup, which results in not so great query plans.
I assume this is because the conventions for EF us to use the CurrentItemId to look up the CurrentItem. My question is, is there a way for my to tell EF to always perform its joins for CurrentItem by mapping the Header.Id,Header.CurrentItemId -> Item.HeaderId,Item.Id?
I believe this is a slight different scenario than the one described here: composite key as foreign key
In my case, I have a one to one mapping not one top many, and there doesn't seem to be a WithforeignKey method available for that scenario.
We ended up not being able to get EF to generate the SQL the way we wanted - so we wrote a db command interceptor to dynamically find instances of this join and re-write the join to match our designed composite key.
We configure this as the DbContext level like so:
this.ModifyJoin<Item, Header>(
(i) => new Header() { CurrentItemId = i.Id }, //What to find
(i) => new Header() { CurerntItemId = i.Id, Id = i.HeaderId }); //What to replace with
This information is attached to the context instance itself, so when the command interceptor sees the overrides, it uses them to re-write the SQL.
This ends up working well for most scenarios, but there are some - such as when additional filtering is doing on the Item table as part of the LINQ statement, that the aliasing rules used by EF become too complex to follow without writing a full SQL parser.
For our use, this results in the ideal join about 90% of the time, which is good enough for us.
The code to do all this isn't difficult, but it's too big to put here. Add a comment if you want a copy and I'll put it up on GitHub.
In my School EF Model, I have Kids and Tutorials in many-to-many relationship.
Let's assume both Kids and Tutorials have existing items in them, now we just want to change their existing relationships. That is, to add/delete some tutorials from a kid.
var kid; //the request target to modify relationships
//kid.Tutorials has the old existing relationships to be modified by add/del
var tutorialsToAdd; //the request to add relationships
var tutorialsToDel; //the request to del relationships
using (var conn = new SchoolEFModels(efConnectionStr)) {
conn.Kids.Attach(kid);
kid.Tutorials.ForEach(t => conn.Tutorials.Attach(t));
kid.Tutorials.AddRange(tutorialsToAdd); //simple add extension in batch
kid.Tutorials.RemoveRange(tutorialsToDel); //simple del extension in batch
conn.SaveChanges();
}
When I do this, I got an exception saying:
"Cannot insert duplicate key in object 'dbo.Tutorials'. The duplicate key value is (10)."
I can see EF is trying to create new Tutorial items instead of updating the existing relationship for me. Which is what I don't want. You misunderstood me EF!
What is wrong with my code? How do I make it update Many-to-Many relationships?
I figured it out.
Adding/removing it will make EntityState turn to Added/Deleted. Therefore, causing it to reinsert existing Ids as the article mentioned, thank Gert there for the link.
So, if you modify each of the conn.entry(kid/tutorials).State to EntityState.Modified and then call conn.ChangeTracker.DetectChanges(); then conn.SaveChanges(); it will only update the many-to-many table as expected.
UPDATE:
One thing you need to be careful tho. If the in-memory objects list of Kids and Tutorials are linked to each other. e.g. Kids[0].Tutorials[0] == Tutorials[0] && Tutorial[0].Kids[0] == Kids[0] EF will not be able to handle this dead loop for you. You need to break this circular link first.
To do so, my approach is to open a Connection and read the Kid out Includes(Tutorials), and then use the result to update many to many relationship, but not to use the in-memory objects.
I need to update an entity that has two children which also has two children that both are dependent on both parents.
Job(PK: Jobid)
Holes(PK: Holeid, FK: Jobid) / Orders(PK: Orderid, FK: Jobid)
Tools(PK: Toolid, FK: Holeid, FK: Orderid) / ToolHoles(PK: Holeid, Orderid)
Tools also has 7 children that inherit from it.
The job will already exist on save. The job may or may not already contain 1 or more of each child entity.
I need to be able to save all this information in one transaction so that partial info is not saved to the database.
My current attempt has been to build up the Job entity with all the relevant information and call SaveChanges. If I'm adding new entities, keys will have to be generated on save for all but Jobid. Is what I'm trying to accomplish even possible?
Making some assumptions here, let me know if I'm off base. If your scenario looks like the below, then you should be fine.
var myHoles = new Holes();
var myOrders = new Orders();
var myTools = new Tools();
var myToolHoles = new ToolHoles();
myJob.Holes.Add(myHoles); //myJob already exists
myJob.Orders.Add(myOrders);
myHoles.Tools.Add(myTools);
myOrders.Tools.Add(myTools);
myHoles.ToolHoles.Add(myToolHoles);
myOrders.ToolHoles.Add(myToolHoles);
db.SaveChanges();
You say "one transaction" but the reality is that several transactions take place.
myHoles and myOrders will be inserted to the database with their JobId set appropriately.
EF will find out what their IDs are
myTools and myToolHoles will be inserted with the HoleId and OrderId set with the values found in the second step.
So I have two tables, A and B. Each table is set up like so:
A: ItemAId, ItemAProperty1, ItemAProperty2, etc.
B: ItemBId, ItemBProperty1, ItemBProperty2, etc.
I have a record in table A and a record in table B. These records are in no way related to each other.
I have a third table, C set up like so:
C: ItemAId, ItemBId
I want to make a new record in table C from existing records in tables A and B. C is not available using intellisense. Everywhere I've looked, the suggestion is to build an object from A that already exists, and object(s) from B that already exist, add B to A (or vice versa), then do an add... and because the objects already exist in the database, EF will just do an update and link the objects together in the intermediate table. (From Insert/Update Many to Many Entity Framework . How do I do it?) I.E.:
/**** Rough Psuedocode ****/
var a = context.First(a => a.Id == passedInAId);
var b = context.First(b => b.Id == passedInBId);
a.BProperty.Add(b);
context.Add(a);
context.SaveChanges()
However, in this case, the objects have no properties that relates them so this is not working.
I know I can write a stored proc to do this, but is it possible to do with EF?
So, as I discovered from digging around some more...
Even though ItemA and ItemB have no direct relations with each other, it appears that the fact that there is a linking table which contains the primary IDs results in an ICollection of ItemA inside the ItemB object, and vice versa. So to get it to work, really all I needed to do was, in fact, get the two objects I wanted to link, add one to the other's collection of said objects, and do an add. Actual (imitation) code of how it works is:
using ( var ctx = new Model())
{
var item1 = ctx.ItemA.FirstOrDefault(i => i.Id == itemAId);
var item2 = ctx.ItemB.FirstOrDefault(j => j.Id == itemBId);
item1.ItemBs.Add(item2);
ctx.SaveChanges();
}
Of course, First() would work if the existence of the objects was already verified, and the "item1.ItemBs.Add()" has "ItemBs" because of EF's annoying tendency to pluralize EVERYTHING.