I have read many topics on saving a many to many relationship with EF 6.1, but I'm not getting any further. The join table with reservationId and facilityId is still empty after I save my reservation.
These are my models:
public class Reservation
{
[Key]
public int reservationId { get; set; }
//Lijst met Facilities, Many to Many
public virtual ICollection<Facility> FacilitiesList { get; set; }
}
public class Facility
{
[Key]
public int FacilityId { get; set; }
public decimal FacilityPrice { get; set; }
public string FacilityType { get; set; }
//Many to Many
public virtual ICollection<Reservation> reservationList { get; set; }
public Facility()
{
//Instantiate our implementation of ICollection
this.reservationList = new HashSet<Reservation>();
}
}
I create the join table by overriding OnModelCreating:
public partial class DataContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Reservation>()
.HasMany<Facility>(s => s.FacilitiesList)
.WithMany(c => c.reservationList)
.Map(cs =>
{
cs.MapLeftKey("ReservationId");
cs.MapRightKey("FacilityId");
cs.ToTable("ReservationFacility");
});
}
}
Edit: Here is where I save a reservation:
public ActionResult Create()
{
ViewBag.FacilitiesList = new SelectList(_facilityrepository.GetAll(), "FacilityId", "FacilityType");
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ReservationId,ArrivalDate,LeaveDate,CampingSpotId,UserId,PersonsAmount,FacilityType,FacilityPrice")] Reservation reservation)
{
_reservationrepository.Add(reservation);
return RedirectToAction("ShowAvailableSpots", new { ArrivalDate = reservation.ArrivalDate, LeaveDate = reservation.LeaveDate, ReservationID = reservation.ReservationId });
}
Now I have the tables. I can save facilities and I can select them when creating a reservation with a MultiSelectList.
Why is the join table always empty? What do I have to do to fill the FacilitiesList so I can get this data anywhere in my project?
Because I was completely new to this I didn't had a clue that I needed to initialise and populate the list. Thx to #dellywheel for pointing me in the right direction!
Here's the working code:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ReservationId,ArrivalDate,LeaveDate,CampingSpotId,UserId,PersonsAmount,FacilityType,FacilityPrice")] Reservation reservation, int[] SelectedFacilities)
{
//Instantiate our facilities list!
reservation.FacilitiesList = new List<Facility>();
foreach (int facId in SelectedFacilities)
{
var facType = _facilityrepository.GetFacilityType(facId);
var facPrice = _facilityrepository.GetFacilityPricePerDay(facId);
reservation.FacilitiesList.Add(new Facility { FacilityId = facId, FacilityType = facType, FacilityPrice = facPrice} );
}
_reservationrepository.Add(reservation);
return View() //code omitted
}
Related
I am attempting to save date in multiple tables with a one-to-many relationship in using EF Core. When I do, I get this error:
InvalidOperationException: The instance of entity type 'OrganizationGroupEntity' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
Here is my code:
Request model:
public class Organization
{
public Organization()
{ }
public Organization(OrganizationEntity organizationEntity, List<OrganizationGroupEntity> organizationGroupEntities)
{
Id = organizationEntity.Id;
Name = organizationEntity.Name;
Groups = ToList(organizationGroupEntities);
}
public int Id { get; set; }
public string Name { get; set; }}
public List<OrganizationGroup> Groups { get; set; }
private List<OrganizationGroup> ToList(List<OrganizationGroupEntity> organizationGroupEntities)
{
return organizationGroupEntities.Select(
entity => new OrganizationGroup(entity)
).ToList();
}
}
public class OrganizationGroup
{
public OrganizationGroup()
{ }
public OrganizationGroup (OrganizationGroupEntity entity)
{
Id = entity.Id;
Group = entity.Group;
}
public int Id { get; set; }
public string Group { get; set; }
}
Entity models:
public class OrganizationEntity
{
public OrganizationEntity()
{ }
public OrganizationEntity(Organization model)
{
Id = model.Id;
Name = model.Name;
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
}
public class OrganizationGroupEntity
{
public OrganizationGroupEntity()
{ }
public OrganizationGroupEntity(int organizationId, OrganizationGroup model)
{
Id = model.Id;
OrganizationId = organizationId;
Group = model.Group;
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public int OrganizationId { get; set; }
public string Group { get; set; }
}
dbContext:
public DbSet<OrganizationEntity> Organizations { get; set; }
public DbSet<OrganizationGroupEntity> OrganizationGroups { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<OrganizationEntity>()
.ToTable("Organizations", "dbo");
modelBuilder.Entity<OrganizationGroupEntity>()
.ToTable("OrganizationGroups", "dbo");
}
repository:
public async Task<Organization> UpdateOrganization(Organization request)
{
// Get the org entity
var organizationEntity = new OrganizationEntity(request);
// get the org groups entities
var groupEntities = request.Groups
.Select(
group => new OrganizationGroupEntity(request.Id, group)
).ToList();
// Get the group entities to remove
var oldEntities = GetOrganizationGroups(request.Id);
var entitiesToRemove = new List<OrganizationGroupEntity>();
foreach (var oldEntity in oldEntities.Result)
{
if (!groupEntities.Any(e => e.Id == oldEntity.Id))
{
entitiesToRemove.Add(oldEntity);
}
}
using (var transaction = _context.Database.BeginTransaction())
{
_context.Organizations.Update(organizationEntity);
_context.OrganizationGroups.UpdateRange(groupEntities); // <-- Fails here
_context.OrganizationGroups.RemoveRange(entitiesToRemove);
await _context.SaveChangesAsync();
transaction.Commit();
}
return request;
}
private async Task<IEnumerable<OrganizationGroupEntity>> GetOrganizationGroups(int organizationId)
{
return await _context.OrganizationGroups
.Where(e => e.OrganizationId == organizationId)
.OrderBy(e => e.Order)
.ToListAsync();
}
It turns out when I was getting the current groupEntities in order to fins out what to remove I was initiating tracking on that table. Adding AsNoTracking() to GetOrganizationGroups solved my issue. Like so:
private async Task<IEnumerable<OrganizationGroupEntity>> GetOrganizationGroups(int organizationId)
{
return await _context.OrganizationGroups
.AsNoTracking()
.Where(e => e.OrganizationId == organizationId)
.OrderBy(e => e.Order)
.ToListAsync();
}
In my ASP.NET MVC 5 application, I have situation where I need to get the primary key that got generated for a particular entry in database. Also, I need to get that id and set a foreign key relationship in database. I have tried below attempt and it seems to be working but I had to call _context.SaveChanges() TWICE.
UI Behaviour: the user has a dropdown with a list of companies in referral form. When a company is not found, user will select "Others" in the dropdown. And it will show a textbox, where user will enter the company name. So I need to add this company to the database and link it to the referral row.
Relationship between tables Referral and Company:
dBModelBuilder.Entity<Company>()
.HasMany(u => u.Referrals)
.WithRequired(u => u.Company)
.HasForeignKey(u => u.CompanyId);
Model classes:
public class Company
{
[Key]
public int CompanyId { get; set; }
public string CompanyName { get; set; }
public ICollection<Referral> Referrals { get; set; }
public ICollection<CoverLetter> CoverLetters { get; set; }
}
public class Referral
{
[Key]
public int ReferralId { get; set; }
[Display(Name = "REFERRALS")]
public string ReferralName { get; set; }
public int? CoverLetterId { get; set; }
public virtual CoverLetter CoverLetter { get; set; }
public int CompanyId { get; set; }
public virtual Company Company { get; set; }
}
ViewModel:
public class Referral
{
[Key]
public int ReferralId { get; set; }
[Display(Name = "REFERRALS")]
public string ReferralName { get; set; }
public int? CoverLetterId { get; set; }
public virtual CoverLetter CoverLetter { get; set; }
public int CompanyId { get; set; }
public virtual Company Company { get; set; }
}
My attempt inside the controller action:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(ReferralViewModel viewModel)
{
var candidateId = User.Identity.GetUserId();
var referral = new Referral
{
ReferralName = viewModel.ReferralName,
};
if (viewModel.CompanyId.HasValue)
// if it is option 4 then we need to add it to the company table
{
if (!string.IsNullOrEmpty(viewModel.TempCompany))
{
var f = new Company
{
CompanyName = viewModel.TempCompany
};
_context.Companies.Add(f);
// ********FIRST CALL
_context.SaveChanges();
_context.Referrals.Add(referral);
referral.CompanyId = f.CompanyId;
// **********SECOND CALL
_context.SaveChanges(); // SECOND CALL ------
}
else
{
referral.CompanyId = viewModel.CompanyId.Value;
_context.Referrals.Add(referral);
_context.SaveChanges();
}
}
return RedirectToAction("ReferralCenter");
}
Question: can I do these steps in one call to _context.SaveChanges(); ?
EDIT
With this code, I get a NullReferenceException:
if (!string.IsNullOrEmpty(viewModel.TempCompany))
{
var f = new Company
{
CompanyName = viewModel.TempCompany
};
f.Referrals.Add(referral); // Referrals is NULL(EXCEPTION)
_context.Companies.Add(f);
// _context.SaveChanges();
//_context.Referrals.Add(referral);
// referral.CompanyId = f.CompanyId;
_context.SaveChanges();
}
It is my understanding that FK constraints are managed within EF, so long as they are configured in the DB.
If in your EF model the Referral is a child of Company then there should be a referrals collection accessible from the Company object instance.
UPDATE
I just built a simple sample MVC Proj, 2x SQL tables (Referrals and Company FK on CompanyId) 4 x Companies, 4th being OTHER, built an EF model with Reverse POCO for speed, added a Controller with scaffolding and views and modified my controller to the below. (I just insert the date time rather than tempCompanyName. The syntax generated here is more like that in the answer by #ashin, but it works for me.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ReferralId,CompanyId")] Referral referral)
{
if (ModelState.IsValid)
{
if (referral.CompanyId == 4)
{
var f = new Company() { Name = DateTime.Now.ToString() };
referral.Company = f;
}
db.Referrals.Add(referral);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.CompanyId = new SelectList(db.Companies, "CompanyId", "Name", referral.CompanyId);
return View(referral);
}
My Company model does instanciate a new Referrals Collection
public Company()
{
Referrals = new System.Collections.Generic.List<Referral>();
}
If you have the navigation property defined, you could just save both the Company and Referral in one shot as follows:
if (!string.IsNullOrEmpty(viewModel.TempCompany))
{
var f = new Company
{
CompanyName = viewModel.TempCompany
};
referral.Company = f;
_context.SaveChanges();
}
EF will take care of setting the FK.
EDIT
Have updated the code to include _context.Referrals.Add(referral) as mentioned in the comments:
if (!string.IsNullOrEmpty(viewModel.TempCompany))
{
var f = new Company
{
CompanyName = viewModel.TempCompany
};
referral.Company = f;
_context.Referrals.Add(referral);
_context.SaveChanges();
}
I have a Web API application with entity framework 6.
I have 2 entities - Player and Match with many to many relationship between them:
public class Player {
public string PlayerId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<Match> Matches { get; set; }
}
public class Match {
public string MatchId { get; set; }
public DateTime DateTime { get; set; }
public virtual ICollection<Player> Players { get; set; }
}
This is how my DBContext looks like:
public class FIFA15RankingContext : DbContext {
public DbSet<Player> Players { get; set; }
public DbSet<Match> Matches { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Entity<Player>()
.HasMany(t => t.Matches)
.WithMany(t => t.Players)
.Map(m => {
m.ToTable("PlayerMatches");
m.MapLeftKey("PlayerId");
m.MapRightKey("MatchId");
}
);
base.OnModelCreating(modelBuilder);
}
}
EF created for me behind the scenes the table PlayerMatches (PlayerId, MatchId).
This is my MatchesController (as scaffolded by VS 2013):
// POST: api/Matches
[ResponseType(typeof(Match))]
public async Task<IHttpActionResult> PostMatch(Match match)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
db.Matches.Add(match);
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateException)
{
if (MatchExists(match.MatchId))
return Conflict();
else
throw;
}
return CreatedAtRoute("DefaultApi", new { id = match.MatchId }, match);
}
I want to be able to create new match and associate with it existing users.
I couldn't make it work in any other way but to add this code (which smells pretty bad) before db.Matches.Add(match):
var playersFromDB = new List<Player>();
if (match.Players != null) {
foreach (var p in match.Players) {
playersFromDB.Add(db.Players.Find(p.PlayerId));
}
}
match.Players = playersFromDB;
What am I doing wrong?
If all players already exists in database, you can attach them to dbcontext instead of load from db.
if (match.Players != null) {
foreach (var player in match.Players) {
db.Players.Attach(player);
}
}
Manually attach the User entities and set their state to Unchanged
foreach( var p in match.Players )
{
db.Entry( p ).State = EntityState.Unchanged;
}
I have a viewModel that contains two classes...
public class vwbooking
{
public booking bookings { get; set; }
public IEnumerable<trace> traces { get; set; }
}
Booking and trace are entities in an edmx.
I want to update the data in these two class with one call to save.
This is what I've tried, along with several other unsuccessful "shot-in-the-dark" variants...
public ActionResult Edit(vwbooking vwbooking)
{
if (ModelState.IsValid)
{
DbEntityEntry<vwbooking> entry = db.Entry(vwbooking);
entry.Property(e => e.bookings.firstname).IsModified = true;
db.SaveChanges();
}
}
I get the following error when calling the save method...
The entity type vwbooking is not part of the model for the current context.
The GET method loads successfully. The post is where I'm having trouble. This is the GET method...
public ActionResult Edit(int id = 0)
{
booking booking = db.bookings.Find(id);
var viewModel = new vwbooking();
viewModel.bookings = booking;
viewModel.traces = (from l in db.traces where l.bookingid == booking.bookingid select l);
return View(viewModel);
}
This is my db context class
public class salesContext : DbContext
{
public salesContext() : base()
{
Configuration.LazyLoadingEnabled = true;
}
public salesContext(string Connection) : base(Connection)
{
Configuration.LazyLoadingEnabled = true;
}
public DbSet<booking> bookings { get; set; }
public DbSet<trace> traces { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<booking>().HasKey(e => e.bookingid);
modelBuilder.Entity<trace>().HasKey(e => e.traceid);
}
}
The code for update your model is:
db.Attach(vwbooking.bookings)
db.ObjectStateManager.ChangeObjectState(vwbooking.bookings, System.Data.EntityState.Modified)
vwbooking.traces.ToList().ForEach(
t =>
{
db.Attach(t);
db.ObjectStateManager.ChangeObjectState(t, System.Data.EntityState.Modified);
}
);
db.SaveChanges();
try this code in the Edit Controller
Got a problem with my Asp.net Mvc4 application.
I designed a "User" Table and a "Projectable" and there is a many-to-many-relationship.
My User Table:
public partial class User
{
public User()
{
this.Role = new HashSet<Role>();
this.Project = new HashSet<Project>();
}
public int Id { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public virtual ICollection<Role> Role { get; set; }
public virtual ICollection<Project> Project { get; set; }
}
And my Project Table:
public partial class Project
{
public Project()
{
this.Thresholds = new HashSet<Thresholds>();
this.User = new HashSet<User>();
this.Testrelease = new HashSet<Testrelease>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Thresholds> Thresholds { get; set; }
public virtual ICollection<User> User { get; set; }
public virtual ICollection<Testrelease> Testrelease { get; set; }
}
In my Project-Controller :
public ActionResult EditProject(int id = 0)
{
Project project = db.Project.Find(id);
if (project == null)
{
return HttpNotFound();
}
List<CheckBoxListInfoInt> userCheck = new List<CheckBoxListInfoInt>();
foreach (User U in db.User.ToList())
{
userCheck.Add(new CheckBoxListInfoInt
{
ValueInt = U.Id,
DisplayText = U.Username,
IsChecked = (project.User.Contains(U))
});
}
ViewBag.P_usrCB = userCheck;
ViewData["pid"] = id;
return View(project);
}
//
// POST: /KPI_Data/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult EditProject(Project project)
{
if (ModelState.IsValid)
{
db.Project.Attach(project);
db.Entry(project).State = EntityState.Modified;
if (project.User.Count > 0)
project.User.Clear();
List<string> usrlist = new List<string>();
var ucb = Request.Form["P_usrCB"];
if (ucb != null)
foreach (string item in ucb.Split(','))
{
int UserId = Convert.ToInt32(item);
User usr = db.User.Single(x => x.Id == UserId);
project.User.Add(usr);
usrlist.Add(usr.Username);
}
db.SaveChanges();
return RedirectToAction("Index");
}
return View(project);
}
The error occurs at
db.SaveChanges();
in my HttpPost ActionMethod
Error Message is :
An error occurred while saving entities that do not expose foreign key
properties for their relationships. The EntityEntries property will
return null because a single entity cannot be identified as the source
of the exception. Handling of exceptions while saving can be made
easier by exposing foreign key properties in your entity types. See
the InnerException for details.
Why do I get this error? and what is the solution? thx
Avoid db.Entry(ptvm.place).State = EntityState.Modified; as it causes concurrency conflicts with no updation. Use ViewModel for a many-many relation rather than two tables.
You must use UpdateModel(table object, "model");
Full Example is as belows :
[HttpPost]
public ActionResult PlaceTag(PlacesWithTagsViewModel model)
{
if (ModelState.IsValid)
{
tag tagtest = GetTagById(model.tagid);
tag.name= model.tag.name;
tag.nameplural = model.tag.nameplural;
UpdateModel(tag, "model");
db.SaveChanges();
return RedirectToAction("Index", "Dashboard", new { id = 5 });
}
}
The advantage of UpdateModel is that you have to mention only those fields which you update avoiding those which remain static. In this way you can update your related data with Viewmodel in Edit View.