Many-to-many relationship in Entity Framework - c#

I have three tables
News
NewsCategory
Category
When I create a new News item, I need to assign categories to that new item.
My code looks like that
var NewsToSave = new News();
NewsToSave.NewsBody = model.NewsBody;
NewsToSave.NewsTitle = model.NewsTitle;
NewsToSave.NewsImagePath = NewsImagePath;
NewsToSave.NewsDate = System.DateTime.Now;
db.News.Add(NewsToSave);
db.SaveChanges();
foreach (var id in SelectedIDs)
{
// How to assign selected categories to NewsToSave object and save the Ids in the middle table
}
How to assign selected categories to NewsToSave object?
Thank you

You could override your context this way:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<NewsToSave>()
.HasMany(c => c.Categories).WithMany(i => i.NewsToSaves)
.Map(t => t.MapLeftKey("NewsToSaveID")
.MapRightKey("CategoryID")
.ToTable("NewsCategory"));
}
And process the added data:
void AddOrUpdateNews(Context context, int categoryId, string newsTitle)
{
var cat = context.Categories.SingleOrDefault(c => c.Id == categoryId);
var nws = cat.News.SingleOrDefault(n => n.Title == newsTitle);
if(nws == null)
{
cat.News.Add(context.News.SingleOrDefault(c => c.Title == newsTitle));
}
}
This worked great on the ASP.NET application I made a few months ago. Hope it can help you out.

You can create a category for each id, attach it (provided that it exists in the db already) and then add it to the Categories list of the news and save:
foreach(var id in SelectedIDs)
{
var category = new Category { CategoryId == id};
db.Categories.Attach(category);
NewsToSave.Add(category);
}
db.News.Add(NewsToSave);
db.SaveChanges();

foreach (var id in SelectedIDs)
{
// You have to load the category from Db
var cat = db.Categories.FirstOrDefault(x=> c.Id == id);
// Added to the NewsToSave
if (cat !=null)
NewsToSave.Categories.Add(cat);
}
// Store the changes
db.SaveChanges();

Related

Adding new records to an Entity Framework many-many with existing records

I've got two classes as follows:
public class Movie
{
...
public virtual ICollection<Actor> Actors { get; set; }
}
public class Actor
{
...
public virtual ICollection<Movie> Movies { get; set; }
}
And Entity Frameworks creates a table in between to record the relationship between them.
Now, my question is how can I add a new record with existing Movies and Records? There are two parts to this question:
With the following method, how can I add the actors to the movie without replacing all the existing relationships for the movie:
public void AddRelationship(int movieId, int[] actorIds)
{
var movie = new Movie { Id = movieId };
context.Movies.Attach(movie);
foreach(var actorId in actorIds)
{
movie.Actors.add(new Actor{ Id = actorId });
}
context.SaveChanges();
}
This creates a new Actor which is not what I want.
With the following method, how can I replace all the actors for a movie with the given list:
public void ReplaceRelationship(int movieId, int[] actorIds)
{
}
A way with the second method is to delete all the existing ones and readd them, but I'm trying to keep the amount of Db trips down.
Also when adding I don't want to add duplicates, will I have to get all the relationships out and compare in my code?
1.
When you're doing this, you're actually creating a new actor.
movie.Actors.add(new Actor{ Id = actorId });
What you should be doing is first attaching the existing one, and then add it.
var actor = new Actor{ Id = actorId };
context.Actors.Attach(actor);
movie.Actors.Add(actor);
Or in full example:
public void AddRelationship(int movieId, int[] actorIds)
{
var movie = _context.Movies.FirstOrDefault(x => x.Id == movieId);
// You might need to do include actors like this:
//_context.Movies.Include(x => x.Actors).FirstOrDefault(x => x.Id = movieId);
if(movie == null)
{
// Now what?
throw new Exception("Invalid movieId");
}
foreach(var actorId in actorIds)
{
var actor = new Actor
{
Id = actorId
};
_context.Actors.Attach(actor);
movie.Actors.Add(actor); // EF will detect if it already exists or not.
}
_context.SaveChanges();
}
2.
public void ReplaceRelationship(int movieId, int[] actorIds)
{
var movie = _context.Movies.FirstOrDefault(x => x.Id = movieId);
// You might need to do include actors like this:
//_context.Movies.Include(x => x.Actors).FirstOrDefault(x => x.Id = movieId);
if(movie == null)
{
// Now what?
throw new Exception("Invalid movieId");
}
// Get a list of the already existing actors, so we know which to remove.
var existingActorIds = movie.Actors.Select(x => x.Id).ToList();
// Add new actors.
foreach (var actorId in actorIds.Where(x => !existingActorIds .Contains(x.Id)))
{
var newActor = _context.Actors.Find(actorId );
// You might be able to use this instead.
// var newActor = new Actor { Id = actorId };
// _context.Actors.Attach(newActor);
movie.Actors.Add(newActor );
}
var idsToRemove =
existingActorIds.Where(x => !actorIds.Contains(x));
// Remove the old ones
foreach (var actorId in idsToRemove)
{
var actorEntity = movie.Actors.FirstOrDefault(x => x.Id== actorId );
// Again, you should be able to use Attach like above.
// I use FirstOrDefault() since I actually need the full entity later.
movie.Actors.Remove(actorEntity);
}
_context.SaveChanges();
}
A way with the second method is to delete all the existing ones and
readd them, but I'm trying to keep the amount of Db trips down.
Yeah, I totally get you. Unfortunately I haven't found a better solution than to actually call Remove() on each one.
Also when adding I don't want to add duplicates, will I have to get
all the relationships out and compare in my code?
You can check if the item exists first. But in my cases EF has managed this for me. My mapping-table has two PK (one for MovieId and one for ActorId, which doesn't allow duplicates.
this.HasMany(t => t.Actors)
.WithMany(t => t.Movies)
.Map(m =>
{
m.ToTable("ActorMovies");
m.MapLeftKey("ActorId");
m.MapRightKey("MovieId");
});

Delete a single record from Entity Framework?

I have a SQL Server table in Entity Framework named employ with a single key column named ID.
How do I delete a single record from the table using Entity Framework?
It's not necessary to query the object first, you can attach it to the context by its id.
Like this:
var employer = new Employ { Id = 1 };
ctx.Employ.Attach(employer);
ctx.Employ.Remove(employer);
ctx.SaveChanges();
Alternatively, you can set the attached entry's state to deleted :
var employer = new Employ { Id = 1 };
ctx.Entry(employer).State = EntityState.Deleted;
ctx.SaveChanges();
You can use SingleOrDefault to get a single object matching your criteria, and then pass that to the Remove method of your EF table.
var itemToRemove = Context.Employ.SingleOrDefault(x => x.id == 1); //returns a single item.
if (itemToRemove != null) {
Context.Employ.Remove(itemToRemove);
Context.SaveChanges();
}
var stud = (from s1 in entities.Students
where s1.ID== student.ID
select s1).SingleOrDefault();
//Delete it from memory
entities.DeleteObject(stud);
//Save to database
entities.SaveChanges();
Employer employer = context.Employers.First(x => x.EmployerId == 1);
context.Customers.DeleteObject(employer);
context.SaveChanges();
I am using entity framework with LINQ. Following code was helpful for me;
1- For multiple records
using (var dbContext = new Chat_ServerEntities())
{
var allRec= dbContext.myEntities;
dbContext.myEntities.RemoveRange(allRec);
dbContext.SaveChanges();
}
2- For Single record
using (var dbContext = new Chat_ServerEntities())
{
var singleRec = dbContext.ChatUserConnections.FirstOrDefault( x => x.ID ==1);// object your want to delete
dbContext.ChatUserConnections.Remove(singleRec);
dbContext.SaveChanges();
}
More generic approuch
public virtual void Delete<T>(int id) where T : BaseEntity, new()
{
T instance = Activator.CreateInstance<T>();
instance.Id = id;
if (dbContext.Entry<T>(entity).State == EntityState.Detached)
{
dbContext.Set<T>().Attach(entity);
}
dbContext.Set<T>().Remove(entity);
}
Just wanted to contribute the three methods I've bounced back and forth with.
Method 1:
var record = ctx.Records.FirstOrDefault();
ctx.Records.Remove(record);
ctx.SaveChanges();
Method 2:
var record = ctx.Records.FirstOfDefault();
ctx.Entry(record).State = EntityState.Deleted;
ctx.SaveChanges();
ctx.Entry(record).State = EntityState.Detached;
One of the reasons why I prefer to go with Method 2 is because in the case of setting EF or EFCore to QueryTrackingBehavior.NoTracking, it's safer to do.
Then there's Method 3:
var record = ctx.Records.FirstOrDefault();
var entry = ctx.Entry(record);
record.DeletedOn = DateTimeOffset.Now;
entry.State = EntityState.Modified;
ctx.SaveChanges();
entry.State = EntityState.Detached;
This utilizes a soft delete approach by setting the record's DeletedOn property, and still being able to keep the record for future use, what ever that may be. Basically, putting it in the Recycle Bin.
Also, in regards to Method 3, instead of setting the entire record to being modified:
entry.State = EntityState.Modified;
You would also simply set only the column DeletedOn as modified:
entry.Property(x => x.DeletedOn).IsModified = true;
With Entity Framework 6, you can use Remove.
Also it 's a good tactic to use using for being sure that your connection is closed.
using (var context = new EmployDbContext())
{
Employ emp = context.Employ.Where(x => x.Id == id).Single<Employ>();
context.Employ.Remove(emp);
context.SaveChanges();
}
[HttpPost]
public JsonResult DeleteCotnact(int id)
{
using (MycasedbEntities dbde = new MycasedbEntities())
{
Contact rowcontact = (from c in dbde.Contact
where c.Id == id
select c).FirstOrDefault();
dbde.Contact.Remove(rowcontact);
dbde.SaveChanges();
return Json(id);
}
}
What do you think of this, simple or not, you could also try this:
var productrow = cnn.Product.Find(id);
cnn.Product.Remove(productrow);
cnn.SaveChanges();
Using EntityFramework.Plus could be an option:
dbContext.Employ.Where(e => e.Id == 1).Delete();
More examples are available here
u can do it simply like this
public ActionResult Delete(int? id)
{
using (var db = new RegistrationEntities())
{
Models.RegisterTable Obj = new Models.RegisterTable();
Registration.DAL.RegisterDbTable personalDetail = db.RegisterDbTable.Find(id);
if (personalDetail == null)
{
return HttpNotFound();
}
else
{
Obj.UserID = personalDetail.UserID;
Obj.FirstName = personalDetail.FName;
Obj.LastName = personalDetail.LName;
Obj.City = personalDetail.City;
}
return View(Obj);
}
}
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int? id)
{
using (var db = new RegistrationEntities())
{
Registration.DAL.RegisterDbTable personalDetail = db.RegisterDbTable.Find(id);
db.RegisterDbTable.Remove(personalDetail);
db.SaveChanges();
return RedirectToAction("where u want it to redirect");
}
}
model
public class RegisterTable
{
public int UserID
{ get; set; }
public string FirstName
{ get; set; }
public string LastName
{ get; set; }
public string Password
{ get; set; }
public string City
{ get; set; }
}
view from which u will call it
<table class="table">
<tr>
<th>
FirstName
</th>
<th>
LastName
</th>
<th>
City
</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td> #item.FirstName </td>
<td> #item.LastName </td>
<td> #item.City</td>
<td>
Edit |
Details |
Delete
</td>
</tr>
}
</table>
i hope this will be easy for u to understand
You can do something like this in your click or celldoubleclick event of your grid(if you used one)
if(dgEmp.CurrentRow.Index != -1)
{
employ.Id = (Int32)dgEmp.CurrentRow.Cells["Id"].Value;
//Some other stuff here
}
Then do something like this in your Delete Button:
using(Context context = new Context())
{
var entry = context.Entry(employ);
if(entry.State == EntityState.Detached)
{
//Attached it since the record is already being tracked
context.Employee.Attach(employ);
}
//Use Remove method to remove it virtually from the memory
context.Employee.Remove(employ);
//Finally, execute SaveChanges method to finalized the delete command
//to the actual table
context.SaveChanges();
//Some stuff here
}
Alternatively, you can use a LINQ Query instead of using LINQ To Entities Query:
var query = (from emp in db.Employee
where emp.Id == employ.Id
select emp).Single();
employ.Id is used as filtering parameter which was already passed from the CellDoubleClick Event of your DataGridView.
Here's a safe way:
using (var transitron = ctx.Database.BeginTransaction())
{
try
{
var employer = new Employ { Id = 1 };
ctx.Entry(employer).State = EntityState.Deleted;
ctx.SaveChanges();
transitron.Commit();
}
catch (Exception ex)
{
transitron.Rollback();
//capture exception like: entity does not exist, Id property does not exist, etc...
}
}
Here you can pile up all the changes you want, so you can do a series of deletion before the SaveChanges and Commit, so they will be applied only if they are all successful.
The best way is to check and then delete
if (ctx.Employ.Any(r=>r.Id == entity.Id))
{
Employ rec = new Employ() { Id = entity.Id };
ctx.Entry(rec).State = EntityState.Deleted;
ctx.SaveChanges();
}
For a generic DAO this worked:
public void Delete(T entity)
{
db.Entry(entity).State = EntityState.Deleted;
db.SaveChanges();
}

Linq To Sql using AttachAll. DuplicateKeyException

I searched for the answer on the website but I did not find one. I have the following problem ...
I'm trying to update multiple records in the database as follows:
public void SaveJItem(List<DataForDespatcher> Jitem)
{
JitemsTable.InsertAllOnSubmit(Jitem.Where(i => i.Id ==0));
JitemsTable.AttachAll(Jitem.Where(i => i.Id != 0));
JitemsTable.Context.Refresh(RefreshMode.KeepCurrentValues, Jitem);
JitemsTable.Context.SubmitChanges();
}
The table is described as follows:
[Table(Name = "tanks")]
public class DataForDespatcher
{
[Column(IsPrimaryKey = true, IsDbGenerated = true,AutoSync = AutoSync.OnInsert)]
public int Id { get; set; }
/*bla bla bla */
}
When I update I get the error:
"DuplicateKeyException" in "JitemsTable.AttachAll(Jitem.Where(i => i.Id != 0));".
How do I correctly update the data?
ADD:
razor update form:
#inherits WebViewPage<IEnumerable<DomainModel.Entities.DataForDespatcher>>
/*bla bla bla*/
data controller save(get):
public ViewResult EditForDispatcher(int group)
{
var list = DataRep.JItems.Where(x => x.group == group).Select(x => x);
return View(list);
}
data controller save(post):
[HttpPost]
public ActionResult EditForDispatcher(List<DataForDespatcher> Jitem, string addNewOperation, string sendParam, string operations)
{
if (ModelState.IsValid)
{
int group = DataRep.JItems.ToList().Max(x => x.group + 1);
if (Jitem.Any(x => x.group != 0))
foreach (var dataForDespatcher in Jitem)
dataForDespatcher.group = Jitem.Where(x=>x.group!=0).Select(x=>x.group).First();
else
foreach (var dataForDespatcher in Jitem)
dataForDespatcher.group = group;
DataRep.SaveJItem(Jitem);
}
return View(Jitem);
}
I mean exception is occur because you have both an updated and new items in List and the first in the list is not inserted item. I think, you must do somethong like this:
JitemsTable.InsertAllOnSubmit(Jitem.Where(i => i.Id ==0));
JitemsTable.AttachAll(Jitem.Where(i => i.Id != 0));
JitemsTable.Context.Refresh(RefreshMode.KeepCurrentValues, Jitem);
JitemsTable.Context.SubmitChanges();

How to update correctly an M:N relationship?

i have a simple relationship m:m between classes Thing and People but my solution when going to update a record need to delete previous record and then create the new
Example:
My tables
People (PeopleId, Name)
Thing (ThingId, Name)
PeopleHasThing (PeopleId, ThingId)
My Model
PeopleModel.cs
// The following method works!
// I've tried but I have my doubts about deleting records
public static void Update(PeopleModel p)
{
using (_context = new myDataContext())
{
var result = (from r in _context.People
where r.PeopleId == p.PeopleId
select r).SingleOrDefault();
if (null == result) return;
result.Name = p.Name;
PeopleHasThing.DeleteAllByPeopleId(result.PeopleId);
EntitySet<PeopleHasThing> set = new EntitySet<PeopleHasThing>();
//HasThing = List<ThingModel>
//ThingModel: { (Int32)ThingId, (bool)IsMarked. (string)Description }
m.HasThing.ForEach(e =>
{
if (e.IsMarked)
{
set.Add(new PeopleHasThing
{
ThingId = e.ThingId,
People = result
});
}
});
result.PeopleHasThing = set;
_context.SubmitChanges();
}
}
ahd the question is How to update correctly an M:N relationship?

Entity Framework 4. Join Tables. Entity Framework perfoming an Insert when no action is required

I have a database setup with the following: -
Person Table
Hobby Table
Game Table
GameInfo Table
Person [1 - M] Hobby [1 - M] Game [M - 1] GameInfo
Game is just a join from Hobby to GameInfo
I am having an issue whereby I would fetch Person which has a Collection<Game> and add to this collection (i.e. I am just updating links, not wanting to insert new GameInfo).
Upon Calling SaveChanges() EntityFramework will insert the links AS WELL AS inserting new GameInfo, which is not my desired result.
I have looked at Entry().State etc but the problem is where I am handling the updates of my Person is outside of the context.
I am basically fetching a Person creating a new Game with Ids that I know exist already and then calling the SaveChanges() and would expect that it would just insert into the Game Table, not the GameInfo table
EDIT 1: Code Sample - sort of
public void Save(Profile profile)
{
using (GDContext context = GetContext())
{
DataProfile dataProfile = context.Profiles.Single(u => u.ProfileId == profile.Id);
ProfileHandler.HandleDataModelChanges(dataProfile, profile);
context.SaveChanges();
}
}
public override void HandleDataModelChanges(DataProfile dataModel, Profile model)
{
dataModel.ProfileId = model.Id;
dataModel.FirstName = model.FirstName;
dataModel.LastName = model.LastName;
dataModel.DateOfBirth = model.DateOfBirth;
dataModel.Email = model.Email;
foreach(var hobby in model.Hobbies)
{
DataHobby dataHobby = dataModel.Hobbies.SingleOrDefault(p => p.HobbyId == hobby.HobbyId);
if (dataHobby == null)
{
dataHobby = new DataHobby();
}
HobbyHandler.HandleDataModelChanges(dataHobby, hobby);
}
}
public override void HandleDataModelChanges(DataHobby dataModel, Hobby model)
{
dataModel.HobbyId = model.Id;
HandleGames(dataModel, model);
HandleCrafts(dataModel, model);
HandleCollections(dataModel, model);
}
private void HandleGames(DataHobby dataModel, Hobby model)
{
IEnumerable<DataGame> gamesToRemove = dataModel.Games.Where(g => !model.Games.Any(ds => ds.Id == g.GameId)).ToArray();
foreach (var game in gamesToRemove)
{
dataModel.Games.Remove(game);
}
foreach (var game in model.Games)
{
if (!dataModel.Games.Any(e => e.GameId == game.Id))
{
DataGame dataGame = new DataGame();
dataGame.GameId = game.Id;
dataGame.GameName = game.Name;
dataModel.Games.Add(dataGame);
}
}
}
EDIT 2 - Context configuration
this.Configuration.LazyLoadingEnabled = false;
this.Configuration.AutoDetectChangesEnabled = true;
public GameInfoConfiguration()
{
HasKey(x => x.GameId);
ToTable("GameData");
}
public PersonConfiguration()
{
HasKey(x => x.PersonId);
ToTable("Person");
}
public HobbyConfiguration()
{
HasKey(x => x.HobbyId);
HasRequired(x => x.Person).WithMany(x => x.Hobbies);
HasMany(x => x.Games).WithMany(g => g.Hobbies).Map(x => x.MapLeftKey("HobbieId").MapRightKey("GameId").ToTable("PersonGame"));
ToTable("HobbyGame");
}
Well I still don't see where you work with GameInfo - your description absolutely doesn't correspond with your code. By looking at your code I guess the problem will be in the snippet like this:
foreach (var game in model.Games)
{
if (!dataModel.Games.Any(e => e.GameId == game.Id))
{
DataGame dataGame = new DataGame();
dataGame.GameId = game.Id;
dataGame.GameName = game.Name;
dataModel.Games.Add(dataGame);
}
}
This will always insert a new Game - you told EF to insert a new Game. If you want to add existing Game you must do:
foreach (var game in model.Games)
{
if (!dataModel.Games.Any(e => e.GameId == game.Id))
{
DataGame dataGame = new DataGame();
dataGame.GameId = game.Id;
dataGame.GameName = game.Name;
context.Games.Attach(dataGame); // Now the context knows that it is not a new entity
dataModel.Games.Add(dataGame);
}
}
I think the mistake I have made here is that I am dealing with DataGame when really what I should be dealing with is a POCO class to represent the "join" between DataGame and Hobby Like a HobbyGame POCO.

Categories