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
Related
What is the best way to implement update row if it exists, else insert new row logic using Entity Framework?
Below is what I have done so far. I want to check, if any field in the existing employee database has changed then only update that record or if it is a new one add as a new row.
Ex- Update the job title if it has changed, or add it as a new line if a new employee is added
//DbContext
public class DataContext : DbContext
{
public static string providerName = "System.Data.SqlClient";
public DbSet<DisplayAPIDataEmployee>? Employee { get; set; }
protected override void OnConfiguring(Microsoft.EntityFrameworkCore.DbContextOptionsBuilder optionBuilder)
{
optionBuilder.UseSqlServer("Server=;Initial Catalog = ;user id = ;password=");
}
protected override void OnModelCreating(Microsoft.EntityFrameworkCore.ModelBuilder modelBuilder)
{
modelBuilder.Entity<DisplayAPIDataEmployee>().ToTable("Employee", e => e.IsTemporal());
}
}
// Data model
[Table("Employee")]
public class DisplayAPIDataEmployee
{
public DisplayAPIDataEmployee()
{
createdOn = DateTime.Now;
}
public DateTime ?createdOn { get; set; }
public string ?displayName { get; set; }
public string ?shortBirthDate { get; set; }
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public string employee_id { get; set; }
}
Inject the DbContext class into your controller and handle your logic in a controller method
private readonly DataContext _context;
public Controller(DataContext _context) => this._context = _context;
...
// rest of your code
...
public void Test(string employee_id) {
using DataContext dbContext = _context;
using IDbContextTransaction transaction = dbContext.Database.BeginTransaction();
try {
DisplayAPIDataEmployee? employee = dbContext.Employee.FirstOrDefault(e => e.employee_id.Equals(employee_id));
if (employee is null) {
// add employee
DisplayAPIDataEmployee add_employee = new(); //
add_employee.employee_id = "";
dbContext.Employee.AddRange(add_employee);
dbContext.SaveChanges();
}
else {
employee.employee_id = ""; // update employee property value
dbContext.SaveChanges(); // entity 'employee' is tracked by EF Core and any saved changes to it is reflected to entity in Database.
}
transaction.Commit(); // commit all save changes if successful
}
catch (Exception ex)
{
transaction.Rollback(); // rollback in case of errors
dbContext.ChangeTracker.Clear();
// Log error
}
}
I have encountered an issue when inserting (Add method of EF API) or updating (Update method of EF API) entities holding reference properties to existing entities (I call existing entity an entity that already exists in the database, and has its PK properly set).
The model consists in Place, Person, Address, and Status :
A person has many addresses.
A place has several persons, and also
several addresses.
Places, Persons and Addresses have statuses.
All entities have an Id, Name, Created date and Modified date (these fields are all defined in an abstract BaseEntity)
If I create a whole graph for a "Place", with new Persons and new Addresses, and save it in one step, everything is fine.
If I create a Place with Addreses then save it, it is still ok.
But at last when I add an existing person and resave the Place, I have an exception: EF actually tries to insert the existing person, and SQL Server throws an error because EF tried to insert a row with a provided Id (PK are set to be generated by SQL Server).
That means that by default, EF Core 1.1.0 looks like being unable to properly traverse relationships and discover which enitites should be added, and which one should be ignored or updated. It tries to insert an entity which already has its PK set to a positive value.
After doing some research, I discovered the new DbContext.ChangeTracker.Track() method of the EF Core 1.1.0 API, and it allows one to execute a callback method on all the entities discovered by traversing the relationships of the root entity.
Thanks to this, I have set up the appropriate State, according to the value of the primary key.
Without this code (in DbRepository.ApplyStates()), none of my insert would work, as long as they would refer a relation to an existing entity.
Note that with EF7 and the DNX CLI, this scenario would work, even without the DbRepository.ApplyStates() thing.
Source to reproduce
everything is in there: models, DbContext, Repository and test code.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using Microsoft.EntityFrameworkCore;
namespace EF110CoreTest
{
public class Program
{
public static void Main(string[] args)
{
Seed();
}
private static void Seed()
{
// Repo
var statusRepo = new DbRepository<Status>();
var personRepo = new DbRepository<Person>();
var addressRepo = new DbRepository<Address>();
var placeRepo = new DbRepository<Place>();
// Status
if (!statusRepo.GetAll().Any())
{
statusRepo.InsertOrUpdate(new Status() { Name = "Active" });
statusRepo.InsertOrUpdate(new Status() { Name = "Archive" });
statusRepo.SaveChanges();
}
var statusActive = statusRepo.GetSingle(1);
var statusArchive = statusRepo.GetSingle(2);
// Delete the non static data
foreach(var address in addressRepo.GetAll()) addressRepo.Delete(address);
addressRepo.SaveChanges();
foreach (var place in placeRepo.GetAll()) placeRepo.Delete(place);
placeRepo.SaveChanges();
foreach (var person in personRepo.GetAll()) personRepo.Delete(person);
personRepo.SaveChanges();
Console.WriteLine("Cleared any existing data");
/***********************************************************************/
// Step 1 : a person with status and addresses is saved
var personWithAddresses = new Person()
{
Name = "Jon SNOW",
Status = statusActive,
AddressCollection = new List<Address>()
{
new Address() { City = "Castleblack", Status = statusActive },
new Address() { City = "Winterfel", Status = statusArchive }
}
};
personRepo.InsertOrUpdate(personWithAddresses);
personRepo.SaveChanges();
Console.WriteLine("Step 1 ok");
System.Threading.Thread.Sleep(1000);
/***********************************************************************/
// Step 2 : Create a place with addresses
var placeWithAddress = new Place()
{
Name = "Castleblack",
Status = statusActive
};
placeWithAddress.AddressCollection.Add(new Address() { City = "Castleblack", Status = statusActive });
placeRepo.InsertOrUpdate(placeWithAddress);
placeRepo.SaveChanges();
Console.WriteLine("Step 2 ok");
System.Threading.Thread.Sleep(1000);
/***********************************************************************/
// Step 3 : add person to this place
placeWithAddress.PersonCollection.Add(personWithAddresses);
placeRepo.InsertOrUpdate(placeWithAddress);
placeRepo.SaveChanges();
Console.WriteLine("Step 3 ok");
System.Threading.Thread.Sleep(1000);
}
}
public class DbRepository<T> where T : BaseEntity
{
protected readonly MyContext _context;
public DbRepository() { _context = new MyContext(); }
public T GetSingle(int id) => _context.Set<T>().FirstOrDefault(e => e.Id == id);
public IEnumerable<T> GetAll() => _context.Set<T>().AsEnumerable();
public void Insert(T entity)
{
ApplyStates(entity);
_context.Add(entity);
}
public void Update(T entity)
{
ApplyStates(entity);
_context.Update(entity);
}
public void Delete(T entity)
{
_context.Remove(entity);
}
private void ApplyStates(T entity)
{
_context.ChangeTracker.TrackGraph(entity, node =>
{
var entry = node.Entry;
var childEntity = (BaseEntity)entry.Entity;
entry.State = childEntity.IsNew ? EntityState.Added : EntityState.Modified;
});
}
public void InsertOrUpdate(T entity)
{
if (entity.IsNew) Insert(entity); else Update(entity);
}
public void SaveChanges()
{
var pendingChanges = _context.ChangeTracker.Entries<T>()
.Where(entry => entry.State == EntityState.Added || entry.State == EntityState.Modified)
.Select(e => e.Entity)
.ToList();
foreach (var entity in pendingChanges)
{
entity.Modified = DateTime.Now;
if (entity.Created == null) entity.Created = DateTime.Now;
}
_context.SaveChanges();
}
}
#region Models
public abstract class BaseEntity
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime? Created { get; set; }
public DateTime? Modified { get; set; }
[NotMapped]
public bool IsNew => Id <= 0;
}
public class Person : BaseEntity
{
public int? StatusId { get; set; }
public Status Status { get; set; }
public List<Address> AddressCollection { get; set; } = new List<Address>();
}
public class Address : BaseEntity
{
public string Zip { get; set; }
public string City { get; set; }
public int? StatusId { get; set; }
public Status Status { get; set; }
public int? PersonId { get; set; }
public Person Person { get; set; }
public int? PlaceId { get; set; }
public Place Place { get; set; }
}
public class Place : BaseEntity
{
public int? StatusId { get; set; }
public Status Status { get; set; }
public List<Person> PersonCollection { get; set; } = new List<Person>();
public List<Address> AddressCollection { get; set; } = new List<Address>();
}
public class Status : BaseEntity { }
#endregion
#region Context
public class MyContext : DbContext
{
public DbSet<Status> StatusCollection { get; set; }
public DbSet<Person> PersonCollection { get; set; }
public DbSet<Address> AddressCollection { get; set; }
public DbSet<Place> PlaceCollection { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
// Basic event fire of model creation
base.OnModelCreating(builder);
// Status
builder.Entity<Status>().ToTable("Status", "Shared");
// Person
builder.Entity<Person>().ToTable("Person", "Shared");
builder.Entity<Person>()
.HasMany(p => p.AddressCollection)
.WithOne(a => a.Person);
// Address
builder.Entity<Address>().ToTable("Address", "Shared");
builder.Entity<Address>()
.HasOne(p => p.Person)
.WithMany(a => a.AddressCollection);
// Place
builder.Entity<Place>().ToTable("Place", "Shared");
builder.Entity<Place>()
.HasMany(p => p.AddressCollection)
.WithOne(p => p.Place);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"Server=(localdb)\mssqllocaldb;Database=EF110CoreTest;Trusted_Connection=True;");
}
}
#endregion
}
Project.json file
{
"version": "1.0.0-*",
"buildOptions": {
"emitEntryPoint": true
},
"dependencies": {
"Microsoft.EntityFrameworkCore": "1.1.0",
"Microsoft.EntityFrameworkCore.SqlServer": "1.1.0",
"Microsoft.EntityFrameworkCore.Tools": "1.1.0-preview4-final"
},
"frameworks": {
"net461": {}
},
"tools": {
"Microsoft.EntityFrameworkCore.Tools.DotNet": "1.0.0-preview3-final"
}
}
Exception details
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. See the inner exception for details. ---> System.Data.SqlClient.SqlException: Cannot insert explicit value for identity column in table 'Person' when IDENTITY_INSERT is set to OFF.
I modified some code, please review it.
In class DbRepository, added another constructor, to make sure there is the same DbContext in different DbRepository.
public DbRepository(MyContext myContext)
{
_context = myContext;
}
In class Person added 2 properties, to ensure the relation between Person and Place.
public int? PlaceId { get; set; }
public Place Place { get; set; }
In function Seed, modified some code with above modifications.
Firstly, in the part of initialize repository.
// Repo
var myContext = new MyContext();
var statusRepo = new DbRepository<Status>(myContext);
var personRepo = new DbRepository<Person>(myContext);
var addressRepo = new DbRepository<Address>(myContext);
var placeRepo = new DbRepository<Place>(myContext);
This will make all repository use same database connection.
Secondly, due to those changes, the clear process should change the orders, too.
// Delete the non static data
foreach (var address in addressRepo.GetAll()) addressRepo.Delete(address);
addressRepo.SaveChanges();
foreach (var person in personRepo.GetAll()) personRepo.Delete(person);
personRepo.SaveChanges();
foreach (var place in placeRepo.GetAll()) placeRepo.Delete(place);
placeRepo.SaveChanges();
In your Step 1, I extract the address with CatsleBlack, because I guess the one in Person and the other one in Place should be the same.
So, when you initialize a new Person, it will be
var castleBlack = new Address {City = "Castleblack", Status = statusActive};
var personWithAddresses = new Person()
{
Name = "Jon SNOW",
Status = statusActive,
AddressCollection = new List<Address>()
{
castleBlack,
new Address() { City = "Winterfel",
Status = statusArchive }
}
};
Initialize the Place
var placeWithAddress = new Place()
{
Name = "Castleblack",
Status = statusActive
};
placeWithAddress.AddressCollection.Add(castleBlack);
Those are what I have done, can save successfully. The Person record in db also has its PlaceId.
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
}
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;
}
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.