There is a many to many relationship between Artist and ArtistType. I can easily add artist ArtistType like below
foreach (var artistType in this._db.ArtistTypes
.Where(artistType => vm.SelectedIds.Contains(artistType.ArtistTypeID)))
{
artist.ArtistTypes.Add(artistType);
}
_db.ArtistDetails.Add(artist);
_db.SaveChanges();
This goes and updates the many to many association table with correct mapping. But when I try to remove any item from table I do not get any error but it does not remove it from the table?
foreach (var artistType in this._db.ArtistTypes
.Where(at => vm.SelectedIds.Contains(at.ArtistTypeID)))
{
artistDetail.ArtistTypes.Remove(artistType);
}
this._db.Entry(artistDetail).State = EntityState.Modified;
this._db.SaveChanges();
What am I missing?
Standard way is to load the artist including the current related types from the database and then remove the types with the selected Ids from the loaded types collection. Change tracking will recognize which types have been removed and write the correct DELETE statements to the join table:
var artist = this._db.Artists.Include(a => a.ArtistTypes)
.SingleOrDefault(a => a.ArtistID == someArtistID);
if (artist != null)
{
foreach (var artistType in artist.ArtistTypes
.Where(at => vm.SelectedIds.Contains(at.ArtistTypeID)).ToList())
{
artist.ArtistTypes.Remove(artistType);
}
this._db.SaveChanges();
}
For removing only one field, I came up with this solution. It seems odd but in EF, most of the things are odd anyway because we try to tell EF the database ops in terms of OOP.
using (var db = new Context())
{
//Create existing entities without fetch:
var artist = new Artist() { ArtistID = _artistID };
var type = new Type() { TypeID = _typeID };
//Add one entity to other's list
//This is in memory, not connected.
//So we do this because we try to tell EF that we want to remove this item
//Without fetch, we should add it first in order to remove :)
artist.ArtistTypes.Add(type);
//Attach that entity which you add an item to its list:
db.Artists.Attach(artist);
//It's now connected and recognized by EF as database operation
//After attaching, remove that item from list and save db
artist.ArtistTypes.Remove(type);
db.SaveChanges();
}
That's it! With this solution, you are no longer fetching all entries of joined table ArtistTypes.
Related
I have to fetch some data from an external API and insert it into my database using EF Core. The data i get as a response is related. I need to use id's (which are also coming to me from an api), to create some relations on these entities. Below i attach snippet with the method responsible for creating these relations:
private IList<LeagueEntity> CombineLeaguesWithGames(IList<LeagueEntity> leagues,
IList<GameEntity> games)
{
if (leagues != null && games != null)
{
foreach (var league in leagues)
{
league.Games = games.Where(g => g.LeagueId == league.LeagueId).ToList();
foreach (var team in league.Teams)
{
team.HomeGames = games.Where(g => g.HomeTeam.TeamId == team.TeamId).ToList();
team.AwayGames = games.Where(g => g.AwayTeam.TeamId == team.TeamId).ToList();
team.TeamId = 0;
}
league.LeagueId = 0;
}
foreach (var game in games)
{
game.GameId = 0;
game.HomeTeam = null; // to avoid repetitions
game.AwayTeam = null;
}
}
return leagues;
}
As you noticed, i have to set id of every single entity to 0 before inserting it to my database. The reason is, i cannot insert explicit key value to my tables. I don't really like this solution, is there any, more elegant way to ignore id value before insert, and let the database find it while saving changes?? The sql script "SET IDENTITY_INSERT ... ON/OFF" doesn't fit to my problem because only one table can have identity insert turned on at a time, and i need to insert related data.
I have a database and a set of tables that already have data in.
I am using code first. Now I have been asked to add a Category table to the database and with that, to change some of the other tables to reference that table via a foreign key.
I know through previous experience, that if I update the database after changing the models, it will moan because an FK can't have 0.
So I want to use the Seed method to actually update the Foreign Keys as the database is updated.
I want to do something like this:
protected override void Seed(DatabaseContext context)
{
// Create our categories
context.Categories.AddOrUpdate(m => m.Id,
new Category { Id = 1, Name = "Cameras" },
new Category { Id = 2, Name = "Televisions" }
);
// Get all our current feeds
var feeds = context.Feeds.ToList();
context.Feeds.AddOrUpdate(m => m.Id, feeds.Select(m => m.CategoryId = 1).ToArray());
}
Can someone let me know how I might actually do this? Or if there is another way of doing it?
Inside a loop:
foreach(var f in context.Feeds)
{
f.CategoryId=1;
}
context.SaveChanges();
Or using Entity Framework Extended library to do it in one batch:
context.Feeds.Update(f=>new Feed{CategoryId=1});
How do I delete a record in a junction table within Entity Framework 5?
When reverse engineering my DataContext, Entity Framework seems to have recognized my junction table and automatically added Collections to my Models to represent the M:M relationship. This is great when adding items, as I can simply build my entire Entity and everything gets inserted properly. Perfect.
However, I'm stumped on removing a relationship. For example, an Activity can have multiple Contacts associated to it, and this is linked using a junction table (dbo.ActivityContacts) that consists of the columns:
ActivityID
ContactID
Both my Activity and Contact models have been updated by EF with Collections to represent the other. For example, my Activity model looks like this:
public class Activity
{
public int ActivityID { get; set; }
public string Subject { get; set; }
public virtual ICollection<Contacts> Contacts { get; set; }
}
In a non-EF environment, I would simply delete the record from the junction table and move on with my day. However, it seems I cannot access the junction table directly using EF, so I'm a tad confused on how to remove the record (relationship).
How can I properly remove a record from a junction table in Entity Framework?
Agree with #Chris.
Another solution is to do:
context.Entry(activity).State = EntityState.Deleted;
Entity Framework should remove the record for you, if you remove the associated object from either side of the relationship.
Assuming you've obtained this Activity instance from your context and want to remove a specific Contact with a known ID:
unwantedContact = context.Contacts.Find(contactID);
myActivity.Contacts.Remove(unwantedContact);
context.SaveChanges();
Should delete the record in your junction table, unless I'm being daft.
ali golshani did a good job providing a solution. Let me try to expand on it a little more. In my scenario I have two list boxes where you can move items left or right (selected or not selected)
The 'dto' object below is sent from the client. It's checking the selected state for each item in the list. If anyone knows of any way to improve this any more please leave feedback.
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);
});
Lets look at another example....This one is for a list box with embedded check boxes (a little simpler). Honestly this could probably be applied to the solution above to make easier to read code.
protected void saveRelatedConnectors(test_engine testEngine, List<int> connectorTypes)
var stepConnectorsToDelete = testEngine.step_connector.Where(x => (connectorTypes.Count == 0) ||
(connectorTypes.Count != 0 && !connectorTypes.Contains(x.id)));
stepConnectorsToDelete.ToList().ForEach(x =>
{
testEngine.step_connector.Remove(x);
});
var stepConnectorsToAdd = entities.step_connector.Where(x => connectorTypes.Contains(x.id));
stepConnectorsToAdd.ToList().ForEach(x =>
{
testEngine.step_connector.Add(x);
});
entities.SaveChanges();
contact_to_delete = context.Contacts.Find(contactID);
selected_activity = context.Activity.Find(ActivityID);
context.Entry(selected_activity).Collection("Activity").Load();
selected_activity.Contacts.Remove(contact_to_delete);
db.SaveChanges();
I am long time reader - first time poster - and after countless hours of research on this Entity Framework issue, I felt like I needed some help. I'm new to C# and new to programming, so please bear with me.
I have a fairly simple many-to-many relationship in my data model with Web 2.0 style "Tags". Shifts have tags and users have tags.
I am pulling a shift from a cache. When I try to make a copy of a shift, copying over the shift details and tags from the cached copy, I do the following (simplified).
Data.Shift s = new Data.Shift();
/* copy over a bunch of stuff from the cached shift object. I'll spare the boring details */
foreach (var t in shift.Tags) { //shift object is a cached object
Data.Tag dt = new Data.Tag
{
TagID = t.TagID,
Name = t.Name,
OrgID = t.OrgID,
};
s.Tags.Add(dt);
}
Even though I have explicitly set the TagID in the new Data.Tag object, on SaveChanges() a new tag is inserted into the DB, rather than just creating a relationship on the Tag that already exists in the DB. TagID is a PK identity column
When I try the following code:
foreach (var t in shift.Tags){
s.Tags.Add(t)
}
It obviously fails because the shift was cached from a different object context than the current requests' context.
Any thoughts? As far as I can tell:
Clearing the cache is not an option for me because of performance concerns -- I recognize that this whole issue would go away if I did this work within the same object context.
Also, pulling the Data.Tag from the DB in the current context is not an option because of perf concerns.
This seems like a really easy thing I am trying to do...
Edit
Ok -- I've tried updating my code with both solutions but I am running into trouble with both. Let's try the first one:
// ctx is grabbed up here from HttpContext.Current.Items
Data.Shift s = new Data.Shift();
/* copy over a bunch of stuff from the cached shift object. I'll spare the boring details */
foreach (var t in shift.Tags) { //shift object is a cached object
Data.Tag dt = new Data.Tag
{
TagID = t.TagID,
Name = t.Name,
OrgID = t.OrgID,
};
ObjectStateEntry entry;
ctx.ObjectStateManager.TryGetObjectStateEntry(t.EntityKey, out entry);
if (entry == null || entry.State == EntityState.Detached) {
ctx.Tags.Attach(t);
}
s.Tags.Add(dt);
}
This is throwing the following error:
An entity object cannot be referenced by multiple instances of IEntityChangeTracker.
I am guessing that the reason I am getting is this error, is because the shift object and its tags are pulled out of a cache (obviously from different contexts). Thoughts?
It doesn't matter if you set TAgId to existing value or not. EF is stupid - it doesn't do any Upsert / Merge for you. It doesn't check if the same entity exists and if TagId is auto generated in the database it even throw away your value once you call SaveChanges.
What you have to do? You must manually tell EF that Tag is not a new entity.
Try either:
Data.Shift s = new Data.Shift();
context.Shifts.AddObject(s); // Add a new shift
foreach (var t in shift.Tags)
{ //shift object is a cached object
Data.Tag dt = new Data.Tag
{
TagID = t.TagID,
Name = t.Name,
OrgID = t.OrgID,
};
context.Tags.Attach(Tag); // Attach an existing tag
s.Tags.Add(dt);
}
context.SaveChanges();
or
Data.Shift s = new Data.Shift();
foreach (var t in shift.Tags)
{ //shift object is a cached object
Data.Tag dt = new Data.Tag
{
TagID = t.TagID,
Name = t.Name,
OrgID = t.OrgID,
};
s.Tags.Add(dt);
}
context.Shifts.AddObject(s);
// Now shift and all tags are added
// Change the state of each tag to unchanged
foreach (var tag in s.Tags)
{
context.ObjectStateManager.ChangeEntityState(tag, EntityState.Unchanged);
}
context.SaveChanges();
I've a Entity Framework 4.0, with poco object. the edmx model file is generated from the database.
This datacontext is accessed through WCF service, it's only mean that I receive some objects and I need to attach them to the current datacontext(or reload them with the key correspondance).
Everything seems to work fine, except for one case:
I've a N-N relationship between two table, so I've an association table, without any field other than ID of two tables:
LINQ transform this into the following schema, this seems to be right.
When I retrieve data, there is no problem, data I've inserted myself in the Right_group are correctly transformed into "new object in my collection of Rights/Groups".
But if I try to modify something and save, it doesn't work
public void SaveRights(Group group, List<Rights> rights){
//here, group and rights are objects attached to the database
group.Rights.Clear();
group.Rights.AddRange(rights);
_dataContext.SaveChanges();
}
So my question is: How to save this "relationship" of two objects ?
Thank you!
If you want to avoid loading the objects from the database first you can do it like this(Code taken from one of my aplications so you will have to adapt it):
public void AddAndRemovePersons(int id, int[] toAdd, int[] toDelete)
{
var mailList = new MailList { ID = id, ContactInformations = new List<ContactInformation>() };
this.db.MailLists.Attach(mailList);
foreach (var item in toAdd)
{
var ci = new ContactInformation { ID = item };
this.db.ContactInformations.Attach(ci);
this.db.ObjectStateManager.ChangeRelationshipState(mailList, ci, ml => ml.ContactInformations, System.Data.EntityState.Added);
}
foreach (var item in toDelete)
{
var ci = new ContactInformation { ID = item };
this.db.ContactInformations.Attach(ci);
this.db.ObjectStateManager.ChangeRelationshipState(mailList, ci, ml => ml.ContactInformations, System.Data.EntityState.Deleted);
}
}
I found deleting the relationship as hard as creating it so I left that code in there. One thing about this solution is that both the maillist and the contacts exist prior to this function being run. I attach them to make the state manager track them.
If you are adding new objects that you also want to save you would use the
this.db.MailLists.AddObject(you new item here)
I hope that helps!
Just a thought... how are the keys setup in the Right_Group table? If you use both IDRight and IDGroup together as primary key - this problem might occur. One suggetion is to add a new column (ID) into the Right_Group table, and having this ID as the primary key. Then use foreign keys on the other columns (IDRight, IDGroup) respectivly.