I just updated an entire WCF app from EF4 / AutoMapper 1.1 to EF6 / AutoMapper 6.0.0.2 and the behavior is not completely the same.
This doesn't work for me :
Entity Framework - Add Child Entity
Before :
child.Parent = parentObject
OR
parentObject.Children.Add(child)
had the same result in real time (while debugging == before SaveChanges), so I decided to use child.Parent = parentObject for the readability. child.Parent = parentObject added a child in parentObject automatically. The child was also added to the db.
Now : child.Parent = parentObject is not enough anymore (child is not added in the db), I have to add parentObject.Children.Add(child). Sometimes I need the link child.Parent = parentObject, so I have to write both lines. Can someone explain to me why it does not work anymore ?
Also :
I could write before :
Mapper.CreateMap< Patient, PATIENTENTITY >()
.ForMember(dest => dest.Gender, opt => opt.ResolveUsing< PatientGenderResolver >())
.ForMember(dest => dest.REF_GENDER, opt => opt.Ignore())
where dest.Gender is the PK(int) and PatientGenderResolver find the id(int) of the Gender in the table REF_GENDER. This mapping was enough to set PATIENTENTITY.REF_GENDER in real time thanks to the Id resolver.
Now the id is set but PATIENTENTITY.REF_GENDER remains null.
Also I tried to set directly PATIENTENTITY.REF_GENDER with a resolver but it add a Gender in the table REF_GENDER...
So again, can someone explain to me why it does not work anymore ?
EDIT
Some precisions :
Before :
patientEntity = Mapper.PatientToEntity(patientModel);
//patientEntity.REF_GENDER is null
Context.PATIENTENTITIES.AddObject(patientEntity);
//patientEntity.REF_GENDER is set !
Context.SaveChanges();
Now :
patientEntity = Mapper.PatientToEntity(patientModel);
//patientEntity.REF_GENDER is null
Context.PATIENTS.Add(patientEntity);
//patientEntity.REF_GENDER is still null !
//patientEntity.REF_GENDER = Context.REF_GENDER.Find(patientEntity.Gender);//I am obliged to add this line everywhere for every REF !
Context.SaveChanges();
My guess is that the two problems I have are related
EDIT
I just go back in my project. I now have EF6 and Automapper 1.1.
The problems are exactly the sames so I guess Automapper is not involved.
EDIT
I get around the REF_GENDER issue with
patientEntity = Mapper.PatientToEntity(patientModel, Context);
public PATIENT PatientToEntity(Patient patient, EntityContainer context)
{
PATIENT entity = AutoMapper.Mapper.Map<Patient, PATIENT>(patient);
if (patient.Id == null || patient.Id == Guid.Empty)
entity.PatientId = Guid.NewGuid();
else
entity.PatientId = patient.Id;
entity.REF_GENDER = context.REF_GENDER.Find(entity.Gender);
return entity;
}
Apparently, the context has to be the same otherwise a new REF_GENDER is added to the db
You don't mention it explicitly, but you didn't only move from EF 4 to 6, but also from ObjectContext to DbContext. That's a tremendous difference in behavior of the entity classes.
In the ObjectContext API the generated entity classes were stuffed with code that cooperated closely with the context they were involved in. A reference property like child.Parent would look like:
public Parent Parent
{
get
{
return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Parent>("model.FK_Child_Parent", "Parent").Value;
}
set
{
((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference<Parent>("model.FK_Child_Parent", "Parent").Value = value;
}
}
So setting this property is setting the Value property of an EntityReference<Parent>. EF4.1's code isn't public, so we can only guess what happens inside. One thing is clear: it changes childs state into Added -- if child wasn't yet attached to the context.
Fortunately EF abandoned this rigid vendor lock-in when it released the DbContext API (in EF 4.1 no less). Now this generated property isn't anything but an auto-property:
public Parent Parent { get; set; }
This made it much easier to unify the database-first and code-first modes of working with EF. In fact, both code-first and database-first entity classes were POCOs now.
The price (if you like) is that EF can't keep as close a track of everything that happens as it could before. Before, all entity classes inherited from EntityObject, and EF could keep track of all of their interactions. The statement...
child.Parent = parentObject;
would draw the yet unknown child into the context through the attached parentObject.
Now, when someone sets child.Parent, no one but child knows what happened, not even Parent. Which means: there is no way whatsoever for EF to become aware of this change when its change tracker executes DetectChanges (as it does very frequently).
That's why using DbContext you have to add a new child to the context yourself, either by explicitly setting its state, or adding it to context.Children. Or by adding it to parent.Children, which is a change the change tracker can detect, if parent is attached.
Related
Platform: .NET 4.5, EF6
Original code:
model.ContentGroups = new List<ContentGroup>();
model.ContentGroups.Add(new ContentGroup());
Working code:
model.ContentGroups.Clear()
model.ContentGroups = new List<ContentGroup>();
model.ContentGroups.Add(new ContentGroup());
ContentGroups definition:
private ICollection<ContentGroup> _contentGroups;
public virtual ICollection<ContentGroup> ContentGroups
{
get { return _contentGroups ?? (_contentGroups = new List<ContentGroup>()); }
set { _contentGroups = value; }
}
If model.ContentGroups already contains one item, the original code resulted two items in the collection unless deliberately make call to collection Clear()
It only occurs when compiling code in release mode, but not in debug build.
Any feedback is appreciated.
Michael
Without seeing all the code related to your DbContext and how you are actually using this I can't be certain but I think it is because setting the property to a new list sort of conflicts with entity framework. I am guessing the issue is related to lazy loading and the way entity framework knows when to do something with the database. Even though you have set it to a new List, EF still goes out to the database and re-fills the list when you enumerate it. Setting to a new List doesn't tell EF to do anything database wise.
If you want to clear the list then all you need is the .Clear() call, there is no need to ever set it to a new list. The Clear() will instruct EF to update the database when you SaveChanges on the DbContext.
I have this code in a Windows Service targeted to .Net 4.5 that uses a database-first Entity Framework layer:
var existingState = DataProcessor.GetProcessState(workerId);
existingState.ProcessStatusTypeId = (int)status;
existingState.PercentProgress = percentProgress;
existingState.ProgressLog = log;
DataProcessor.UpdateProcessState(existingState);
And this code in a data processing class in the same solution:
public ProcessState GetProcessState(int id)
{
using (var context = new TaskManagerEntities())
{
var processes = (from p in context.ProcessStates.Include("ProcessType").Include("ProcessStatusType")
where p.IsActive && p.ProcessStateId == id
select p);
return processes.FirstOrDefault();
}
}
public ProcessState UpdateProcessState(ProcessState processState)
{
using (var context = new TaskManagerEntities())
{
context.ProcessStates.Add(processState);
context.Entry(processState).State = System.Data.EntityState.Modified;
context.SaveChanges();
}
return processState;
}
ProcessState is a parent to two other classes, ProcessStatusType and ProcessType. When I run that code in the windows service, it retrieves a record, updates the entity and saves it. Despite the fact that the ProcessType child is never used in the above code, when the save on the ProcessState entity is performed, EF does an insert on the ProcessType table and creates a new record in it. It then changes the FK in the ProcessStatus entity to point it at the new child and saves it to the database.
It does not do this in the ProcessStatusType table, which is set up with an essentially identical FK parent-child relationship.
I now have a database full of identical ProcessType entries that I don't need, and I don't know why this is occurring. I feel like I'm making some obvious mistake that I can't see because this is my first EF project. Is the issue that I'm allowing the context to expire in between calls but maintaining the same entity?
Using Add will set the state of all elements to Added, which is causing the child elements to be inserted. The parent element is not inserted as you specify EntityState.Modified for this element.
Try using the following in the UpdateProcessState rather than using Add.
context.ProcessStates.Attach(processState);
context.Entry(processState).State = EntityState.Modified;
context.SaveChanges();
Attach will set the state of all elements to Unchanged and by specifying Modified for the parent element you are indicating that only this element should be updated.
On another note. You should use the strongly-typed Include(x => x.ProcessType) rather than Include("ProcessType").
I'm using a customized method for tracking individual modified properties of an n-tier disconnected entity class. I extracted it from
Programming Entity Framework: DbContext by Julia Lerman and Rowan
Miller (O’Reilly). Copyright 2012 Julia Lerman and Rowan Miller,
978-1-449-31296-1.
The code is:
public void ApplyChanges<TEntity>(TEntity root) where TEntity : class, IObjectWithState {
// bind the entity back into the context
dbContext.Set<TEntity>().Add(root);
// throw exception if entity does not implement IObjectWithState
CheckForEntitiesWithoutStateInterface(dbContext);
foreach (var entry in dbContext.ChangeTracker.Entries<IObjectWithState>()) {
IObjectWithState stateInfo = entry.Entity;
if (stateInfo.State == RecordState.Modified) {
// revert the Modified state of the entity
entry.State = EntityState.Unchanged;
foreach (var property in stateInfo.ModifiedProperties) {
// mark only the desired fields as modified
entry.Property(property).IsModified = true;
}
} else {
entry.State = ConvertState(stateInfo.State);
}
}
dbContext.SaveChanges();
}
The purpose of this method is to let the EF know only a predefined set of entity fields are ready for update in the next call of SaveChanges(). This is needed in order to workaround the entity works in ASP.NET MVC 3 as follows:
on initial page load: the Get action of the controller is loading the
entity object and passing it as a parameter to the view.
The View generate controls for editing 2 of the fields of the entity,
and holds the ID of the record in a hidden field.
When hitting [save] and posting the entity back to the controller all
of the fields excepting the 3 preserved in the view comes with a null
value. This is the default behavior of the MVC binding manager.
If i save the changes back to the database the update query will of course overwrite the non mapped fields with a sentence as follows:
UPDATE non_mapped_field_1 = NULL, ..., mapped_field_1 = 'mapped_value_1', mapped_field_2 = 'mapped_value_2', ... non_mapped_field_n = NULL WHERE ID = mapped_field_3
This is the reason i'm trying to track the fields individually and update only those fields i'm interested in. before calling the custom method with ApplyChanges() i'm adding the list of fields i want to be included in the update to the IObjectWithState.ModifiedProperties list, in order to get a SQL statement as follows:
UPDATE mapped_field_1 = 'mapped_value_1', mapped_field_2 = 'mapped_value_2' WHERE id = mapped_value_3
The problem is, when marking one of the fields as modified in ApplyChanges, i.e.:
entry.Property(property).IsModified = true;
the system is throwing the following exception:
{System.InvalidOperationException: Member 'IsModified' cannot be called for property 'NotifyCEDeadline' on entity of type 'User' because the property is not part of the Entity Data Model.
at System.Data.Entity.Internal.InternalPropertyEntry.ValidateNotDetachedAndInModel(String method)
at System.Data.Entity.Internal.InternalPropertyEntry.set_IsModified(Boolean value)
at System.Data.Entity.Infrastructure.DbPropertyEntry.set_IsModified(Boolean value)
...
So the question is. There's a way to bypass this EF validation or let the context know of the existance of this system property (IsModified) that i'm trying to change?
Summary of the architeture:
EF Code first (annotation + Fluent API)
Oracle .NET EF Data provider (ODAC)
Context is injected to a cutom business context with nInject.MVC => this is the reason i customized the ApplyChanges() method from
using (var context = new BreakAwayContext()){
context.Set().Add(root);
to a simple call to the already initialized dbcontext
dbContext.Set().Add(root);
Oracle Database is created manually i.e. without the help of EF, so no EF metadata tables are used.
Thanks,
Ivan.
Very good description, however I can't find any information on why you need a transient property called "IsModified" in the object and/or why you need to tell EF about it being modified (EF won't be able to persist it anyway).
The value of the IsModified property should be set by the model binder if the property was incldued in the view anyway.
You could just add code in your ApplyChanges method to skip a property named "IsModified", or even better, filter only known properties using entry.CurrentValues.PropertyNames, e.g.:
foreach (var property in stateInfo.ModifiedProperties) {
// mark only the desired fields as modified
if (entry.CurrentValues.PropertyNames.Contains(property)) {
entry.Property(property).IsModified = true;
}
}
Update: Ivan, very sorry I did not understand the problem better when you posted it several months ago and that I did not follow up after your added these clarifying comments. I think I understand better now. That said, I think the code snippet that I offered can be part of the solution. From looking at the exception you are getting again, I understand now that the problem that EF is detecting is that NotifyCEDDealine is not a persistent property (i.e. it is not mapped in the Code First model to a column in the database). IsModified can only be used against mapped properties, therefore you have two options: you change the code of the implementation of IObjectWithState in your entities so that non-mapped properties are not recorded in ModifiedProperties, or you use my code snippet to prevent calling IsModified with those.
By the way, an alternative to doing all this is to use the Controller.TryUpdateModel API to set only the modified properties in your entities.
Hope this helps (although I understand it is very late).
I have an ASP.NET MVC view for editing a model object. The edit page includes most of the properties of my object but not all of them -- specifically it does not include CreatedOn and CreatedBy fields since those are set upon creation (in my service layer) and shouldn't change in the future.
Unless I include these properties as hidden fields they will not be picked up during Binding and are unavailable when I save the modified object in my EF 4 DB Context. In actuality, upon save the original values would be overwritten by nulls (or some type-specific default).
I don't want to drop these in as hidden fields because it is a waste of bytes and I don't want those values exposed to potential manipulation.
Is there a "first class" way to handle this situation? Is it possible to specify a EF Model property is to be ignored unless explicitly set?
Use either:
public bool SaveRecording(Recording recording)
{
// Load only the DateTime property, not the full entity
DateTime oldCreatedOn = db.Recordings
.Where(r => r.Id == recording.Id)
.Select(r => r.CreatedOn)
.SingleOrDefault();
recording.CreatedOn = oldCreatedOn;
db.Entry(recording).State = EntityState.Modified;
db.SaveChanges();
return true;
}
(Edit: The query only loads the CreatedOn column from the database and is therefore cheaper and faster than loading the full entity. Because you only need the CreatedOn property using Find would be unnecessary overhead: You load all properties but need only one of them. In addition loading the full entity with Find and then detach it afterwards could be shortcut by using AsNoTracking: db.Recordings.AsNoTracking().SingleOrDefault(r => r.Id == recording.Id); This loads the entity without attaching it, so you don't need to detach the entity. Using AsNoTracking makes loading the entity faster as well.)
Edit 2
If you want to load more than one property from the database you can project into an anonymous type:
public bool SaveRecording(Recording recording)
{
// Load only the needed properties, not the full entity
var originalData = db.Recordings
.Where(r => r.Id == recording.Id)
.Select(r => new
{
CreatedOn = r.CreatedOn,
CreatedBy = r.CreatedBy
// perhaps more fields...
})
.SingleOrDefault();
recording.CreatedOn = originalData.CreatedOn;
recording.CreatedBy = originalData.CreatedBy;
// perhaps more...
db.Entry(recording).State = EntityState.Modified;
db.SaveChanges();
return true;
}
(End of Edit 2)
Or:
public bool SaveRecording(Recording recording)
{
Recording oldVersion = db.Recordings.Find(recording.Id);
recording.CreatedOn = oldVersion.CreatedOn;
// flag only properties as modified which did really change
db.Entry(oldVersion).CurrentValues.SetValues(recording);
db.SaveChanges();
return true;
}
(Edit: Using CurrentValues.SetValues flags only properties as Modified which indeed have been changed compared to the original state in the database. When you call SaveChanges EF will sent only the properties marked as modified in an UPDATE statement to the database. Whereas setting the state in Modified flags all properties as modified, no matter if they really changed or not. The UPDATE statement will be more expensive because it contains an update for all columns.)
If you don't want to send that data down to the client, I don't see any other option but to load up the original from the db in your service layer when you save and merge those original property values back in to the updated object. There's no way for EF to know that you didn't set those values to null on purpose and don't actually want to save them that way.
You could implement your own model binder that ignores the properties you don't want to pass around. Start here - http://lostechies.com/jimmybogard/2009/03/18/a-better-model-binder/
I think when you going to update use getById to get all the entity and then set your relevant properties and then you can update. It will be easy if you are using some kind of mapper (Automapper) to map your properties from view model to loaded entity from DB.
If you want to avoid making an additional (unnecessary) call to your database before every update, you can either use self-tracking entities or set StoreGeneratedPattern="Identity" for those fields in your entity model. And yes, Identity is misleading, but that sounds like the setting you'd want:
Identity A value is generated on insert and remains unchanged on update.
http://msdn.microsoft.com/en-us/library/system.data.metadata.edm.storegeneratedpattern.aspx
I have a Linq to Sql Entity which has an EntitySet. In my View I display the Entity with it's properties plus an editable list for the child entites. The user can dynamically add and delete those child entities. The DefaultModelBinder works fine so far, it correctly binds the child entites.
Now my problem is that I just can't get Linq To Sql to delete the deleted child entities, it will happily add new ones but not delete the deleted ones. I have enabled cascade deleting in the foreign key relationship, and the Linq To Sql designer added the "DeleteOnNull=true" attribute to the foreign key relationships. If I manually delete a child entity like this:
myObject.Childs.Remove(child);
context.SubmitChanges();
This will delete the child record from the DB.
But I can't get it to work for a model binded object. I tried the following:
// this does nothing
public ActionResult Update(int id, MyObject obj) // obj now has 4 child entities
{
var obj2 = _repository.GetObj(id); // obj2 has 6 child entities
if(TryUpdateModel(obj2)) //it sucessfully updates obj2 and its childs
{
_repository.SubmitChanges(); // nothing happens, records stay in DB
}
else
.....
return RedirectToAction("List");
}
and this throws an InvalidOperationException, I have a german OS so I'm not exactly sure what the error message is in english, but it says something along the lines of that the entity needs a Version (Timestamp row?) or no update check policies. I have set UpdateCheck="Never" to every column except the primary key column.
public ActionResult Update(MyObject obj)
{
_repository.MyObjectTable.Attach(obj, true);
_repository.SubmitChanges(); // never gets here, exception at attach
}
I've read alot about similar "problems" with Linq To Sql, but it seems most of those "problems" are actually by design. So am I right in my assumption that this doesn't work like I expect it to work? Do I really have to manually iterate through the child entities and delete, update and insert them manually? For such a simple object this may work, but I plan to create more complex objects with nested EntitySets and so on. This is just a test to see what works and what not. So far I'm disappointed with Linq To Sql (maybe I just don't get it). Would be the Entity Framework or NHibernate a better choice for this scenario? Or would I run into the same problem?
It will definately work in Entity Framework that comes with .NET 4 (I'm doing similar things in the RC version)
This does not explain the exception but:
You should dispose the ObjectContext that's (most likely) wrapped in your repository. The context caches items, and should only be used for a single unit-of-work.
Try to use a pattern like:
public ActionResult Update(int id, MyObject obj) // obj now has 4 child entities
{
using(var repository = CreateRepository())
{
var obj2 = _repository.GetObj(id);
if(TryUpdateModel(obj2))
{
repository.SubmitChanges();
}
else
.....
}
return RedirectToAction("List");
}
When fetching items, create a new repository as well. They are cheap to create and dispose, and should be disposed as quickly as possible.