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);
Related
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.
Is it possible to update objects with Entity Framework, without grabbing them first?
Example: Here, I have a function that provides a Primary Key to locate the objects, pulls them, then updates them. I would like to eliminate having to pull the objects first, and simply run an UPDATE query. Removing the need for the SELECT query being generated.
public async Task<int> UpdateChecks(long? acctId, string payorname, string checkaccountnumber, string checkroutingnumber, string checkaccounttype)
{
using (var max = new Max(_max.ConnectionString))
{
var payments = await
max.payments.Where(
w =>
w.maindatabaseid == acctId && (w.paymentstatus == "PENDING" || w.paymentstatus == "HOLD")).ToListAsync();
payments.AsParallel().ForAll(payment =>
{
payment.payorname = payorname;
payment.checkaccountnumber = checkaccountnumber;
payment.checkroutingnumber = checkroutingnumber;
payment.checkaccounttype = checkaccounttype;
payment.paymentmethod = "CHECK";
payment.paymentstatus = "HOLD";
});
await max.SaveChangesAsync();
return payments.Count;
}
}
You can use the Attach() command to attach an entity you already know exists and then call SaveChanges() will will call the appropriate update method. Here is some sample code from the MSDN article on the topic:
on the subject:
var existingBlog = new Blog { BlogId = 1, Name = "ADO.NET Blog" };
using (var context = new BloggingContext())
{
context.Entry(existingBlog).State = EntityState.Unchanged;
// Do some more work...
context.SaveChanges();
}
Note that this is general EF logic, not related to any specific database implementation.
I have three classes, Fish (which contains two properties of type Chips and MushyPeas respectively), MushyPeas (which contains a property of type Chips) and Chips (which has a Name property).
I am running the following piece of hypothetical code:
int chipsId;
using (var db = new FishContext())
{
var creationChips = new Chips() { Name = "A portion of chips" };
db.Chips.Add(creationChips);
db.SaveChanges();
chipsId = creationChips.ChipsId;
}
Chips retrievedChips1;
using (var db = new FishContext())
{
retrievedChips1 = db.Chips.Where(x => x.ChipsId == chipsId).ToList()[0];
}
Chips retrievedChips2;
using (var db = new FishContext())
{
retrievedChips2 = db.Chips.Where(x => x.ChipsId == chipsId).ToList()[0];
}
using (var db = new FishContext())
{
db.Chips.Attach(retrievedChips1);
db.Chips.Attach(retrievedChips2);
var mushyPeas = new MushyPeas() { Chips = retrievedChips2 };
var fish = new Fish() { Chips = retrievedChips1, MushyPeas = mushyPeas };
db.Fish.Add(fish);
db.ChangeTracker.DetectChanges();
db.SaveChanges();
}
This is to simulate a situation in my real app, in which EF objects (which may actually represent the same database record) are loaded from a variety of different DbContexts and then added to an object tree in another DbContext.
If I don't call the two db.Chips.Attach lines, then brand new Chips entities are created when the Fish object is saved to the database, and assigned new IDs.
Calling db.Chips.Attach solves this issue for one of the retrieved obejcts, but the second Attach call fails with the exception "An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key."
What is the best way to achieve what I want to achieve here?
As a grizzled EF vet, I've come to the conclusion that it's best to avoid using Attach in many cases.
The exception "An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key" is usually misleading since the object that you're trying to attach isn't actually attached to the data context. What happens when you attach an object is it recursively attaches any entities that it references. So, if you attach an entity to the data context, and then attach another entity that references any entity that was implicitly attached previously, you will get this error. The solution is pretty simple:
using (var db = new FishContext())
{
var chips1 = db.Chips.Find(retrievedChips1.Id);
var chips2 = db.Chips.Find(retrievedChips2.Id);
var mushyPeas = new MushyPeas() { Chips = chips2 };
var fish = new Fish() { Chips = chips1, MushyPeas = mushyPeas };
db.Fish.Add(fish);
db.ChangeTracker.DetectChanges();
db.SaveChanges();
}
This guarantees that both entities will be attached to the data context without any sort of ObjectStateManager issues.
You could query the Local collection to check if an entity with the same key is already attached and if yes, use the attached entity:
using (var db = new FishContext())
{
var attachedChips1 = db.Chips.Local
.SingleOrDefault(c => c.ChipsId == retrievedChips1.ChipsId);
if (attachedChips1 == null)
{
db.Chips.Attach(retrievedChips1);
attachedChips1 = retrievedChips1;
}
var attachedChips2 = db.Chips.Local
.SingleOrDefault(c => c.ChipsId == retrievedChips2.ChipsId);
if (attachedChips2 == null)
{
db.Chips.Attach(retrievedChips2);
attachedChips2 = retrievedChips2;
}
var mushyPeas = new MushyPeas() { Chips = attachedChips2 };
var fish = new Fish() { Chips = attachedChips1, MushyPeas = mushyPeas };
//...
}
(The first check doesn't make sense in this simple example because a new context is empty with nothing attached to it. But you get the idea...)
However, in the case that you also want to update the related entities (for example by setting the state to Modified after attaching) it would be a problem if retrievedChips1 and retrievedChips2 have (except the key value) different property values. You had to decide somehow which is the "correct one". But that would be business logic. You just have to hand over one of them to EF and only one. In your scenario it wouldn't matter which one you use because you are only creating a relationship and for this EF will only care about the key value.
Side note: Instead of ...ToList()[0] the more natural way would be ...First() (or Single() in this case because you are querying the key).
So, in a desperate attempt to wrangle EntityFramework into being usable. I am here..
private MyEntity Update(MyEntity orig)
{
//need a fresh copy so we can attach without adding timestamps
//to every table....
MyEntity ent;
using (var db = new DataContext())
{
ent = db.MyEntities.Single(x => x.Id == orig.Id);
}
//fill a new one with the values of the one we want to save
var cpy = new Payment()
{
//pk
ID = orig.ID,
//foerign key
MethodId = orig.MethodId,
//other fields
Information = orig.Information,
Amount = orig.Amount,
Approved = orig.Approved,
AwardedPoints = orig.AwardedPoints,
DateReceived = orig.DateReceived
};
//attach it
_ctx.MyEntities.Attach(cpy, ent);
//submit the changes
_ctx.SubmitChanges();
}
_ctx is an instance variable for the repository this method is in.
The problem is that when I call SubmitChanges, the value of MethodId in the newly attached copy is sent to the server as 0, when it is in fact not zero if I print it out after the attach but before the submit. I am almost certain that is related to the fact that the field is a foreign key, but I still do not see why Linq would arbitrarily set it to zero when it has a valid value that meets the requirements of the constraint on the foreign key.
What am I missing here?
You should probably set Method = orig.Method, but I can't see your dbml, of course.
I think you need to attach the foreign key reference
var cpy = new Payment()
{
//pk
ID = orig.ID,
//other fields
Information = orig.Information,
Amount = orig.Amount,
Approved = orig.Approved,
AwardedPoints = orig.AwardedPoints,
DateReceived = orig.DateReceived
};
//create stub entity for the Method and Add it.
var method = new Method{MethodId=orig.MethodId)
_ctx.AttachTo("Methods", method);
cpy.Methods.Add(method);
//attach it
_ctx.MyEntities.Attach(cpy, o);
//submit the changes
_ctx.SubmitChanges();
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.