I have the following code:
Get contact
public Contact LoadContactFromId(int contactId)
{
using(var ctx = new ContactContext())
{
var contact = ctx.Contacts.AsNoTracking().First(c => c.ContactId == contactId);
return contact;
}
}
Update Contact
public void UpdateExistingContact(Contact updatedContact)
{
using(var ctx = new ContactContext())
{
ctx.Contacts.Attach(updatedContact);
ctx.Entry(updatedContact).State = EntityState.Modified;
foreach (var item in updatedContact.ContactPoints)
{
ctx.Entry(item).State = (item.ContactPointId == 0) ? EntityState.Added : EntityState.Modified;
}
ctx.SaveChanges();
}
}
I am currently working with ASP.NET webforms and because of its stateless model, I can't keep working within the DbContext. Because of this, I end up basically recreating the entire Contact entity from the POST data and then update it.
This seems like a horrible way to do this because I lose conncurrncey resolution since I'm not working with the true original entity and it seems kind of cumbersome to always have to recreate the object.
Is there an easier way to do this? Am I looking at this all wrong?
Related
I set up a Generic repository using this code for update
private void AttachIfNot(TEntity entityToActive)
{
if (_dbContext.Entry(entityToActive).State == EntityState.Detached)
{
_dbSet.Attach(entityToActive);
}
}
private void UpdateEntity(TEntity entityToUpdate)
{
AttachIfNot(entityToUpdate);
_dbContext.Entry(entityToUpdate).State = EntityState.Modified;
}
It just attach the entity and set the modified state to save.
But when I use efocre ownsone to map a value object,the update entity function is not working.
I found out that it only works when I set Valueobject to modified too.
_dbContext.Entry(entityToUpdate).State = EntityState.Modified;
_dbContext.Entry(entityToUpdate.Valueobject).State = EntityState.Modified;
But It is hard for me to specify all the value objects in a Generic Repository.
This is code also has problems with one to many or other relations.
The working way is like this:
Classroom classroom = new Classroom
{
Id = 1,
Name = "b",
Students = new List<Student>
{
new Student()
{
Name = "aa",
Id = 2
}
}
};
if (_defaultDbContext.Entry(classroom).State == EntityState.Detached)
{
_defaultDbContext.Classrooms.Attach(classroom);
foreach(var stu in classroom.Students)
{
_defaultDbContext.Students.Attach(stu);
}
}
_defaultDbContext.Entry(classroom).State = EntityState.Modified;
foreach (var stu in classroom.Students)
{
_defaultDbContext.Entry(stu).State = EntityState.Modified;
}
_defaultDbContext.SaveChanges();
I found out one way is get the entity form repo then update it using automapper:
targetEntity = repo.GetById(entityId);
automapper.map(souceEntity,targetEntity);
//or
automapper.map(souceDto,targetEntity);
_dbContext.Save();
The entity comes by query, so the change will be tracked.
But I have to configure the automapper with this entity map when I want to change entity
CreateMap<EntityType, EntityType>();
I think it's not the best solution. Is there a bettere way?
DbContext.Update would be fine to fix this problem.
see:
https://www.learnentityframeworkcore.com/dbcontext/change-tracker
I'm using EF5 with MVC and POCO's and need a little help
I have an update function which is passed a disconnected POCO. The POCO has a 'navigation property' collection, eg: Provider has
public virtual ICollection<Company> Companies { get; set; }
When the Provider was loaded (and the old context closed) it had two Company objects, now it has four and I'd like to update.
I thought the code below might work but Companies is not updated (but the non-navigational properties of Provider (like string Name {get;set} are still updated ok) and there is no error
public void Update(Provider entity)
{
// Existing entity
_context.Entry(entity).State = EntityState.Modified;
if (entity.Companies.Any())
{
//try to tell EF about the companies
foreach (var company in entity.Companies)
{
//the company exists already - let the context know....
_context.Entry(company).State = EntityState.Modified;
_context.Companies.Attach(company);
}
}
}
... and later: _unitOfWork.SaveChanges();
For an insert of Provider with Companies I had successfully used:
if (entity.Companies.Any())
{
//these are not to be created - they exist -
//I want EF to add them as nav properties
foreach (var company in entity.Companies)
{
//the company exists already - let the context know....
_pvpContext.Companies.Attach(company);
}
}
// New entity
_pvpContext.Entry(entity).State = EntityState.Added;
I'm going to go and read Julia Lerman's book as EF is killing me - but I'd really appreciate any help updating 'Companies' in the meantime - Thx
Edit:
Taking #Manos' kind advice I tried:
List<Company> companies = new List<Company>();
if (entity.Companies != null && entity.Companies.Any())
{
//pull out the Companies from the POCO
companies = entity.Companies.ToList();
//remove them
entity.Companies = new Collection<Company>();
entity.Companies.Clear();
}
// pass existing entity to the context, tagged as modified
_pvpContext.Entry(entity).State = EntityState.Modified;
if (companies.Any())
{
//now re-add the companies while the context is listening. ffs.
foreach (var company in companies)
{
entity.Companies.Add(company);
}
}
If I add the Provider.Companies to the context (like in the insert) I get:
Violation of PRIMARY KEY constraint 'PK__tmp_ms_x__679519B7F943FD8D'.
Cannot insert duplicate key in object 'dbo.ProviderCompany'. The
duplicate key value is (5, 3)
which is odd as there is not composite key of (provider 5, company 3) - so maybe it's trying to add it in twice here?
If I don't pre-add the Provider.Companies I get:
at System.Data.Entity.Internal.InternalContext.SaveChanges() at
System.Data.Entity.Internal.LazyInternalContext.SaveChanges() at
System.Data.Entity.DbContext.SaveChanges()
I only have 4.1 to test, but try this as a basic logic:
public void Update(Provider entity)
{
// Existing entity
Provider contextProvider = _context.Entry(entity);
contextProvider.Companies.Clear();
foreach (var company in entity.Companies)
{
contextProvider.Companies.Add(company);
}
}
This needs a little refinement in order to only add new companies as opposed to doing a complete removal and reinstatement but it should work.
Edit in response to comment:
Try catching the exception thrown by SaveChanges() with the following:
try {
_unitOfWork.SaveChanges();
} catch (System.Data.Entity.Validation.DbEntityValidationException e) {
foreach (var k in e.EntityValidationErrors) {
foreach (var e1 in k.ValidationErrors) {
Console.WriteLine("{0} - {1}", e1.PropertyName, e1.ErrorMessage);
}
}
}
It should give you a little more information to go on.
I have a class ReportConfigurationManager which manages the CRUD operations against a UserReport entity. The two operations of interest are "Get" and "SaveUpdate". In both cases I wrap the operation in a using statement so that the DbContext is disposed at the end of the query.
Now eventually these methods will form part of a WCF service, but they may also be called internally within the service. My present difficulties are with getting a set of Unit Tests to work which call the ReportConfigurationManager directly.
I can create a new UserReport and save it (this took me a while to solve as the entity has several nested objects which already exist in the database - I needed to "Attach" each of these in turn to the context before calling Add on the UserReport in order to get it to save correctly.
My issues now are with Updates.
Despite having
context.Configuration.ProxyCreationEnabled = false;
context.Configuration.AutoDetectChangesEnabled = false;
on ALL methods which use the ReportConfigurationManager, when I came to attach a UserReport, it failed with the classic "an object with the same key already exists in the ObjectStateManager" (I thought disabling Change Tracking was meant to handle this?).
So now I have switched to using the following code which I found here
public UserReport SaveUpdateUserReport(UserReport userReport)
{
using (var context = new ReportDataEF())
{
context.Configuration.ProxyCreationEnabled = false;
context.Configuration.AutoDetectChangesEnabled = false;
if (userReport.Id > 0)
{
{
UserReport oldReport = context.UserReports.Where(ur => ur.Id == userReport.Id).FirstOrDefault();
context.Entry(oldReport).CurrentValues.SetValues(userReport);
}
}
else
{
//Need to attach everything to prevent EF trying to create duplicates in the database
context.ReportTopTypes.Attach(userReport.ReportTopType);
context.ReportWindows.Attach(userReport.ReportWindow);
context.ReportSortOptions.Attach(userReport.ReportSortOption);
foreach (var col in userReport.ReportColumnGroups)
{
context.ReportColumnGroups.Attach(col);
}
context.ReportTemplates.Attach(userReport.ReportTemplate);
//just add the new data
context.UserReports.Add(userReport);
}
context.SaveChanges();
}
return userReport;
}
My concern is that my code seems laborious - I need to get a copy of the old object before I can save the updated copy? And I'm not convinced by my Save New logic either.
So is this approach correct, or is there a better way of writing the above?
Further details of other stuff going on:
Because I'll be sending the object graphs over WCF. I've implemented Eager Loading:
public static DbQuery<ReportTemplate> IncludeAll(this DbQuery<ReportTemplate> self)
{
return self
.Include("ReportColumnGroups.ReportColumns.ReportColumnType")
.Include("ReportColumnGroups.ReportColumnType")
.Include("ReportSortOptions.ReportSortColumns.ReportColumn.ReportColumnType")
.Include("ReportSortOptions.ReportSortColumns.ReportSortType");
}
public static DbQuery<UserReport> IncludeAll(this DbQuery<UserReport> self)
{
return self
.Include("ReportTemplate")
.Include("ReportTopType")
.Include("ReportWindow")
.Include("ReportSortOption.ReportSortColumns.ReportColumn.ReportColumnType")
.Include("ReportSortOption.ReportSortColumns.ReportSortType")
.Include("ReportColumnGroups.ReportColumns.ReportColumnType")
.Include("ReportColumnGroups.ReportColumnType");
}
public static DbQuery<ReportSortOption> IncludeAll(this DbQuery<ReportSortOption> self)
{
return self
.Include("ReportSortColumns.ReportColumn.ReportColumnType")
.Include("ReportSortColumns.ReportSortType");
}
public static DbQuery<ReportColumnGroup> IncludeAll(this DbQuery<ReportColumnGroup> self)
{
return self
.Include("ReportColumn.ReportColumnType")
.Include("ReportColumnType");
}
public static DbQuery<ReportColumn> IncludeAll(this DbQuery<ReportColumn> self)
{
return self
.Include("ReportColumnType");
}
public static DbQuery<ReportSortColumn> IncludeAll(this DbQuery<ReportSortColumn> self)
{
return self
.Include("ReportColumn.ReportColumnType")
.Include("ReportSortType");
}
I have a set of static, cached data that I obtain as follows:
using (var context = new ReportDataEF())
{
context.Configuration.ProxyCreationEnabled = false;
context.Configuration.AutoDetectChangesEnabled = false;
reportConfigurationData = new ReportingMetaData()
{
WatchTypes = context.WatchTypes.ToList(),
ReportTemplates = context.ReportTemplates.IncludeAll().ToList(),
ReportTopTypes = context.ReportTopTypes.ToList(),
ReportWindows = context.ReportWindows.ToList(),
ReportSortOptions =
context.ReportSortOptions.IncludeAll().ToList()
};
}
and I retrieve UserReports as follows:
public UserReport GetUserReport(int userReportId)
{
using (var context = new ReportDataEF())
{
context.Configuration.ProxyCreationEnabled = false;
context.Configuration.AutoDetectChangesEnabled = false;
var visibleReports =
context.UserReports.IncludeAll().Where(ur => ur.Id == userReportId).FirstOrDefault();
return visibleReports;
}
}
The test I am concerned with gets an existing UserReport from the DB, Updates its ReportTemplate and ReportColumnGroups properties with objects from the static data class and then attempts to save the updated UserReport.
Using the code from Ladislav's answer, this fails when I try to attach the UserReport, presumably because one of the objects I've attached to it, already exists in the database.
Yes there is another way. First think you should know is that EF doesn't support partially attached object graphs so both Attach and Add have side effects to attach or add all entities in the graph which are not yet tracked by the context. This will simplify your insertion code a lot.
public UserReport SaveUpdateUserReport(UserReport userReport)
{
using (var context = new ReportDataEF())
{
context.Configuration.ProxyCreationEnabled = false;
context.Configuration.AutoDetectChangesEnabled = false;
// Now all entities in the graph are attached in unchanged state
context.ReportTopTypes.Attach(userReport);
if (userReport.Id > 0 &&
context.UserReports.Any(ur => ur.Id == userReport.Id))
{
context.Entry(userReport).State = EntityState.Modified;
}
else
{
context.Entry(userReport).State = EntityState.Added;
}
context.SaveChanges();
}
return userReport;
}
This is equivalent to your original code. You don't load user report again - you just check its existence in DB. This code has a lot of problems - for example if you changed any other related object it will not be persisted to database because currently its state is Unchanged. It can be even more complicated if you need to change relations.
I'm using EF4. I'm adding a series of new entities from a list of DTOs, and I'm not saving changes until after all of them are added. I'm wanting to set the IDs of the DTOs to what the new entities' IDs are. How on earth do I do this? Does EF provide a mechanism for this?
With a single entity I would do this:
public void InsertMyDto(MyDto a_dto)
{
var newEntity = new MyEntity
{
Name = a_dto.Name,
Type = a_dto.Type.ToString(),
Price = a_dto.Price
};
_dataContext.MyEntities.AddObject(newEntity);
_dataContext.SaveChanges();
a_dto.ID = newEntity.ID;
}
This works fine, but what do I do in this case?
public void InsertMyDtos(IEnumerable<MyDto> a_dtos)
{
foreach (var myDto in a_dtos)
{
var newEntity = new MyEntity
{
Name = myDto.Name,
Type = myDto.Type.ToString(),
Price = myDto.Price
};
// Does some validation logic against the database that might fail.
_dataContext.MyEntities.AddObject(newEntity);
}
_dataContext.SaveChanges();
// ???
}
I want to save all at once, because I have validation work (not shown above) that is done against the database and fails before it gets to SaveChanges, and if it fails I want it to fail as a whole transaction (i.e. rollback).
I don't think that EF can help you here. It even can't help you for a single instance which forces you to write a_dto.ID = newEntity.ID. The counterpart of this code for multiple entites is to keep track of the pairs of dtos and new entities:
public void InsertMyDtos(IEnumerable<MyDto> a_dtos)
{
Dictionary<MyDto, MyEntity> dict = new Dictionary<MyDto, MyEntity>();
foreach (var myDto in a_dtos)
{
var newEntity = new MyEntity
{
Name = myDto.Name,
Type = myDto.Type.ToString(),
Price = myDto.Price
};
dict.Add(myDto, newEntity);
// Does some validation logic against the database that might fail.
_dataContext.MyEntities.AddObject(newEntity);
}
_dataContext.SaveChanges();
foreach (var item in dict)
item.Key.ID = item.Value.ID; // Key is MyDto, Value is MyEntity
}
I am new to MVC, and am really struggling with what I seems like it should be a very common scenario. I'm using MVC2 RTM, and the Entity Framework for my model objects.
What I have working:
An edit view for a parent object that contains a collection of child objects. The form displays all the editable fields for the parent, and iterates through and displays all editable fields for all the associated child objects (in the same view). I am able to successfully handle the edit action in my controller, but run into issues when I try to bind values in the form collection to the EF model objects.
The problem:
In my controller function, when I call TryUpdateModel and pass the parent object, I get the following error:
"The EntityCollection has already been initialized. The InitializeRelatedCollection method should only be called to initialize a new EntityCollection during deserialization of an object graph."
I have seen a lot of other posts from people struggling with similar issues, but have not found a solution. Is this not possible without building a custom model binder? If anyone has a working example, I would greatly appreciate it. For some reason, I am able to iterate through the child collection and successfully execute TryUpdateModel on the child objects, but when I call it on the parent, the error above is thrown. Ideally I'd like to call it once for the parent, and have the whole object tree update from the form.
Here's the controller code:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formValues)
{
EFEntities ef = new EFEntities();
ParentObject parent = ef.ParentObjects.SingleOrDefault(p => p.ID == id);
if (ModelState.IsValid)
{
int i = 0;
foreach (child in parent.ChildObjects)
{
//this works fine
TryUpdateModel(child, "ChildObjects[" + i + "]");
i++;
}
//this blows up
if (TryUpdateModel(parent))
{
ef.SaveChanges();
return RedirectToAction("Details", new { id = parent.ID });
}
}
return View(parent);
}
Thanks for this question, even though it wasn't answered it gave me my answer. The best thing I can find to do is this (using your example):
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formValues)
{
EFEntities ef = new EFEntities();
ParentObject parent = ef.ParentObjects.SingleOrDefault(p => p.ID == id);
if (ModelState.IsValid)
{
int i = 0;
foreach (child in parent.ChildObjects)
{
//this works fine
TryUpdateModel(child, "ChildObjects[" + i + "]");
i++;
}
//exclude the collections and it won't blow up...
if (TryUpdateModel(parent, "Parent", null, new string[] {"ChildObjects"}))
{
ef.SaveChanges();
return RedirectToAction("Details", new { id = parent.ID });
}
}
return View(parent);
}
Ultimately I found a more elegant solution, but never came back to post it. Here it is - sorry for the delay:
if (ModelState.IsValid)
{
if (TryUpdateModel(parent, new[] "prop1", "prop2", "prop3" })) //specify parent-only properties to include
{
if (TryUpdateModel(parent.ChildObjects, "ChildObjects"))
{
_ef.SaveChanges();
return RedirectToAction("Details", new { id = parent.ID }) }
}
}
return View(parent);
I'm converting this code from a real life app, so my apologies for any typos.