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.
Related
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.
I have read over several other questions about this, but I haven't been able to get it to work for me.
I'm using EF Code First, I have two entities Room and Equipment with a many-to-many relationship. It's correct in the DB with an intermediate table that contains a foreign key to both the Room and Equipment tables. However, I haven't been able to get an insert to work.
var room = roomService.FindById(roomId);
var equipment = service.FindById(equipmentId);
//do I need to do both of these?
room.Equipment.Add(equipment);
equipment.Rooms.Add(room);
db.SaveChanges();
This doesn't work at all. I have also trying using Attach to attach both the relevant entities to the context based on advice in other questions but it just seems to stop the code from running. Also, I tried manually change the EntityState to Modified and that didn't help either.
Any ideas?
I use code first of Entity framework. There are two classes "Question" and "User". I defined a relationship as below:
this.HasRequired(v => v.Creator).WithMany(v => v.Questiones)
.HasForeignKey(v => v.CreatorId).WillCascadeOnDelete(false);
After gernerating the database I found that it always create foreign key between Id of User and CreatorId of Question. Because of lower performance of FK(and other reason),I want to define navigation property relationship without setting foreign key in database? Delete FK after EF created it?
If cannot do this using fluent api, could you tell me why EF designed in this way please?
About the lower performance of FK. I have a User table with 5 Million records in it. when I insert a Question into db, since the db check the question.CreatorId validation from User table, it always slower than without FK.
And there are many other reasons that I need to remove FK.
I think I am somewhat obsession because I think that deleting FK after created it is strangely and ugly. What i want is implementing this by using something like WithoutForeignKey in fluent api:
this.HasRequired(v => v.Creator).WithMany(v => v.Questiones)
.WithoutForeignKey(v => v.CreatorId).WillCascadeOnDelete(false);
Without questioning why are you trying to do this strange thing and going just to the answer: you could delete fk constraint after generated, or you could use migrations and remove FK generation from the migration code.
SQL code generated when traversing nav properties will work even if fk constraint doesn't exist, except for cascade deleting
If you want a relationship between two tables, you need to define a foreign key. No way around it. Even if you use Map() in fluent api, you can only hide the foreign key in your model, in the background EF will still use it and it will exist in the database.
Also I don't get what you mean by "performance" of foreign key? One extra (likely small) column won't make a difference. If you mean the navigation properties for the performance part, you can do 3 things:
Don't include them in your model
Make them non-virtual to disable lazy loading
Disable lazy loading all together with ctx.Configuration.LazyLoadingEnabled = false;
If you don't want to tell db about relation and treat both entities as not related (I wonder why), then just ignore these navigation properties and FK field. Note that you will be responsible for managing related entities: saving and loading them from db, updating ids etc
this.Ignore(q => q.Creator);
this.Ignore(q => q.CreatorId);
And you also need to ignore other side of relation, otherwise EF will generate FK column with default name Creator_CreatorId. So in Creator entity configuration:
this.Ignore(c => c.Questiones);
I have a model similar to this:
Context: The idea is that it's a database of samples. One sample has details, and several samples can be collated together into a CollatedSample, and the details can be collated together as well in a CollatedDetail. So, one CollatedSample has many CollatedDetails, and starts from many Samples, each of which has many Details. A CollatedDetail has many Details too. It's a "nice" square.
My approach to adding records is thus:
var sample = new Sample();
var detail = new Detail();
sample.Details.Add(detail);
// suppose I add a bit more meat to these entities...
var collatedSample = new CollatedSample();
var collatedDetail = new CollatedDetail();
collatedSample.Samples.Add(sample);
collatedSample.CollatedDetails.Add(collatedDetail);
collatedDetail.Details.Add(detail);
context.CollatedSamples.AddObject(collatedSample);
context.SaveChanges();
So I've added all elements to eachother, and added Detail to both Sample and CollatedDetail. On SaveChanges, I get an Update Exception with the jolly message:
Unable to determine the principal end of the 'SamplingModel.FK_Detail_CollatedDetailId' relationship. Multiple added entities may have the same primary key.
What I think might really be happening is there is an attempt to record the Detail entity before the CollatedDetail is recorded. That Detail table, with its two relationships, is the one causing the trouble (not adding them to either Sample nor CollatedDetail confirms it). Perhaps there is a way to specify the order of insertion? I also tried the reverse, to set the parent instead of using .Add() on children collections, with the same result. Otherwise, how do I make this sort of 2-pronged insertion in one shot?
EDIT: tl;dr:
I found a workaround: I removed the Foreign Key between Detail and CollatedDetail like #JaderDiag suggested, and the reference field from the CollatedDetail table. Entity Framework creates partial classes, so it was easy to create other partial classes for both entities and manually join them. This will be much much slower, I suspect, but it provides the same fluidity in later exploitation as the entities would have provided with the foreign key.
This is a workaround, and definitely not the solution I was looking for. Would vote this down if I could.
Such a task: we have 2 tables in our L2S Entity classes. It needs to manage with current fields of current tables by numbering em somehow.
Exact question is How can I point to the exact field of exact table without using entity relation names? Such as TmpLinqTable[2] instead of TmpLinqTable.TableField.
Moreover if it can be managed by ids of the entity, not the table.
So my understanding of what you are trying to do is to log changes that happen to your entites. Is that correct? You might want to look into the GetModifedMembers method on the Table class. Here's an interesting link...
http://geekswithblogs.net/steveclements/archive/2008/04/15/linq-to-sql-property-changed--changing-logging.aspx