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();
}
}
Related
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.
using the following code:
using (GagaShaggyContext db = new GagaShaggyContext())
{
ItemModel itemToChange = null;
itemToChange = (from i in db.Items
where i.ItemID == checkoutItem.Item.ItemID
select i).FirstOrDefault();
itemToChange.FrontFeature = false;
db.SaveChanges();
}
The model is saving back to the database with a brand new ID, which I want to save changes to the original database entry. Is there any reason for this to happen?
Thanks
Edit
On breakpoint analysis adding the receipt item before hand is adding a different ItemID not that I can see why:
using (var db = new GagaShaggyContext())
{
db.Receipts.Add(rec);
db.SaveChanges();
}
using (var db = new GagaShaggyContext())
{
var ItemToUse = (from i in db.ItemModels
where i.ItemModelID == checkoutItem.Item.ItemModelID
select i).FirstOrDefault();
rec.ItemModel = ItemToUse;
db.Receipts.Add(rec);
db.SaveChanges();
}
This fixed it. We need to actually retrieve the relevant Item and put it inside the Receipt (rec) then we could add it, now that Entity Framework recognizes the relationship between these two.
I guess you are trying to Insert into DB
using (GagaShaggyContext db = new GagaShaggyContext())
{
ItemModel itemToChange = new ItemModel();
itemToChange = (from i in db.Items
where i.ItemID == checkoutItem.Item.ItemID
select i).FirstOrDefault();
if(itemToChange !=null)
{
itemToChange.FrontFeature = false;
db .Items.Add(itemToChange);
db.SaveChanges();
}
}
If you are trying to Update the record than
using (GagaShaggyContext db = new GagaShaggyContext())
{
ItemModel itemToChange = new ItemModel();
itemToChange = (from i in db.Items
where i.ItemID == checkoutItem.Item.ItemID
select i).FirstOrDefault();
if(itemToChange !=null)
{
itemToChange.FrontFeature = false;
objDBContext.Entry(itemToChange).State = EntityState.Modified;
objDBContext.SaveChanges();
}
}
My class
class student
{
public string studentid { get; set; }
public string groupid { get; set; }//Group id
}
My List
List<student> pupils = new List<student>();
here I select students with no group id
var studentsWithNoGroupId = from student in pupils
where student.groupid =="00"
select student;
I want to go through a for loop which runs number of times equal to studentsWithNoGroupId.Count and assign the group ids with some values I have. (Just showing how to assign each object will be enough).
How to do that?
Or do I have to change my linq for that?
Please somebody help me.
pupils.Where(p => p.groupid == "00")
.ToList()
.ForEach(
p => {
p.groupid = "whatever";
});
EDIT(after Hatsjoem hint, even more simple):
pupils.FindAll(p => p.groupid == "00")
.ForEach(p => {
p.groupid = "whatever";
});
You mean something like this?
var studentsWithNoGroupId =
from student in pupils
where student.groupid =="00"
select student;
foreach(var student in studentsWithNoGroupId)
{
student.groupid = "x";
}
Your line:
var studentsWithNoGroupId = from student in pupils
where student.groupid =="00"
select student;
creates a Linq query. This query does not execute immediately. To execute it, try the following:
var studentsWithNoGroupIdQuery = from student in pupils
where student.groupid =="00"
select student;
var studentsWithNoGroupId = studentsWithNoGroupIdQuery.ToList();
foreach (var student in studentsWithNoGroupId)
{
student.groupid = // Your logic here.
}
// Save pupils back to the database here.
The ToList() causes the query to execute and store the results in a List<student>. It is important to execute the query before the iteration, because you are changing the same variable groupid on which the query is being filtered.
I don't know if this is what you are looking for but I will give it a try:
var yourPupils = pupils.Where(p => p.groupid == "00")
.Select(s => new student() {
studentid = s.studentid,
groupid = "yourCustomAssignedId"
}).ToList();
For-loop version:
var numberOfPupils = pupils.Count();
for (int i = 0; i < numberOfPupils; i++)
{
if (pupils[i].groupid == "00")
{
pupils[i].groupid = "yourCustomAssignedId";
}
}
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();
}
TestEntities context = new TestEntities();
var item = context.TestTables.Single(s => s.ID == 1);//item.Name is "Giorgi"
item.Name = "Hello";
var item1 = context.TestTables.Single(s => s.ID == 1);
Console.WriteLine(item1.Name);
context.SaveChanges();
What do you expect to be written? Hello is written! Why?
TestEntities context = new TestEntities();
var item = context.TestTables.Single(s => s.ID == 1);//item.Name is "Giorgi"
item.Name = "Hello";
var item1 = context.TestTables.Single(s => s.ID == 1);
context.SaveChanges();
Console.WriteLine(item1.Name);
What do you expect to be written? Hello is written! Why?
* there are two different questions*
Your changes are registered in the context, although not saved to the database until you call SaveChanges. If you need the original value, you could either open a new context, reload the entity or inspect the change tracker for changes.
Added tests:
[Fact]
public void TestUsingNewContext()
{
using (var context = new TestEntities())
{
var item = context.TestTables.Single(s => s.ID == 1);
item.Name = "Hello";
using (var newContext = new TestEntities())
{
var item1 = newContext.TestTables.Single(s => s.ID == 1);
Assert.Equal("Giorgi", item1.Name);
}
}
}
[Fact]
public void TestUsingReload()
{
using (var context = new TestEntities())
{
var item = context.TestTables.Single(s => s.ID == 1);
item.Name = "Hello";
context.Entry(item).Reload();
var item1 = context.TestTables.Single(s => s.ID == 1);
Assert.Equal("Giorgi", item1.Name);
}
}
[Fact]
public void TestUsingChangeTracker()
{
using (var context = new TestEntities())
{
var item = context.TestTables.Single(s => s.ID == 1);
item.Name = "Hello";
foreach (var entry in context.ChangeTracker.Entries<TestTable>().Where(e => e.State == EntityState.Modified))
{
entry.CurrentValues.SetValues(entry.OriginalValues);
}
var item1 = context.TestTables.Single(s => s.ID == 1);
Assert.Equal("Giorgi", item1.Name);
}
}
The "context" lives in memory, so when you change stuff in context they are not changed in the database, but they are changed in the "context" (memory), only when you call context.SaveChanges() you actually persisting your updats/changes to the database.
Why would you need SaveChanges() if not to actually save the changes..
In both cases item and item1 linked with same entity in context. EntityFramework store entity in context for you. I supose when you select it again it returns cached copy. When you change some entity in context anyone who work with this context see this changes. Changes goes to database only after comit.
using(TestEntities context = new TestEntities())
{
var item = context.TestTables.Single(s => s.ID == 1);//item.Name is "Giorgi"
item.Name = "Hello";
using(TestEntities context = new TestEntities())
{
var item1 = context.TestTables.Single(s => s.ID == 1);
Console.WriteLine(item1.Name); // you will get old value here
}
}
Try to read about it in msdn. Bacause to much stones underwater here. For example: what will be if some one change your entity in db and you try to commit your changes after?
http://msdn.microsoft.com/en-us/data/ee712907
http://msdn.microsoft.com/en-us/data/jj592904.aspx