I'm somewhat new to EF 6.0 so I'm pretty sure I'm doing something wrong here.
there are two questions related to the problem
what am I doing wrong here
what's the best practice to achieve this
I'm using a code first model, and used the edmx designer to design the model and relationships, the system needs to pull information periodically from a webservice and save it to a local database (SQL Lite) in a desktop application
so I get an order list from the API, when I populate and try to save Ticket, I get a duplicate key exception when trying to insert TicketSeatType -
how do I insert the ticket to dbContext, so that It doesn't try and re-insert insert TicketSeatType and TicketPriceType, I have tried setting the child object states to unchanged but it seems to be inserting
secondly, what would be the best practice to achieve this using EF ? it just looks very inefficient loading each object into memory and comparing if it exists or not
since I need to update the listing periodically, I have to check against each object in the database if it exists, then update, else insert
code:
//read session from db
if (logger.IsDebugEnabled) logger.Debug("reading session from db");
dbSession = dbContext.SessionSet.Where(x => x.Id == sessionId).FirstOrDefault();
//populate orders
List<Order> orders = (from e in ordersList
select new Order {
Id = e.OrderId,
CallCentreNotes = e.CallCentreNotes,
DoorEntryCount = e.DoorEntryCount,
DoorEntryTime = e.DoorEntryTime,
OrderDate = e.OrderDate,
SpecialInstructions = e.SpecialInstructions,
TotalValue = e.TotalValue,
//populate parent refernece
Session = dbSession
}).ToList();
//check and save order
foreach (var o in orders) {
dbOrder = dbContext.OrderSet.Where(x => x.Id == o.Id).FirstOrDefault();
if (dbOrder != null) {
dbContext.Entry(dbOrder).CurrentValues.SetValues(o);
dbContext.Entry(dbOrder).State = EntityState.Modified;
}
else {
dbContext.OrderSet.Add(o);
dbContext.Entry(o.Session).State = EntityState.Unchanged;
}
}
dbContext.SaveChanges();
//check and add ticket seat type
foreach (var o in ordersList) {
foreach (var t in o.Tickets) {
var ticketSeatType = new TicketSeatType {
Id = t.TicketSeatType.TicketSeatTypeId,
Description = t.TicketSeatType.Description
};
dbTicketSeatType = dbContext.TicketSeatTypeSet.Where(x => x.Id == ticketSeatType.Id).FirstOrDefault();
if (dbTicketSeatType != null) {
dbContext.Entry(dbTicketSeatType).CurrentValues.SetValues(ticketSeatType);
dbContext.Entry(dbTicketSeatType).State = EntityState.Modified;
}
else {
if (!dbContext.ChangeTracker.Entries<TicketSeatType>().Any(x => x.Entity.Id == ticketSeatType.Id)) {
dbContext.TicketSeatTypeSet.Add(ticketSeatType);
}
}
}
}
dbContext.SaveChanges();
//check and add ticket price type
foreach (var o in ordersList) {
foreach (var t in o.Tickets) {
var ticketPriceType = new TicketPriceType {
Id = t.TicketPriceType.TicketPriceTypeId,
SeatCount = t.TicketPriceType.SeatCount,
Description = t.TicketPriceType.Description
};
dbTicketPriceType = dbContext.TicketPriceTypeSet.Where(x => x.Id == ticketPriceType.Id).FirstOrDefault();
if (dbTicketPriceType != null) {
dbContext.Entry(dbTicketPriceType).CurrentValues.SetValues(ticketPriceType);
dbContext.Entry(dbTicketPriceType).State = EntityState.Modified;
}
else {
if (!dbContext.ChangeTracker.Entries<TicketPriceType>().Any(x => x.Entity.Id == ticketPriceType.Id)) {
dbContext.TicketPriceTypeSet.Add(ticketPriceType);
}
}
}
}
dbContext.SaveChanges();
//check and add tickets
foreach (var o in ordersList) {
dbOrder = dbContext.OrderSet.Where(x => x.Id == o.OrderId).FirstOrDefault();
foreach (var t in o.Tickets) {
var ticket = new Ticket {
Id = t.TicketId,
Quantity = t.Quantity,
TicketPrice = t.TicketPrice,
TicketPriceType = new TicketPriceType {
Id = t.TicketPriceType.TicketPriceTypeId,
Description = t.TicketPriceType.Description,
SeatCount = t.TicketPriceType.SeatCount,
},
TicketSeatType = new TicketSeatType {
Id = t.TicketSeatType.TicketSeatTypeId,
Description = t.TicketSeatType.Description
},
Order = dbOrder
};
//check from db
dbTicket = dbContext.TicketSet.Where(x => x.Id == t.TicketId).FirstOrDefault();
dbTicketSeatType = dbContext.TicketSeatTypeSet.Where(x => x.Id == t.TicketSeatType.TicketSeatTypeId).FirstOrDefault();
dbTicketPriceType = dbContext.TicketPriceTypeSet.Where(x => x.Id == t.TicketPriceType.TicketPriceTypeId).FirstOrDefault();
if (dbTicket != null) {
dbContext.Entry(dbTicket).CurrentValues.SetValues(t);
dbContext.Entry(dbTicket).State = EntityState.Modified;
dbContext.Entry(dbTicket.Order).State = EntityState.Unchanged;
dbContext.Entry(dbTicketSeatType).State = EntityState.Unchanged;
dbContext.Entry(dbTicketPriceType).State = EntityState.Unchanged;
}
else {
dbContext.TicketSet.Add(ticket);
dbContext.Entry(ticket.Order).State = EntityState.Unchanged;
dbContext.Entry(ticket.TicketSeatType).State = EntityState.Unchanged;
dbContext.Entry(ticket.TicketPriceType).State = EntityState.Unchanged;
}
}
}
dbContext.SaveChanges();
UPDATE:
Found the answer, it has to do with how EF tracks references to objects, in the above code, I was creating new entity types from the list for TicketPriceType and TicketSeatType:
foreach (var o in ordersList) {
dbOrder = dbContext.OrderSet.Where(x => x.Id == o.OrderId).FirstOrDefault();
foreach (var t in o.Tickets) {
var ticket = new Ticket {
Id = t.TicketId,
Quantity = t.Quantity,
TicketPrice = t.TicketPrice,
TicketPriceType = new TicketPriceType {
Id = t.TicketPriceType.TicketPriceTypeId,
Description = t.TicketPriceType.Description,
SeatCount = t.TicketPriceType.SeatCount,
},
TicketSeatType = new TicketSeatType {
Id = t.TicketSeatType.TicketSeatTypeId,
Description = t.TicketSeatType.Description
},
Order = dbOrder
};
....
in this case the EF wouldn't know which objects they were and try to insert them.
the solution is to read the entities from database and allocate those, so it's referencing the same entities and doesn't add new ones
foreach (var t in o.Tickets) {
//check from db
dbTicket = dbContext.TicketSet.Where(x => x.Id == t.TicketId).FirstOrDefault();
dbTicketSeatType = dbContext.TicketSeatTypeSet.Where(x => x.Id == t.TicketSeatType.TicketSeatTypeId).FirstOrDefault();
dbTicketPriceType = dbContext.TicketPriceTypeSet.Where(x => x.Id == t.TicketPriceType.TicketPriceTypeId).FirstOrDefault();
var ticket = new Ticket {
Id = t.TicketId,
Quantity = t.Quantity,
TicketPrice = t.TicketPrice,
TicketPriceType = dbTicketPriceType,
TicketSeatType = dbTicketSeatType,
Order = dbOrder
};
...}
Don't you think that you are trying to write very similar codes for defining the state of each entity?
We can handle all of these operations with a single command.
You can easily achieve this with the newly released EntityGraphOperations for Entity Framework Code First. I am the author of this product. And I have published it in the github, code-project (includes a step-by-step demonstration and a sample project is ready for downloading) and nuget. With the help of InsertOrUpdateGraph method, it will automatically set your entities as Added or Modified. And with the help of DeleteMissingEntities method, you can delete those entities which exists in the database, but not in the current collection.
// This will set the state of the main entity and all of it's navigational
// properties as `Added` or `Modified`.
context.InsertOrUpdateGraph(ticket);
By the way, I feel the need to mention that this wouldn't be the most efficient way of course. The general idea is to get the desired entity from the database and define the state of the entity. It would be as efficient as possible.
Related
I have to update one field in the row of the table after fetching two records from the same row. As an easiest practice I have fetched two records individually, created a new value and then updating that particular property through Entity framework. I think there is a better way to do the same thing with less code. If any body can suggest please.
if (objModel.amountpaid==0)
{
using (estatebranchEntities db=new estatebranchEntities())
{
int rentVar = Convert.ToInt32(db.PropertyDetails.Where(m => m.propertyid == objVM.propertyid).Select(m => m.rent).SingleOrDefault());
int balanceVar = Convert.ToInt32(db.PropertyDetails.Where(m => m.propertyid == objVM.propertyid).Select(m => m.balance).SingleOrDefault());
int balanceUpdateVar = (rentVar + balanceVar);
var propInfo = new PropertyDetail() { balance = balanceUpdateVar };
//var result = (from a in db.PropertyDetails
// where a.propertyid == objVM.propertyid
// select new PropertyDetail
// {
// rent = a.rent,
// balance = a.balance
// }).ToList();
db.PropertyDetails.Attach(propInfo);
db.Entry(propInfo).Property(z => z.balance).IsModified = true;
db.SaveChanges();
}
}
Here is what I think you can do.
Fetch the data once and update once.
using (estatebranchEntities db=new estatebranchEntities())
{
var propDetails = db.PropertyDetails.FirstOrDefault(m => m.propertyid == objVM.propertyid);
if (propDetails != null)
{
int rentVar = Convert.ToInt32(propDetails.rent);
int balanceVar = Convert.ToInt32(propDetails.balance);
int balanceUpdateVar = rentVar + balanceVar;
//now do the update
propDetails.balance = balanceUpdateVar;
db.Entry(proDetails).State = EntityState.Modified;
db.SaveChanges();
}
}
if you need to use the rentVar,balanceVar or the balanceUpdateVar, outside of the using statement then declare them outside it.
I want to update a specific row of the database. This is the code:
public void Update_Datos(int ID)
{
int UserId = Convert.ToInt16((string)(Session["UserId"]));
using (var db = new Entities())
{
//Reading
List<Datos_Personales> objDatos = db.Datos_Personales.ToList<Datos_Personales>();
foreach (Datos_Personales item in objDatos)
{
}
//Update
var datos_personales = db.Datos_Personales.FirstOrDefault(d => d.UserId == UserId && d.Id == ID);
Datos_Personales datos = objDatos[datos_personales.Id];
datos = db.Datos_Personales.Where(d => d.UserId == UserId && d.Id == ID).First();
datos.Fecha_de_nacimiento = Convert.ToDateTime(Fecha_de_nacimiento.Text);
datos.Nombre_Completo = txt_Nombre_Completo.Text;
datos.Identificacion = txt_Identificacion.Text;
datos.Estado_civil = ddEstadoCivil.SelectedValue;
datos.Telefono = txt_num_telefono.Text;
datos.Departamento = ddDepartamento.SelectedValue;
datos.Nacionalidad = Country.SelectedValue;
datos.Salario_min_aceptado = ddSalario_min_aceptado.SelectedValue;
datos.Titulo = txt_Titulo.Text;
datos.Descripcion_Profesional = txt_Descripcion_Profesional.Text;
datos.UserId = Convert.ToInt16(UserId);
db.Datos_Personales.Add(datos);
db.SaveChanges();
}
}
The other is issue is that it creating another row instead updating the one which I need.
You must not read all Db. When you use this line List<Datos_Personales> objDatos = db.Datos_Personales.ToList<Datos_Personales>();, your all entity (all rows in Db) stored into memory.
You can fetch your data directly and edit it. Finally you must not add entity again, only call SaveChages. (If you did not close ChangeTracker)
public void Update_Datos(int ID)
{
int UserId = Convert.ToInt16((string)(Session["UserId"]));
using (var db = new Entities())
{
Datos_Personales datos = db.Datos_Personales.FirstOrDefault(d => d.UserId == UserId && d.Id == ID));
if(datos == null)
return;
datos.Fecha_de_nacimiento = Convert.ToDateTime(Fecha_de_nacimiento.Text);
datos.Nombre_Completo = txt_Nombre_Completo.Text;
datos.Identificacion = txt_Identificacion.Text;
datos.Estado_civil = ddEstadoCivil.SelectedValue;
datos.Telefono = txt_num_telefono.Text;
datos.Departamento = ddDepartamento.SelectedValue;
datos.Nacionalidad = Country.SelectedValue;
datos.Salario_min_aceptado = ddSalario_min_aceptado.SelectedValue;
datos.Titulo = txt_Titulo.Text;
datos.Descripcion_Profesional = txt_Descripcion_Profesional.Text;
datos.UserId = Convert.ToInt16(UserId);
db.SaveChanges();
}
}
I am new to entity framework and LINQ. I am stuck at an issue where I need to firstly check if the record already exists, if it exists then I need to update the record with column RESUMEID accordingly. If not then I need to add the record. I am able to add successfully but I don't know how to update the record in LINQ.
Below is my attempt:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(ReferralViewModel viewModel)
{
var candidateId = User.Identity.GetUserId();
// I AM CONFUSED ABOUT BELOW STATEMENT
var IsDupeReferral = _context.Referrals
.Where(r => (r.CandidateId == candidateId)
&& (r.CompanyId == viewModel.CompanyId) && (r.SkillId == viewModel.SkillId))
.Select(r=>r.ReferralId).SingleOrDefault();
if(IsDupeReferral!=0)
{
//IF I FIND DUPE REFERRAL RECORD I WANT TO UPDATE SOME OF THE VALUES IN THAT
_context.Referrals.Where(r => r.ReferralId == IsDupeReferral).
AND UPDATE r.resumeId with viewModel.ResumeId // How to do this?
// NOT SURE ABOUT BELOW LINE EITHER
_context.SaveChanges();
}
else
{
// BELOW CODE IS WORKING FINE
var referral = new Referral
{
ReferralName = viewModel.ReferralName,
ResumeId = viewModel.ResumeId,
CandidateId = candidateId,
DegreeId = viewModel.DegreeId,
CoverLetterId = viewModel.CoverLetterId,
SkillId = viewModel.SkillId
};
if (!string.IsNullOrEmpty(viewModel.TempCompany))
{
var newCompany = new Company
{
CompanyName = viewModel.TempCompany
};
newCompany.Referrals.Add(referral);
_context.Companies.Add(newCompany); ;
}
else
{
referral.CompanyId = viewModel.CompanyId.Value;
_context.Referrals.Add(referral);
}
_context.SaveChanges();
}
return RedirectToAction("ReferralCenter");
}
Here's the solution
//IF I FIND DUPE REFERRAL RECORD I WANT TO UPDATE SOME OF THE VALUES IN THAT
var referral = _context.Referrals.FirstOrDefault(r => r.ReferralId == IsDupeReferral);
// AND UPDATE r.resumeId with viewModel.ResumeId
if (referral !=null) {
referral.resumeId = viewModel.ResumeId;
_context.Entry(referral).State = System.Data.EntityState.Modified;
_context.SaveChanges();
}
Actually, you don't need getting the IsDupeReferral and then request the record again. Try to combine your code as the following:
var referral = _context.Referrals
.Where(r => (r.CandidateId == candidateId)
&& (r.CompanyId == viewModel.CompanyId) && (r.SkillId == viewModel.SkillId)).SingleOrDefault();
if (referral !=null) {
referral.resumeId = viewModel.ResumeId;
_context.Entry(referral).State = System.Data.EntityState.Modified;
_context.SaveChanges();
}
else {
// add a new record
}
Referral referral = _context.Referrals.FirstOrDefault(r=> r.ReferralId = SomeId);
if(referral == null) // then referral does not exist - add it
{
referral = new Referral{
ReferralName = viewModel.ReferralName,
ResumeId = viewModel.ResumeId,
CandidateId = candidateId,
DegreeId = viewModel.DegreeId,
CoverLetterId = viewModel.CoverLetterId,
SkillId = viewModel.SkillId
};
_context.Referrals.Add(referral);
}
else // referral already exists - update its values
{
//make changes to referral
referral.ReferralName = viewModel.ReferralName;
referral.ResumeId = viewModel.ResumeId;
referral.CandidateId = candidateId;
referral.DegreeId = viewModel.DegreeId;
referral.CoverLetterId = viewModel.CoverLetterId;
referral.SkillId = viewModel.SkillId;
}
_context.SaveChanges(); //no matter added or updated - save the changes
I've three tables Student (studID, fullName, gender...), Enroll (studID, courseID, date) and Course (courseID,courseName, ...). I used the code below to delete all records from Enroll table with studID 001 where there are about three courses the student signed for. However, it only deletes one record.
using(var context = new DBEntities())
{
var _stud = (from s in context.Students where s.studID == "001" select s).FirstOrDefault();
var _course = _stud.Courses.FirstOrDefault();
_course.Students.Remove(_stud);
context.SaveChanges();
}
What do I miss here?
Thank you guys for assisting. Here is how I solved it:
using (var context = new DBEntities())
{
var student = (from s in context.Students where s.studID == "001" select s).FirstOrDefault<Student>();
foreach (Course c in student.Courses.ToList())
{
student.Courses.Remove(c);
}
context.SaveChanges();
}
I used the code below to delete all records from Enroll table
Are you deleting enrolls or students?
Student student = context.Student.FirstOrDefault(s => s.studID == "001");
if (student!=null)
{
student.Enrolls.Load();
student.Enrolls.ToList().ForEach(e => context.Enroll.DeleteObject(e));
}
Have you try this code :
var student = context.Students.Where(p => p.studID == "001").ToList();
foreach (var item in student)
{
if (student != null)
{
var course = student.Courses.ToList();
if (course != null)
{
foreach (var item2 in course)
{
course.Students.Remove(item2);
context.SaveChanges();
}
}
}
}
For others looking to this answer you could also do it using include.
using(var context = new DBEntities())
{
// Get student by id
var student = context.Students.Include(s => s.Courses).Where(s => s.studID == "001").FirstOrDefault();
if(student.Courses != null)
{
// Retrieve list of courses for that student
var coursesToRemove = stud.Courses.ToList();
// Remove courses
foreach (var course in coursesToRemove)
{
student.Courses.Remove(course);
}
// Save changes
context.SaveChanges();
}
}
Let's assume we have a model/database tables like below (not well laid out but fits my question). Let's also assume one movie has always only one director and one producer.
Genre
Id
Name
SubGenre
Id
Name
GenreId
Genre (navigational property)
Director
Id
Name
List<Movie> (navigational property)
Producer
Id
Name
List<Movie> (navigational property)
Actors
Id
Name
List<Movie> (there is many-many table in db) (navigational property)
Movie
Id
Name
SubGenreId
DirectorId
ProducerId
List<Actor> (navigational property)
Director (navigational property)
Producer (navigational property)
SubGenre (navigational property)
MovieActors (table)
MovieId
ActorId
Now, let's assume, we are already in the data layer (so there will be not be major change-tracking on objects except for the id's). We have the data in the movie object that needs to be persisted. If it exists, it needs to be updated, if not inserted.
Movie object (movieDTO) that's sent to DataLayer.
MovieName
GenreName
SubGenreName
DirectorName
ProducerName
List ActorNames
Let's also assume there are millions of Directors/Producers/Actors/Movies (basically huge amounts of data in each table) and the names of directors/producers/actors/movie are unique for simplicity purpose. So the logic would be, if the SubGenre exists use that, otherwise create SubGenre record. Similarly look for if Genre exists, use that otherwise create it. Likewise for the director, producer and actors.
Finally, the questions are
Which of these would be faster and why? (or) Are they same even while taking millions of records into consideration?
Is it likely to change between Sql Server and Oracle? (With oracle, I am using Devart as the provider)
Approach 1:
using (MovieDBContext context = new MovieDBContext())
{
Movie movie;
movie = context.Movies.Where(a => a.Name == movieDTO.MovieName).SingleOrDefault();
if (movie == null)
movie = new Movie() { Name = movieDTO.MovieName };
var subGenre = context.SubGenres.Where(a => a.Name == movieDTO.SubGenreName).SingleOrDefault();
if (subGenre == null)
{
movie.SubGenre = new SubGenre() { Name = movieDTO.SubGenreName };
// Check if Genre exists.
var genre = context.Genres.Where(a => a.Name == movieDTO.GenreName).SingleOrDefault();
if (genre == null)
{
movie.SubGenre.Genre = new Genre() { Name = movieDTO.GenreName };
}
else
{
movie.SubGenre.Genre = genre;
}
}
else
movie.SubGenre = subGenre;
var director = context.Directors.Where(a => a.Name == movieDTO.DirectorName).SingleOrDefault();
if (director == null)
movie.Director = new Director() { Name = movieDTO.DirectorName };
else
movie.Director = director;
var producer = context.Producers.Where(a => a.Name == movieDTO.ProducerName).SingleOrDefault();
if (producer == null)
movie.Producer = new Producer() { Name = movieDTO.ProducerName };
else
movie.Producer = producer;
// I am skipping the logic of deleting all the actors if the movie is existing.
foreach (var name in movieDTO.Actors)
{
var actor = context.Actors.Where(a => a.Name == name).SingleOrDefault();
if (actor == null)
movie.Actors.Add(new Actor() { Name = name });
else
movie.Actors.Add(actor);
}
// Finally save changes. All the non-existing entities are added at once.
// EF is keeping track if the entity exists or not.
context.SaveChanges();
}
Approach 2:
using (MovieDBContext context = new MovieDBContext())
{
var genre = context.Genres.Where(a => a.Name == movieDTO.GenreName).SingleOrDefault();
if (genre == null)
{
genre = new Genre() { Name = movieDTO.GenreName };
context.Genres.Add(genre); // genre.Id is populated with the new id.
context.SaveChanges();
}
var subGenre = context.SubGenre.Where(a => a.Name == movieDTO.SubGenreName).SingleOrDefault();
if (subGenre == null)
{
subGenre = new SubGenre() { Name = movieDTO.SubGenreName };
context.SubGenres.Add(subGenre); // subGenre.Id is populated with the new id.
context.SaveChanges();
}
var director = context.Directors.Where(a => a.Name == movieDTO.DirectorName).SingleOrDefault();
if (director == null)
{
director = new Director() { Name = movieDTO.DirectorName };
context.Directors.Add(director); // director.Id is populated with the new id.
context.SaveChanges();
}
var producer = context.Producers.Where(a => a.Name == movieDTO.ProducerName).SingleOrDefault();
if (producer == null)
{
producer = new Producer() { Name = movieDTO.ProducerName };
context.Producers.Add(producer); // director.Id is populated with the new id.
context.SaveChanges();
}
// Similarly for actors, add them if they don't exist.
foreach (var name in movieDTO.Actors)
{
var actor = new Actor() { Name = movieDTO.name };
context.Actors.Add(actor);
context.SaveChanges();
}
// Lastly movie.
Movie movie = context.Movies.Where(a => a.Name == movieDTO.MovieName).SingleOrDefault();
if (movie == null)
{
movie = new Movie() { Name = movieDTO.MovieName };
}
// This works for update as well.
// The id's are added/updated instead of actual entities.
movie.DirectorId = director.Id;
movie.SubGenreId = subGenre.Id;
movie.ProducerId = producer.Id;
// I am skipping the logic of deleting all the actors if the movie is existing.
foreach (var name in movieDTO.Actors)
{
var actor = context.Actors.Where(a => a.Name == name).SingleOrDefault(); // Actors always exist now because we added them above.
movie.Actors.Add(actor);
}
// Finally save changes. Here only Movie object is saved as all other objects are saved earlier.
context.SaveChanges();
}