I'm tyring to add an entity to DbSet and save changes in context in Entity Framework Code First. I have University DbSet:
public DbSet<University> Universities { get; set; }
I have 5 class in use - Term; Faculty; Department; University; and Student. Their constructor methods are like:
public Student()
{
Universities = new List<University>();
User = new DATABASE.User();
Terms = new List<Term>();
}
public University()
{
UniversityFaculties = new List<Faculty>();
UniversityDepartments = new List<Department>();
Students = new List<Student>();
}
public Term()
{
Students = new List<Student>();
Lessons = new List<Lesson>();
university = new University();
}
public Faculty()
{
University = new University();
Students = new List<Student>();
}
public Department()
{
University = new University();
Students = new List<Student>();
}
I get a Student entity from previous forms,(student which logged in the program). When I'm trying to add entities that filled by comboboxes and textboxes, I realized that Universities DbSet is filling with the real entity that I meant to add but is also filling with an empty University entity. My Id values are not incrementing automatically so I'm incrementing them manually. My code:
Create Faculty Entity:
Faculty f = entities.Faculties.OrderByDescending(o => o.FacultyId).FirstOrDefault();
int newFacultyId = (f == null ? 1 : f.FacultyId + 1);
var faculty = new Faculty();
faculty.FacultyId = newFacultyId;
faculty.FacultyName = FacultyComboBox2.SelectedItem.ToString();
Create Department Entity:
Department d = entities.Departments.OrderByDescending(o =>o.DepartmentId).FirstOrDefault();
int newDepartmentId = (d == null ? 1 : d.DepartmentId + 1);
var department = new Department();
department.DepartmentId = newDepartmentId;
department.DepartmentName = DepartmentComboBox3.SelectedItem.ToString();
Create University Entity:
University uni = entities.Universities.OrderByDescending(o => o.UniversityId).FirstOrDefault();
int newUniId = (uni == null ? 1 : uni.UniversityId + 1);
var university = new University();
university.UniversityId = newUniId;
university.UniversityName = UniversityComboBox1.Text;
university.UniversityFaculties.Add(faculty);
university.UniversityDepartments.Add(department);
university.Students.Add(student);
student.Universities.Add(university);
faculty.University = university;
department.University = university;
faculty.Students.Add(student);
department.Students.Add(student);
Create Term Entity:
Term t = entities.Terms.OrderByDescending(o => o.TermId).FirstOrDefault();
int newTermId = (t == null ? 1 : t.TermId + 1);
var term = new Term();
term.TermId = newTermId;
term.TermTerm = int.Parse(TermComboBox1.Text);
term.TermYear = int.Parse(YearComboBox1.Text);
term.Students.Add(student);
student.Terms.Add(term);
term.university = university;
And my DbSet Additions:
entities.Faculties.Add(faculty);
entities.Departments.Add(department);
entities.Universities.Add(university);
entities.Terms.Add(term);
entities.SaveChanges();
But I got validation failed error so I used try catch blocks to analyze exception. I realized that my Universities DbSet contains an empty University entity. When I'm debugging, I realized that they're coming from entity creations that I make for the purpose of determining the ID of the entities. I tried to reach the null values in Universities DbSet and remove them but I couldn't do it in that way.The screenshot is below:
Other DbSet's don't have that problem.I don't know what to do to get rid of that problem.Any helps would be appreciate.Thanks.
The answer possibly is in that you are adding the university once directly into the DbSet but then again through the relationship between faculties and the university.
When you add an entity to a context through DbSet.Add(entity) the entire graph of that entity gets attached to the underlying context with it and all entities in that graph are set to the Added state, except if the root entity is already Added. So then when you associate your Faculty, Term and Department with the University and then add all four to the context you get more than the one University you expected. I can't be 100% sure of what is going on because as you say you are manually generating the Id values and you haven't disclosed your entire model.
There are three ways I see you can solve this:
1: Change the relationship assignment to use the UniversityId on Faculty, Term and Department, or remove the assignment altogether and rely on the relationships as assigned by the collections:
uni = entities.Universities.OrderByDescending(o => o.UniversityId).FirstOrDefault();
int newUniId = (uni == null ? 1 : uni.UniversityId + 1);
var university = new University();
university.UniversityId = newUniId;
university.UniversityName = UniversityComboBox1.Text;
// these lines are sufficient to build the relationships between entities, you don't have to set both sides
university.UniversityFaculties.Add(faculty);
university.UniversityDepartments.Add(department);
university.Students.Add(student);
student.Universities.Add(university);
// use foreign key properties to build the relationship without having large graphs when adding to context
faculty.UniversityId = newUniId;
department.UniversityId = newUniId;
faculty.Students.Add(student);
department.Students.Add(student);
2: Don't add the university to the context
entities.Faculties.Add(faculty);
entities.Departments.Add(department);
// entities.Universities.Add(university);
entities.Terms.Add(term);
entities.SaveChanges();
3: The concept of an Aggregate Root in Domain Driven Design would deal to this by only adding the root entity through which the related entities are accessed.
Related
I have a repository that loads my entity (City) and its related data (PointsOfInterest)
public City GetCity(int cityId, bool includePointsOfInterest)
{
var city = _context.Cities.SingleOrDefault(x => x.Id == cityId);
if (includePointsOfInterest)
{
_context.Entry(city)
.Collection(x => x.PointsOfInterest)
.Load();
}
return city;
}
To test this method, I decided to go with SQLLite InMemory, as I could test the Eager load functionality.
Setup of the context:
SqliteConnection connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
var options = new DbContextOptionsBuilder<CityInfoContext>()
.UseSqlite(connection)
.Options;
var context = new CityInfoContext(options);
var cities = new List<City>()
{
new City()
{
Id = 1,
Name = "New York City",
Description = "The one with that big park.",
PointsOfInterest = new List<PointOfInterest>()
{
new PointOfInterest()
{
Id = 1,
Name = "Central Park",
Description = "The most visited urban park in the United States."
},
new PointOfInterest()
{
Id = 2,
Name = "Empire State Building",
Description = "A 102-story skyscraper located in Midtown Manhattan."
}
}
}
}
context.Cities.AddRange(cities);
context.SaveChanges();
But looks like the SQLite always load its related data, which makes sense, since it is already in memory. But since it is supposed to simulate a Relational Database, is there a way to make it not load the related data automatically?
If not, how can I effectively test it? Should I go for in disk SQLite for my repository tests?
(I'm using EF in memory provider to test code that depends on the Repository)
Are you testing against the same instance of your context and the same DbSet collection you just inserted? If you are, the objects are there because you've just inserted them a step earlier, still in the graph.
Try querying your context like:
var c1 = context.Set<City>().AsQueryable().FirstOrDefault();
// assuming you have initialized the PointsOfInterest coll. in City.cs
Assert.Empty(c1.PointsOfInterest);
You can now apply the same access as _context.Set<City>().AsQueryable() in your repository.
I am writing many (20+) parent child datasets to the database, and EF is requiring me to savechanges between each set, without which it complains about not being able to figure out the primary key. Can the data be flushed to the SQL Server so that EF can get the primary keys back from the identities, with the SaveChanges being sent at the end of writing all of the changes?
foreach (var itemCount in itemCounts)
{
var addItemTracking = new ItemTracking
{
availabilityStatusID = availabilityStatusId,
itemBatchId = itemCount.ItemBatchId,
locationID = locationId,
serialNumber = serialNumber,
trackingQuantityOnHand = itemCount.CycleQuantity
};
_context.ItemTrackings.Add(addItemTracking);
_context.SaveChanges();
var addInventoryTransaction = new InventoryTransaction
{
activityHistoryID = newInventoryTransaction.activityHistoryID,
itemTrackingID = addItemTracking.ItemTrackingID,
personID = newInventoryTransaction.personID,
usageTransactionTypeId = newInventoryTransaction.usageTransactionTypeId,
transactionDate = newInventoryTransaction.transactionDate,
usageQuantity = usageMultiplier * itemCount.CycleQuantity
};
_context.InventoryTransactions.Add(addInventoryTransaction);
_context.SaveChanges();
}
I would like to do my SaveChanges just once at the end of the big loop.
You don`t need to save changes every time if you use objects refernces to newly created objects not IDs:
var addItemTracking = new ItemTracking
{
...
}
_context.ItemTrackings.Add(addItemTracking);
var addInventoryTransaction = new InventoryTransaction
{
itemTracking = addItemTracking,
...
};
_context.InventoryTransactions.Add(addInventoryTransaction);
...
_context.SaveChanges();
Since they're all new items rather than
itemTrackingID = addItemTracking.ItemTrackingID,
you could go with
addItemTracking.InventoryTransaction = addInventoryTransaction;
(or whatever the associated navigation property is) and pull the _context.SaveChanges() out of the loop entirely. Entity Framework is very good at inserting object graphs when everything is new. When saving object graphs containing both new and existing items setting the associated id is always safer.
How about:
var trackingItems = itemCounts
.Select(i => new ItemTracking
{
availabilityStatusID = availabilityStatusId,
itemBatchId = i.ItemBatchId,
locationID = locationId,
serialNumber = serialNumber,
trackingQuantityOnHand = i.CycleQuantity
});
_context.ItemTrackings.AddRange(trackingItems);
_context.SaveChanges();
var inventoryTransactions = trackingItems
.Select(t => new InventoryTransaction
{
activityHistoryID = newInventoryTransaction.activityHistoryID,
itemTrackingID = t.ItemTrackingID,
personID = newInventoryTransaction.personID,
usageTransactionTypeId = newInventoryTransaction.usageTransactionTypeId,
transactionDate = newInventoryTransaction.transactionDate,
usageQuantity = usageMultiplier * t.trackingQuantityOnHand
});
_context.InventoryTransactions.AddRange(inventoryTransactions);
_context.SaveChanges();
However I haven't worked with EF for quite a while and above code is written in notepad so I cannot vouch for it
this question has been asked several times on stackoverflow, and I have read through atleast half a dozen of those, but I can't get my head around a simple many to many linq join query. This is my database EDMX
I'm simply trying to populate a WPF datagrid with the list of students, along with each student's subjects.
Now I know we can simply use navigation properties , instead of doing a join, but I have been unable to get the right result
so a ( either C#/VB.net )query like
var listOfStudents= // get a list with all students , along with each student's subjects
Thank you for any help, its such a simple query but I'm kind of stuck
var listOfStudents = db.Student.Select(x => new { Id = x.Id, Name = x.StudentName, Subjects = x.StudentsSubjects.Select(y => y.Subject) });
If you remove Id field from the table StudentsSubject then remove this table from the model and update your model, EF will automatically convert this table in two navigational properties Subjects and Students for Student and Subject entities respectively.
If you must leave StudentsSubject table schema intact you can use Include() method to get both students and their subjects:
var students = dbContext.Students.Include("StudentsSubject.Subject")
With 'normal' navigation properties you could write the following:
var students = dbContext.Students.Include("Subjects")
Sometimes you need to assembly large graphs then Include() and lazy loading can affect performance. There is a small trick for this case:
// switch off for performance
DbContext.Configuration.AutodetectChangesEnabled = false;
// load root entities
var roots = dbContext.Parents.Where( root => %root_condition%).ToList();
// load entities one level lower
dbContext.DependentEntities.Where( dependent => dependent.Root%root_condition% && %dependent_condition%).ToList();
// detect changes
dbContext.ChangeTracker.DetectChanges();
// enable changes detection.
DbContext.Configuration.AutodetectChangesEnabled = true;
Now root.Dependents collections are filled for roots.
Here is the tradeoff between join redundancy (include or join) and several db requests along with the increasing complexity of requests.
With 'includes' data for joined nodes is duplicated so a chain of includes can produce enormous traffic from DB to client.
With the second approach each level requires filtering conditions of all upper levels in Where() and EF generates the query with N-1 joins for the N-th level, but there is no place for redundancy.
As far as I know EF now works fine with Contains() and conditions for parent nodes can be substituted with Contains():
// load root entities
var roots = dbContext.Parents.Where( root => %root_condition%).ToList();
var rootIds = new List<int>( roots.Select( root => root.Id));
// load entities one level lower
dbContext.DependentEntities.Where( dependent => %dependent_condition% && rootIds.Contains( dependent.RootId)).ToList();
A regular LINQ join should do the trick. Here's a reduced test case:
Public Class Student
Public Property Id As String
Public Property StudentName As String
Public Property GPA As String
End Class
Public Class StudentsSubject
Public Property SubjectId As String
Public Property StudentId As String
Public Property Id As String
End Class
Public Class Subject
Public Property Id As String
Public Property SubjectName As String
End Class
Sub Main()
Dim students As New List(Of Student)
students.Add(New Student With {.Id = "1", .GPA = "GPA1", .StudentName = "John"})
students.Add(New Student With {.Id = "2", .GPA = "GPA2", .StudentName = "Peter"})
Dim subjects As New List(Of Subject)
subjects.Add(New Subject With {.Id = "100", .SubjectName = "Maths"})
subjects.Add(New Subject With {.Id = "200", .SubjectName = "Physics"})
Dim studentsSubject As New List(Of StudentsSubject)
studentsSubject.Add(New StudentsSubject With {.Id = "10", .StudentId = "1", .SubjectId = "100"})
studentsSubject.Add(New StudentsSubject With {.Id = "20", .StudentId = "1", .SubjectId = "200"})
studentsSubject.Add(New StudentsSubject With {.Id = "30", .StudentId = "2", .SubjectId = "100"})
Dim listOfStudents = From st In students
Join ss In studentsSubject On ss.StudentId Equals st.Id
Join sb In subjects On ss.SubjectId Equals sb.Id
Select st.StudentName, st.GPA, sb.SubjectName
End Sub
In Entity Framework 4.2 I have a Trips entity that can have 0..* PlacesOfInterest and 0..* Photos. Places of Interest has 1 Trip and 0..* Photos. Photos have 1 Trip and 0..1 Places of Interest.
When I try to add a Photo, I use this method:
public static Guid Create(string tripId, Model.Photo instance)
{
var context = new Model.POCOTripContext();
var cleanPhoto = new Model.Photo();
cleanPhoto.Id = Guid.NewGuid();
cleanPhoto.Name = instance.Name;
cleanPhoto.URL = instance.URL;
//Relate the POI
cleanPhoto.PlaceOfInterest = Library.PlaceOfInterest.Get(instance.PlaceOfInterestId);
context.PlacesOfInterest.Attach(cleanPhoto.PlaceOfInterest);
//Relate the trip
cleanPhoto.Trip = Library.Trip.Get(new Guid(tripId));
context.Trips.Attach(cleanPhoto.Trip);
//Add the photo
context.Photos.AddObject(cleanPhoto);
context.SaveChanges();
return cleanPhoto.Id;
}
When I test this, I get the following when the Trip is attached:
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
The Trip does appear in the context object, but the PlacesOfInterest does too before the Attach statement. I don't understand how this works, can someone clarify?
EDIT: Here are the POI and Trip Getters
public static Model.Trip Get(Guid tripId)
{
using (Model.POCOTripContext context = new Model.POCOTripContext())
{
var tripEntity = context.Trips.Include("PlacesOfInterest").Include("PlacesOfInterest.PoiAttributes").Include("Photos").FirstOrDefault(c => c.Id == tripId) ?? new Model.Trip();
return tripEntity;
}
}
public static Model.PlaceOfInterest Get(Guid poiId)
{
using (Model.POCOTripContext context = new Model.POCOTripContext())
{
var poiEntity = context.PlacesOfInterest.Include("PoiAttributes").FirstOrDefault(c => c.Id == poiId) ?? new Model.PlaceOfInterest();
return poiEntity;
}
}
Thanks
S
This...
context.Trips.Include("PlacesOfInterest")....
...will load the PlacesOfInterest with the trip. When you attach the trip to the other context trip.PlacesOfInterest get attached as well. Because you already have attached a PlaceOfInterest before (which has an Id of a PlaceOfInterest in the collection) you are attaching two objects of the same type with the same key. This causes the exception.
You can actually simplify your code: You don't need to load the entities because you have their primary key. Then you can just create new instances with that key and attach it:
cleanPhoto.PlaceOfInterest = new PlaceOfInterest
{ Id = instance.PlaceOfInterestId };
context.PlacesOfInterest.Attach(cleanPhoto.PlaceOfInterest);
cleanPhoto.Trip = new Trip { Id = new Guid(tripId) };
context.Trips.Attach(cleanPhoto.Trip);
I create some items from a class X.
I add them to de base, do SaveChanges and all this...
The class Y has a relationship many-to-many with X.
Using another Context, I create a Y instance, putting into the collection of X the elements I've created.
I add Y to Y entity set, it is fine.
When I do Context.SaveChanges(), I get:
A value shared across entities or associations is generated in more than one location. Check that mapping does not split an EntityKey to multiple server-generated columns.
Have you ever seen this error?
EDIT: at the beginning, I've put 1-to-many, after I've noticed it is in fact many-to-many.
EDIT 2: showing the way this is being done. Unlike many people while using .net, we do use layers (business, data...). This is the test case:
[TestMethod]
public void WorksWithAreaCategories()
{
using (new TransactionScope())
{
//arrange
var context = ContextFactory.Create();
var categoryBusiness = new CategoryBusiness(context);
var category = new Category
{
Name = "TestCategory###"
};
categoryBusiness.Add(category);
var areaBusiness = new AreaBusiness(context);
var area = new Area
{
Name = "TestArea###",
Description = "TestAreaDescription###",
Categories = new List<Category> {category}
};
//act
areaBusiness.Add(area);
//assert
var areaFromDb = areaBusiness.FindById(area.AreaID);
Assert.IsNotNull(areaFromDb.Categories);
Assert.IsTrue(areaFromDb.Categories.Count > 0);
Assert.IsTrue(areaFromDb.Categories.Any(c => c.CategoryID == category.CategoryID));
}
}
They share the same context. The business layers call SaveChanges in the end of each Add.
How I resolved it:
After adding both of them, I established the relationship with Update.
[TestMethod]
public void WorksWithAreaCategories()
{
using (new TransactionScope())
{
//arrange
var context = ContextFactory.Create();
var categoryBusiness = new CategoryBusiness(context);
var category = new Category
{
Name = "TestCategory###"
};
categoryBusiness.Add(category);
var areaBusiness = new AreaBusiness(context);
var area = new Area
{
Name = "TestArea###",
Description = "TestAreaDescription###",
};
areaBusiness.Add(area);
//act
area.Categories = new List<Category> { category };
areaBusiness.Update(area);
//assert
var areaFromDb = areaBusiness.FindById(area.AreaID);
Assert.IsNotNull(areaFromDb.Categories);
Assert.IsTrue(areaFromDb.Categories.Count > 0);
Assert.IsTrue(areaFromDb.Categories.Any(c => c.CategoryID == category.CategoryID));
}
}
When the error says "more than one location", it really means more than one context. Data "belongs" in a specific context and you get problems if you try to move it between contexts.
There are 2 ways to fix it:
Do everything in the same context
When you create the Y instance, read the X elements from disk, so that they are in the same context, before adding the X elements to Y.
EDIT based on comment:
Try using a single save changes.