Many to Many Relationship on MVC5 - c#

So I'm in the middle of a project where I need to have a Many-to-Many relationship between Teams and Members.
One team can have many users (from aspnetusers) and a user can have 0 or more teams.
But at the moment, One team can have many users, but one user can only have 1 team, whenever I try to add a user on a new team, that user is removed from team he's already in.
I've reached this point thanks to http://cpratt.co/associating-related-items-in-a-collection-via-a-listbox/
My Team Model:
public class EquipaModel
{
[Key]
public int EquipaID { get; set; }
[Required]
[Display (Name="Nome")]
public string EquipaNome { get; set; }
[Required]
[Display (Name="Descrição")]
public string EquipaDescricao { get; set; }
[Display(Name = "Team Manager")]
public string TeamManagerId { get; set; }
public virtual ApplicationUser TeamManager { get; set; }
public virtual ICollection<ApplicationUser> Membros { get; set; }
}
My extended user model
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity>
GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
var userIdentity = await manager
.CreateIdentityAsync(this,
DefaultAuthenticationTypes.ApplicationCookie);
return userIdentity;
}
public virtual Utilizador Utilizador { get; set; }
}
[Table("Utilizadores")]
public class Utilizador
{
public int Id { get; set; }
public string PrimeiroNome { get; set; }
public string Apelido { get; set; }
public int Pontuacao { get; set; }
public int PaisID { get; set; }
public virtual PaisModel Pais { get; set; }
//public IEnumerable<LinguasProgModel> Linguagens { get; set; }
}
My Team ViewModel
public class EquipasViewModel
{
[Required]
public string EquipaNome { get; set; }
public string EquipaDescricao { get; set; }
public string TeamManagerId { get; set; }
public virtual ApplicationUser TeamManager { get; set; }
[Required]
public List<string> MembrosID { get; set; }
public IEnumerable<SelectListItem> MembrosEscolhidos { get; set; }
}
My Create on EquipaController (TeamController)
public ActionResult Create()
{
ViewBag.TeamManagerId = new SelectList(db.Users, "Id", "Email");
var model = new EquipasViewModel();
PopulateMembrosEscolhidos(model);
return View(model);
}
[HttpPost]
public ActionResult Create(EquipasViewModel model)
{
if (ModelState.IsValid)
{
var equipa = new EquipaModel
{
EquipaNome = model.EquipaNome,
EquipaDescricao = model.EquipaDescricao,
TeamManagerId = model.TeamManagerId,
Membros = db.Users.Where(m => model.MembrosID.Contains(m.Id)).ToList()
};
db.Equipas.Add(equipa);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.TeamManagerId = new SelectList(db.Users, "Id", "Email", model.TeamManagerId);
PopulateMembrosEscolhidos(model);
return View(model);
}
and finally the Edit on my team controller
public ActionResult Edit(string id)
{
ViewBag.TeamManagerId = new SelectList(db.Users, "Id", "Email");
var equipa = db.Equipas.FirstOrDefault(e => e.EquipaNome == id);
if (equipa == null)
{
return new HttpNotFoundResult();
}
var model = new EquipasViewModel
{
EquipaNome = equipa.EquipaNome,
EquipaDescricao = equipa.EquipaDescricao,
MembrosID = equipa.Membros.Select(m => m.Id).ToList()
};
PopulateMembrosEscolhidos(model);
return View(model);
}
[HttpPost]
public ActionResult Edit(string id, EquipasViewModel model)
{
var equipa = db.Equipas.FirstOrDefault(e => e.EquipaNome == id);
if (equipa == null)
{
return new HttpNotFoundResult();
}
if (ModelState.IsValid)
{
equipa.EquipaNome = model.EquipaNome;
equipa.EquipaDescricao = model.EquipaDescricao;
equipa.Membros.Where(m => !model.MembrosID.Contains(m.Id))
.ToList()
.ForEach(m => equipa.Membros.Remove(m));
var MembrosNaEquipa = equipa.Membros.Select(m => m.Id);
var NovosMembros = model.MembrosID.Except(MembrosNaEquipa);
db.Users.Where(m => NovosMembros.Contains(m.Id))
.ToList()
.ForEach(m => equipa.Membros.Add(m));
db.Entry(equipa).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
PopulateMembrosEscolhidos(model);
return View(model);
}
At some point, I'm going to remove the selectlist and replace it for a textbox where the user inputs user names to add to the memberlist of the team - but at the moment i'm just trying to figure out how to save the many-to-many relationship.
Edit
I had that feeling that it was something simple, but just couldn't get there. I started using that solution, but came across an error that to be honest, I've never seen before.
Multiplicity constraint violated. The role 'ApplicationUser_Equipas_Source' of the relationship 'Codings.Models.ApplicationUser_Equipas' has multiplicity 1 or 0..1.
It happens on line "db.Equipas.Add(equipa);" of Create.
It's probably my mistake, since I tried to add a team to the users simply by
var MembrosEscolhidos = db.Users.Where(m => model.MembrosID.Contains(m.Id)).ToList();
foreach (var item in MembrosEscolhidos)
{
item.Equipas.Add(equipa);
}

I think one easy fix would be to reference your EquipaModel as a collection in your users table.
e.g.
public virtual ICollection<EquipaModel> Equipas { get; set; }
like you reference your users in your EquipaModel
e.g.
public class EquipaModel
{
...
public virtual ICollection<ApplicationUser> Membros { get; set; }
}
Then your users can have many "Equipas" or teams.

Related

.Net core EF, saving records to many-to-many type table

I am new to EF and .Net core and I'm having trouble with many-to-many relationship in my project. I used microsoft documentation to setup the relationship, but i have trouble inserting any data. Project is a kanban board and i am trying to set up relations between users and tasks. Both of them already exist. The goal is to have a table with userId and taskId. Here are my models:
KanbanTask Model:
public class KanbanTask : Entity
{
public string Title { get; set; }
[Required]
public string Description { get; set; }
[Required]
public string Status { get; set; }
public int ProgressStatus { get; set; }
public List<UserTask> UserTask { get; set; }
}
User Model:
public class User : Entity
{
[Required]
public string Name { get; set; }
[Required]
public string Surname { get; set; }
public List<UserTask> UserTask { get; set; }
}
Entity Model:
public class Entity
{
public int Id { get; set; }
}
UserTaskModel:
public class UserTask
{
public int UserId { get; set; }
public User User { get; set; }
public int KanbanTaskId { get; set; }
public KanbanTask KanbanTask { get; set; }
}
My DbContex:
public DbSet<KanbanTask> KanbanTasks { get; set; }
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserTask>()
.HasKey(t => new { t.UserId, t.KanbanTaskId });
modelBuilder.Entity<UserTask>()
.HasOne(pt => pt.User)
.WithMany(p => p.UserTask)
.HasForeignKey(pt => pt.UserId);
modelBuilder.Entity<UserTask>()
.HasOne(pt => pt.KanbanTask)
.WithMany(t => t.UserTask)
.HasForeignKey(pt => pt.KanbanTaskId);
}
}
My function in service:
public async Task<ResultDTO> AssignTaskToUser(int taskId, int userId)
{
var result = new ResultDTO()
{
Response = null
};
try
{
var user = await _repo.GetSingleEntity(x => x.Id == userId);
var kanbanTask = await _taskrepo.GetSingleEntity(y => y.Id == taskId);
if (user != null && kanbanTask != null)
{
var usertask = new UserTask()
{
KanbanTaskId = taskId,
UserId = userId
};
kanbanTask.UserTask.Add(usertask);
user.UserTask.Add(usertask);
await _repo.Patch(user);
}
else
result.Response = "Task or user not found";
}
catch (Exception e)
{
result.Response = e.Message;
return result;
}
return result;
}
My repository:
public async Task Patch(T entity)
{
_dbSet.Update(entity);
await _context.SaveChangesAsync();
}
Like this
var usertask = new UserTask()
{
KanbanTaskId = taskId,
UserId = userId
};
db.UserTasks.Add(usertask);
db.SaveChanges();
What you need to is to make sure that your middle entity (UserTask) always saves the Keys of both entities so I strongly suggest to add that logic in UserTask constructor.
public class UserTask
{
public int UserId { get; set; }
public User User { get; set; }
public int KanbanTaskId { get; set; }
public KanbanTask KanbanTask { get; set; }
public UserTask() { }
public UserTask(User user, KanbanTask kanbanTask)
{
KanbanTask = kanbanTask;
KanbanTaskId = kanbanTask.Id;
User = user;
UserId = userId;
}
}
//
var usertask = new UserTask(user, kanbanTask);
kanbanTask.UserTask.Add(usertask);
user.UserTask.Add(usertask);
await _repo.Patch(user);
//
I have wrote an example for this common problem. Here https://github.com/salsita18/ManyToManyNetCore you can check the approach I took, using a single generic MiddleEntity class.
I also added it to nuget in order to reuse it, but you can just make your own implementation following the pattern

Entity Framework and ASP.net MVC - Updating Entity Model with Many-to-Many relationship

Using Asp.Net Identity and Entity Framework, I'am trying to update AspNetUser, and change its AspNetRole.
This is my UserDto class :
public class UserDto : BaseDto
{
public string Uuid { get; set; }
public string Email { get; set; }
public string FullName { get; set; }
public string UserName { get; set; }
public List<RoleDto> Roles { get; set; }
public int NumberOfLikes { get; set; }
public int NumberOfComments { get; set; }
public int NumberOfReviews { get; set; }
}
This is my RoleDto class:
public class RoleDto : BaseDto
{
[ScaffoldColumn(false)]
public string Uuid { get; set; }
[Required(ErrorMessage = "This field is required")]
[MinLength(3, ErrorMessage = "Minimum 3 characters")]
[DisplayName("Role Name")]
public string Name { get; set; }
[ScaffoldColumn(false)]
public int NumberOfUsers { get; set; }
}
This is my Update method in UserController:
[HttpPost]
public ActionResult Edit(UserViewModel vm)
{
UserDto dto = new UserDto
{
Uuid = vm.Uuid,
Email = vm.Email,
FullName = vm.FullName,
UserName = vm.UserName,
Roles = new List<RoleDto>()
};
dto.Roles.Add(new RoleDto
{
Uuid = vm.RoleId
});
OpUserUpdate update = new OpUserUpdate();
update.userDto = dto;
var result = this.Manager.ExecuteOperation(update);
return RedirectToAction("Index");
}
This is User Update Operation:
public class OpUserUpdate : OpUserBase
{
public override OperationResult Execute(FoodRestaurantEntities entities)
{
var id = this.userDto.Roles[0].Uuid;
if (!string.IsNullOrWhiteSpace(this.userDto.Uuid))
{
var user = entities.AspNetUsers.FirstOrDefault(u => u.Id == this.userDto.Uuid);
var role = entities.AspNetRoles.FirstOrDefault(r => r.Id == id);
if(user != null)
{
// If is set email
if (!string.IsNullOrWhiteSpace(this.userDto.Email))
user.Email = this.userDto.Email;
// If is set full name
if (!string.IsNullOrWhiteSpace(this.userDto.FullName))
user.FullName = this.userDto.FullName;
// If is set full name
if (!string.IsNullOrWhiteSpace(this.userDto.UserName))
user.UserName = this.userDto.UserName;
user.AspNetRoles.Add(role);
entities.SaveChanges();
this.Result.Status = true;
this.Result.Message = "Successfully updated";
}
else
{
this.Result.Status = false;
this.Result.Message = "Not successfull";
}
}
return Result;
}
}
1. OperationManager class just executes operation
2. The problem is, that user.AspNetRoles.Add(role) line just adds another Role to a user. For example, if user had User role, it would add Admin role also instead of changing it to Admin. What am I doing wrong?

how i update multiple table using viewmodel in Asp.net mvc

Employee Model
public class Employee
{
[Key]
public int EmployeeID { get; set; }
public string Name { get; set; }
public virtual Department Departments { get; set; }
public int DepartmentID { get; set; }
}
Department Model
public class Department
{
public int DepartmentID { get; set; }
public string DepartmentName { get; set; }
}
View Model for Department and Employee
public class EDViewModel
{
public int ID { get; set; }
public int EmployeeID { get; set; }
public string Name { get; set; }
public Department Departments { get; set; }
public int DepartmentID { get; set; }
public string DepartmentName { get; set; }
}
Now i want to update both tables with single view.
Controller
public ActionResult Edit(int?id)
{
// write some code for update both table at once time
}
PostMethod
[HttpPost]
public ActionResult Edit(EDViewModel Emodel)
{
var user = db.Employees.Where(c => c.Employee_Id == Emodel.Employee_Id).FirstOrDefault();
user.UserName = Emodel.UserName;
user.ProfilePicture = Emodel.ProfilePicture;
db.Entry(user).State = EntityState.Modified;
db.Entry(user).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Home");
}
But in this method only Update Employee record not department
After many searching finally i get a solid solution
hope you like or improve this.
Controller
public ActionResult Edit(int? id)
{
MYDb db = new MYDb();
var user = db.Employees.Where(c => c.Employee_Id == Emodel.Employee_Id).FirstOrDefault();
if (user != null)
{
var vm = new EDViewModel { Employee_id = user.Employee_id, departmentName = user.departmentName };
if (user.department != null)
{
user.Departmet_id = vm.Departments.Departmet_id;
user.DepartmentName = vm.Departments.DepartmentName;
user.Employee_id = vm.employee_id;
user.employeeName = vm.employeeName;
}
return View(user);
}
return Content("Invalid Id");
}
[HttpPost]
public ActionResult Edit(EDViewModel Emodel)
{
var user = db.Employees.Where(c => c.Employee_Id == Emodel.Employee_Id).FirstOrDefault();
user.EmployeeId = Emodel.EmployeeId;
user.EmployeeName= Emodel.EmployeeName;
user.DepartmentName= Emodel.Departmt.DepartmentName;
// Just remove this line
// db.Entry(user).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Home");
}
it worked for me
just remove this
db.Entry(user).State = EntityState.Modified;
if we not remove this entityvalidation occur

Nested entity not being saved

I have the following method that saves an EmailTemplate. Based on the ID of a dropdown it populates the EmailAccount as the foreign entity property.
public ActionResult Edit([Bind(Include = "EmailAccountId, EmailTemplate")] EmailTemplateViewModel emailTemplateViewModel)
{
if (ModelState.IsValid)
{
if (emailTemplateViewModel.EmailAccountId > 0)
{
emailTemplateViewModel.EmailTemplate.EmailAccount = db.EmailAccounts.Find(emailTemplateViewModel.EmailAccountId);
}
db.Entry(emailTemplateViewModel.EmailTemplate).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(emailTemplateViewModel);
}
Everything in the EmailTemplate saves fine apart from EmailAccount. In debugger I can see that the property is populated before db.SaveChanges() is called.
I am setting the entity state to EntityState.Modified but it's not picking up the foreign property.
I tried adding:
db.Entry(emailTemplateViewModel.EmailTemplate.EmailAccount).State = EntityState.Modified;
But this didn't work. How do I tell EntityFramework that it needs to save the nested entity?
Edit:
As requested
public class EmailTemplateViewModel
{
public List<EmailAccount> EmailAccounts { get; set; }
public EmailTemplate EmailTemplate { get; set; }
[Display(Name = "Email Account")]
public int EmailAccountId { get; set; }
public IEnumerable<SelectListItem> EmailAccountsList
{
get
{
var allEmails = EmailAccounts.Select(e => new SelectListItem { Value = e.ID.ToString(), Text = e.Email });
return DefaultEmailAccountList.Concat(allEmails);
}
}
public IEnumerable<SelectListItem> DefaultEmailAccountList
{
get
{
return Enumerable.Repeat(new SelectListItem
{
Value = "-1",
Text = "Select Email Account"
}, count: 1);
}
}
}
public class EmailTemplate
{
public int ID { get; set; }
[StringLength(50)]
[Index(IsUnique = true)]
public string Identifier { get; set; }
public int Interval { get; set; }
public string TitleTemplate { get; set; }
[DataType(DataType.MultilineText)]
public string BodyTemplate { get; set; }
public virtual EmailAccount EmailAccount { get; set; }
}
I was modifying before I attached so the change wasn't tracked.
This works
if (ModelState.IsValid)
{
db.Entry(emailTemplateViewModel.EmailTemplate).State = EntityState.Modified;
if (emailTemplateViewModel.EmailAccountId > 0)
{
emailTemplateViewModel.EmailTemplate.EmailAccount = db.EmailAccounts.Find(emailTemplateViewModel.EmailAccountId);
}
db.SaveChanges();
return RedirectToAction("Index");
}
return View(emailTemplateViewModel);

ViewModel adding custom class to another class

I created this viewmodel:
public class PlayerViewModel
{
PlayerRepository repo = new PlayerRepository();
public Player Player { get; set; }
public int SelectedUserID { get; set; }
public SelectList Users { get; set; }
public PlayerViewModel()
{
Player = new Player();
}
public PlayerViewModel(int id)
{
Player = repo.Retrieve(id);
Users = new SelectList(repo.GetUsers());
SelectedUserID = 0;
}
}
this I have in view:
#Html.DropDownListFor(x => x.SelectedUserID, Model.Users)
#Html.ValidationMessageFor(x => x.SelectedUserID)
and this in controller:
[Authorize]
public ActionResult Upravit(int id)
{
var playerview = new PlayerViewModel(id);
return View(playerview);
}
[Authorize,HttpPost]
public ActionResult Upravit(int id, PlayerViewModel playerView)
{
if (ModelState.IsValid)
{
playerView.Player.User = usRepo.GetUserById(playerView.SelectedUserID);
repo.Save(playerView.Player);
return RedirectToAction("Podrobnosti", new { id = playerView.Player.PlayerID });
}
return View(playerView);
}
Now I have problem that " The field SelectedUserID must be a number." and I have in dropdownlist UserName. I modified this many times, I tried with Dictionary and other ways but everyway has some problem. So I want just ask for best way to add custom class User to class Player.
Player class:
public class Player
{
// pokud použijeme virtual a vlastností tak nám EF rozšíří o další možnosti jako lazy loading a další
[Key]
public int PlayerID { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Surname { get; set; }
public string PhotoUrl { get; set; }
public string Post { get; set; }
public virtual Team Team { get; set; }
public virtual User User { get; set; }
// public int UserID { get; set; }
//public virtual ICollection<Article> Articles { get; set; }
// Here could be next things as number, ...
}
Thanks
Use this constructor instead:
http://msdn.microsoft.com/en-us/library/dd505286.aspx
public SelectList(
IEnumerable items,
string dataValueField,
string dataTextField
)
Something like this:
Users = new SelectList(repo.GetUsers(),"UserID", "UserName");

Categories

Resources