I have a problem that I can't seem to understand, let alone solve. Any help would be appreciated.
So I have an MVC application, Entity Framework, the usual. I have a request that can update one property of one entity. This request sends the Id of the entity and the new value of the property.
In order to avoid querying the database for the entity, and since I only want to do the update on that one property and then return, I attach a new entity to the context with the id and then set the CurrentValue. Something like this:
public class MyEntityHandler
{
// dbContext is my EF context, instantiated from a DI container
// ...
public void UpdateProperty<TProp>(MyEntity entity, Expression<Func<MyEntity , TProp>> property, TProp value)
{
var memberName=(property.Body as MemberExpression).Member.Name;
var temp = dbContext.ChangeTracker.Entries<MyEntity>()
.SingleOrDefault( o => o.Entity.Id == entity.Id );
if( temp == null || temp.State == EntityState.Detached)
{
dbContext.MyEntities.Attach( entity );
}
this.dbContext.Entry(entity).Property(memberName).IsModified = true;
this.dbContext.Entry(entity).Property(memberName).CurrentValue = value;
}
}
Then, this can be used like this: UpdateProperty(e, e=>e.Prop, "NewValue");
where e is of type MyEntity, and Prop is a string property in it.
When the post request comes in, I simply create a new MyEntity, I assign the id and then call this method. Something like this (there are a couple more layers, but it makes no difference for my question):
public ActionResult MyMethod(int id, string newValue)
{
var e=new MyEntity { MyEntityId=id };
new MyEntityHandler().UpdateProperty(e,e=>e.Prop,newValue);
return View();
}
My understanding is that in this case, EF should attach the entity in unchanged state. The entity in my case is not in the context and the attach succeeds. Then I update that one single property, which will put the entity into Modified state, so an update statement should be generated on SaveChanges(). Since only the one property is changed, that should be the only one in the update statement. I double checked the values in the ChangeTracker, I can see that the entity is modified and the property is modified, but every other property i not modified.
The problem is that when I call SaveChanges(), I get a DbValidationException, because one of the other properties is null, but it has a Required attribute. This is all rightfully so - since I attach a new entity and set only the id before attaching, it should be null. I just don't understand the validation error - I'm not trying to insert that value into the database (again, I checked the ChangeTracker and the state of the entity is modified and the property in question is not modified).
Why do I get this validation error? Is this by design? Is there a way to tell EF to let this save through (without disabling the validation altogerher on the context)?
Related
I have an application (webservice), where numerous objects are inserted into a database or updated (with Entity Framework), but never read. So I even don't have read-access to this database.
Now a new requirement wants some properties to be inserted but never to be updated. I save all objects like this:
Type t = obj.GetType();
db.Set<TT>().AddOrUpdate(MapObject<TT>(obj)); //Maps to database entity and saves object
Now the question is, if there is an attribute I can tell a property to be inserted, but ignored while updating? In best case something like this:
[InsertOnly]
public string SomeText { get; set; }
In your unit of work save routine, check ChangeTracker and remove those records or properties that are marked as update. Something like this:
var modifiedItems = _dbContext.ChangeTracker.Entries()
.Where(x => x.State == EntityState.Modified)
.ToList();
EntityState has following types:
Detached
Unchanged
Added
Deleted
Modified
You can add a custom attribute.
public class InsertOnly : Attribute
{}
If we consider
on your EF object, you add the custom property as such:
public class SomeEFClass {
[InsertOnly]
public int Id {get;set;}
}
Then change
db.Set<TT>().AddOrUpdate(MapObject<TT>(obj)); //Maps to database entity and
To include something like (This is pseudo code, don't expect it to run)
foreach(Property info pi in t.GetProperties())
{
if(pi.GetCustomAttribute<InsertOnly>() == null)
{
//You can safely update this property
}
else
{
//This property is only for inserting
}
}
I am uncertain if you can add the custom attribute via partial class overloading of a property? That might be worth a shot?
I am using .net core.
My Goal: I want to be able Edit a SalesOrder just after Creating.
Right now I am able to Create and Edit. But it is throwing an error
The instance of entity type 'SalesOrder' cannot be tracked because
another instance of this type with the same key is already being
tracked. When adding new entities, for most key types a unique
temporary key value will be created if no key is set (i.e. if the key
property is assigned the default value for its type). If you are
explicitly setting key values for new entities, ensure they do not
collide with existing entities or temporary values generated for other
new entities. When attaching existing entities, ensure that only one
entity instance with a given key value is attached to the context.
When I try editing just after creating.
My Save() function:
public class SalesOrdersController : Controller
{
private readonly ApplicationDbContext _dbContext;
public SalesOrdersController(ApplicationDbContext dbContext){
_dbContext = dbContext;
}
// ...other Controller actions
public JsonResult Save([FromBody]SalesOrderViewModel salesOrderViewModel)
{
SalesOrder salesOrder = new SalesOrder();
salesOrder.document_id = salesOrderViewModel.document_id;
salesOrder.customer = salesOrderViewModel.customer;
salesOrder.document_status_id = salesOrderViewModel.document_status_id;
...
salesOrder.object_state = salesOrderViewModel.object_state;
_dbContext.Entry(salesOrder).State = Helpers.ConvertState(salesOrder.object_state);
_dbContext.SaveChanges();
salesOrderViewModel.document_id = salesOrder.document_id;
salesOrderViewModel.object_state = ObjectState.Unchanged;
return Json(new { salesOrderViewModel });
}
}
And a function to update states depending on the request:
public static EntityState ConvertState(ObjectState objectState){
switch (objectState){
case ObjectState.Added:
return EntityState.Added;
case ObjectState.Modified:
return EntityState.Modified;
case ObjectState.Deleted:
return EntityState.Deleted;
default:
return EntityState.Unchanged;
}
}
I understand that it is a problem with refreshing the entity state just after creating. How can I resolve that error?
You said you understand the problem... so the solution is to get the original entity from the database and update its properties directly and then update it itself. I mean what you need to do is to avoid calling
context.Update(entity);
Where entity is the object in your model.
So One solution would be something like the following which I agree it may not the best way of solving it.
Let's assume you are using generic repository (which is harder than non generic because you do not know the fields beforehand)
public void Edit(TBusinessObject entity)
{
var originalEntity = context.Set<TBusinessObject>().AsNoTracking().FirstOrDefault(r => r.Id.Equals(entity.Id));
EntityEntry<TBusinessObject> original = context.Entry(originalEntity);
EntityEntry<TBusinessObject> client = context.Entry(entity);
foreach (var property in original.OriginalValues.Properties)
{
var dbMember = original.Member(property.Name);
var clientMember = client.Member(property.Name);
if(!property.IsPrimaryKey() && dbMember.CurrentValue != clientMember.CurrentValue && clientMember.CurrentValue!= null)
{
dbMember.CurrentValue = clientMember.CurrentValue;
dbMember.IsModified = true;
}
}
context.Update(originalEntity);
context.SaveChanges(true);
}
Again, This code could be optimized and it would be way simpler if it was not a generic repository, where you know the names and the types of the fields.
Update 1:
I found out that EF.Core although not yet fully fledged with all the features that were supported by EF6. Yet it is somehow leaned towards the modern development practices.. The example that you posted was all about using EF.Core to implement the traditional repository mentality. If you switch to use UnitOfWork or CQRS you will not face these problems, changes like updates and CRUS in general will be smooth like never before. I am passing an object to the context, and the context itself is able to figure out to what table it belongs and how to handle it. Therefore I recommend changing the way you choose to utilize EF.Core
Try this simplest implementation:
public void Commit()
{
using (var context = new ApplicationDbContext())
{
context.UpdateRange(Changed);
context.AddRange(Added);
context.RemoveRange(Deleted);
context.SaveChanges();
ClearAllChanges();
}
}
Where "Changed, Added, Deleted" are just lists where you might consider AsynchronousBags
Let's say you have these classes in your entities.
public class Parent
{
public int ParentID { get; set; }
public virtual ICollection<Child> Children { get; set; }
}
public class Child
{
public int ChildID { get; set; }
public int ParentID { get; set; }
public virtual Parent Parent { get; set; }
}
And you have a user interface to update the Parent along with its Children, meaning if the user add new Child then you have to insert, if the user edits an existing Child then you need to update, and if the user removes a Child then you have to delete. Now obviously if you use the following code
public void Update(Parent obj)
{
_parent.Attach(obj);
_dbContext.Entry(obj).State = EntityState.Modified;
_dbContext.SaveChanges();
}
it won't be able to detect the changes inside the Child because EF cannot detect changes inside a Navigation Property.
I've been asking this question for like 4 times and get mixed answers. So is it actually possible to do this stuff without it getting complicated? This problem can fix the problem by separating the user interface between Parent and Child but I don't want to because merging both Child and Parent in one menu is pretty common in business application development and more user friendly.
UPDATE :
I'm trying the solution below but it doesn't work.
public ActionResult(ParentViewModel model)
{
var parentFromDB = context.Parent.Get(model.ParentID);
if (parentFromDB != null)
{
parentFromDB.Childs = model.Childs;
}
context.SaveChanges();
}
Instead of detecting changes inside the Children, EF won't be able to tell what to do with old child. For example if parentFromDB has 3 children the first time I pull it from DB then I delete the 2nd and 3rd child. Then I'm getting The relationship could not be changed because one or more of the foreign-key properties is non-nullable when saving.
I believe this is what happened :
The relationship could not be changed because one or more of the foreign-key properties is non-nullable
Which took me back to square one because in my scenario, I can't just fetch from the DB and update the entry and call SaveChanges.
because EF cannot detect changes inside Navigation Property
This seems to be a somewhat distorted description of the fact that _dbContext.Entry(obj).State = EntityState.Modified doesn't mark navigaton properties as modified.
Of course EF tracks changes in navigation properties. It tracks changes in properties and associations of all entities that are attached to a context. Therefore, the answer to your question, now positively stated...
Is it possible to update child collection in EF out of the box
... is: yes.
The only thing is: you don't do it out of the box.
The "out of the box" way to update any entity, whether it be a parent or a child in some collection is:
Fetch entities from the database.
Modify their properties or add/remove elements to their collections
Call SaveChanges().
That's all. Ef tracks the changes and you never set entity States explicitly.
However, in a disconnected (n-tier) scenario, this gets more complicated. We serialize and deserialize entities, so there can't be any context that tracks their changes. If we want to store the entities in the database, now it's our task to make EF know the changes. There are basically two ways to do this:
Set the states manually, based on what we know about the entities (like: a primary key > 0 means that they exist and should be updated)
Paint the state: retrieve the entities from the database and re-apply the changes from the deserialized entities to them.
When it comes to associations, we always have to paint the state. We have to get the current entities from the database and determine which children were added/deleted. There's no way to infer this from the deserialized object graph itself.
There various ways to alleviate this boring and elaborate task of painting the state, but that's beyond the scope of this Q&A. Some references:
Generic repository to update an entire aggregate
GraphDiff
Its cozs your doing it weirdly.
This requires Lazy loading for getting childs (obviously modify for your usage)
//get parent
var parent = context.Parent.Where(x => x.Id == parentId).SingleOrDefault();
wrote a whole test method for you. (apply to your case)
EmailMessage(parent) is the parent and it has none or many EmailAttachment's(child's)
[TestMethod]
public void TestMethodParentChild()
{
using (var context = new MyContext())
{
//put some data in the Db which is linked
//---------------------------------
var emailMessage = new EmailMessage
{
FromEmailAddress = "sss",
Message = "test",
Content = "hiehdue",
ReceivedDateTime = DateTime.Now,
CreateOn = DateTime.Now
};
var emailAttachment = new EmailAttachment
{
EmailMessageId = 123,
OrginalFileName = "samefilename",
ContentLength = 3,
File = new byte[123]
};
emailMessage.EmailAttachments.Add(emailAttachment);
context.EmailMessages.Add(emailMessage);
context.SaveChanges();
//---------------------------------
var firstEmail = context.EmailMessages.FirstOrDefault(x => x.Content == "hiehdue");
if (firstEmail != null)
{
//change the parent if you want
//foreach child change if you want
foreach (var item in firstEmail.EmailAttachments)
{
item.OrginalFileName = "I am the shit";
}
}
context.SaveChanges();
}
}
Update
Do your AutoMappper Stuff... as you said in your comment.
Then when you are ready to save and you have it back as the correct types ie once which represent entitys(Db) then do this.
var modelParent= "Some auto mapper magic to get back to Db types."
var parent = context.Parent.FirstOrDefault(x => x.Id == modelParent.Id);
//use automapper here to update the parent again
if (parent != null)
{
parent.Childs = modelParent.Childs;
}
//this will update all childs ie if its not in the new list from the return
//it will automatically be deleted, if its new it will be added and if it
// exists it will be updated.
context.SaveChanges();
I have spent hours trying different solutions to find out some decent way of dealing with this problem. The list is so long that I can't write all of them here but few are...
changing parent entity state
changing child entity state
attaching and detaching entity
clearing dbSet.Local to avoid tracking errors
tried writing customer logic in ChangeTracker
rewriting mapping logic between DB to View models
....and so on....
Nothing worked but finally, here is how just a minor change solved the whole mess.
With this solution, you need to stop setting states manually. Just call dbSet.Update() method once, EF will take care of internal state management.
NOTE: This works even though you are working with detached graphs of entities or even with the entities having nested parent-child relationships.
Before code:
public void Update(Parent obj)
{
_parent.Attach(obj);
_dbContext.Entry(obj).State = EntityState.Modified;
_dbContext.SaveChanges();
}
After code:
public void Update(Parent obj)
{
dbSet.Update(obj);
_dbContext.SaveChanges();
}
Reference: https://www.learnentityframeworkcore.com/dbset/modifying-data#:~:text=DbSet%20Update&text=The%20DbSet%20class%20provides,with%20individual%20or%20multiple%20entities.&text=This%20method%20results%20in%20the,by%20the%20context%20as%20Modified%20
If you are using entityframework core, it provides dbSet.Update() method, which takes care of any update in any level of object tree.
For reference please check the documentation link here
For audit logging purposes, I need to get values of all the columns, including FK entities and relational entities that have been modifed for one of the table in the database. Database is basically for a website where user can upload resources (files, online document, picture etc.), I've a table called Material which has multiple many-2-many nd one-2-one relations like Material - Audience, Material - Category, 'Material-Uploader', 'Material-PermissionMaterial -Tags etc. I want to log all the changes happening to a Material. For example if someone removes a Tag from a Material, then I need to log:
[User12 - 12/12/12] - Happy tag got removed from Crappy material.
So far I got this: I can get all the ObjectStateEntries which are modified, added, deleted by using:
context.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Deleted | EntityState.Modified)
Now, I can check whether this ObjectStateEntry is RelationShip or not using:
if (e.IsRelationship) {
HandleRelationshipEntry(e);
}
else {
HandleEntry(e);
}
Within HandleEntry method (Entry is not relationship entry), I can check the type of Entry, in my case it is Material, so I'm doing:
// We care about only Material which are modifed
if (e.State != EntityState.Modified || !(e.Entity is Material))
return;
Once, I know Entry is of type Material Entry, I can get all the columns that have changed for Material table using:
e.CurrentValues[ARCHIVE_COLUMN].ToString() != e.OriginalValues[ARCHIVE_COLUMN].ToString()
At this point, I can log all the non FK changes of Material table. But if column is FK to some other entity, I cannot resolve that FK value to corresponding Entity. I could just know that CategoryID has been changed from 42 to 76 but I cannot resolve name of the Category itself. I tried approach like casting DBDataRecord and CurrentValueRecord to EntityKey but it is just NULL. Is there any way to resolve these FKs to Entities using ObjectStateManager?
My full code for the reference:
private class SingleMaterialLogger {
MaterialAuditData auditData = new MaterialAuditData();
public void HandleEntity(ObjectStateEntry e, ObjectContext context) {
HandlePrimaryTypeChanges(e);
HandleComplexTypeChanges(e, context);
}
private void HandleComplexTypeChanges(ObjectStateEntry e, ObjectContext c) {
// Owner, Category, Contact
ChangeValueHelper(e, CONTACT_COLUMN, (k1, k2) => {
// get old value
User old = c.GetObjectByKey(k1) as User;
User current = c.GetObjectByKey(k2) as User;
});
}
public void HandlePrimaryTypeChanges(ObjectStateEntry e) {
// Name, Description, ArchiveDate, Status
// Again no reflection is used - So change them if column name changes
ChangeValueHelper<string>(e, NAME_COLUMN, (change) => auditData.Name = change);
ChangeValueHelper<string>(e, NAME_COLUMN, (change) => auditData.Description = change);
// TODO - Fix change value helper
if (e.CurrentValues[ARCHIVE_COLUMN].ToString() != e.OriginalValues[ARCHIVE_COLUMN].ToString()) {
auditData.ArchiveDate = new Change<DateTime?>(e.OriginalValues[ARCHIVE_COLUMN] as DateTime?, e.CurrentValues[ARCHIVE_COLUMN] as DateTime?);
}
}
private void ChangeValueHelper(ObjectStateEntry e, string columnName, Action<EntityKey, EntityKey> func) {
if (e.CurrentValues[columnName].ToString() != e.OriginalValues[columnName].ToString()) {
func(e.OriginalValues[columnName] as EntityKey, e.CurrentValues[columnName] as EntityKey);
}
}
private void ChangeValueHelper<T>(ObjectStateEntry e, string columnName, Action<Change<T>> func) where T : class {
if(e.CurrentValues[columnName].ToString() != e.OriginalValues[columnName].ToString()) {
func(new Change<T>(e.OriginalValues[columnName] as T, e.OriginalValues[columnName] as T));
}
}
}
Dictionary<EntityKey, SingleMaterialLogger> singleMaterialLoggerMap = new Dictionary<EntityKey, SingleMaterialLogger>();
private ObjectContext context;
public MaterialAuditLogger(ObjectContext context) {
this.context = context;
}
public void AuditMaterialChanges() {
// Grab everything thats being added/deleted/modified
foreach(var e in context.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Deleted | EntityState.Modified)) {
if (e.IsRelationship) {
HandleRelationshipEntity(e);
}
else {
HandleEntity(e);
}
}
}
private void HandleEntity(ObjectStateEntry e) {
// We care about only Material which are modifed
if (e.State != EntityState.Modified || !(e.Entity is Material))
return;
var logger = SingleLogger(e.EntityKey);
logger.HandleEntity(e, context);
}
private void HandleRelationshipEntity(ObjectStateEntry e) {
// relations whose entity keys contains
}
private SingleMaterialLogger SingleLogger(EntityKey key) {
if(singleMaterialLoggerMap.ContainsKey(key))
return singleMaterialLoggerMap[key];
SingleMaterialLogger logger = new SingleMaterialLogger();
singleMaterialLoggerMap[key] = logger;
return logger;
}
I've run into the same problem.
It's not difficult to pull any entity type with an id value:
DbContext.Set(entityType).Find(id)
However this assumes that you have identified the entity type from the relevant navigation property in the first place. That requires some smarts, basically duplicating the EF logic by using reflection to look at property names and [ForeignKey()] attributes etc.
Some options are:
1) Add smarts to work out the FK model property from the FK ID property. Then do a lookup of the FK model on the fly during the audit log creation process, and store down the .ToString() value in the audit log.
This assumes:
You have a general utility in your DataContext/Repository to lookup any model type on the fly (eg. DbContext.Set(entityType).Find(id))
You are confident that the .ToString() implementation on all your FK models will work reliably because of one of the following:
They never rely on further navigation properties which may cause a run-time error
You can be confident the further navigation properties were properly Include()'ed in your model lookup
You have lazy loading enabled (which I strongly advise against.... but.. it would help here)
You have thought through the transaction implications (if you are using transactions beyond what EF does)
2) Store down the FK ID in the audit log. Then, when viewing the audit log, do a lookup of the FK model on the fly and render ToString() on screen.
We went with this option in our project and it works fine.
However your auditing requirements may be stricter. For example, if someone changes the name/description on the FK model, then this will appear to modify the old audit logs.
I have the following entity:
public class Entity
{
public virtual Guid Id { get; protected set; }
public virtual String Name { get; set; }
}
With the following Mapping:
public class EntityMap : ClassMap<Entity>
{
public EntityMap()
{
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.Name).Not.Nullable();
}
}
I get an error executing the following code:
using(var tx = Session.BeginTransaction())
{
var entity = GetSomethingFromTheDatabase();
if(entity == null)
{
Session.Save(new Entity());
}
entity.Name = "test";
tx.Commit();
}
The error is:
NHibernate.PropertyValueException : not-null property references a null or transient value Entity.Name
When I make the property Name nullable, everything works fine, but NHibernate issues an Insert-statement (with Name=null) followed by an Update-statement (with Name='test')
So: Why does NHibernate try to insert my entity before updating it with the values? That's how the autoincrement id-generator works. We try to avoid this with Guid.Comb. If I read the documentation, Guid.Comb should generate the ID without going to the database.
EDIT:
I clarified the code a bit to show what we want to do. We want to insert an Entity when it is not present in the database.
The database statements are only executed when the transaction is committed. So when Save() is called, nothing happens. But when tx.Commit() is called, the insert is issued and immediately followed by the update. I would think that NHibernate would save all changed values and put them in one Insert.
Is it issuing the inserts as soon as .Save is executed?
Maybe you should set the entity.Name before you issue the Save.
I've always had the strong opinion that if you are creating your entity the constructor should take all of the required fields to construct the object. A field in the database that is not nullable is a required field.
Because you Save it before you modify it? When you call .Save the entity gets validated and if needed persisted or else waits before it either gets flushed or the transaction is completed. When you call Save it has a property with a null value and so you get the exception.
Console.WriteLine(session.Save(..))
Returns the generated ID for your entity. You could see if that value corresponds with the value in the database.
But I would indeed assume that no roundtrip would be made to the database yet until either flushed or committed.