How to auto-update the Modified property on a Entity in Entity Framework 4 when saving?
Asked Answered
N

3

6

Im using EF4 in VS2010, POCO's and the model-first approach.

My entity has the following properties: Id:Guid, Name:String, Created:DateTime, Modified:DateTime, Revision:Int32.

I create my entity, set the name and save it to the database using the EF4-context. This should set Id to a new Guid (works with Identity-SGP), Created set to now, Modified left as null, Revision set to 0. I retrieve the entity, change the name and save it again. This time the Modified-value should be set to now and revision should be 1.

How do I best accomplish this using EF4 with the EDMX-designer?

Update:

This was what I ended up using:

public override int SaveChanges(System.Data.Objects.SaveOptions options)
{
    foreach (ObjectStateEntry entry in ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified).Where(e => e.Entity is EntityBase))
    {
        EntityBase entity = entry.Entity as EntityBase;
        if (entry.State == EntityState.Added)
        {
            entity.Version = new Version() { Major = 1, Minor = 0, Revision = 0 };
            entity.Created = DateTime.Now;
            if (OperationContext.Current != null) entity.CreatedBy = OperationContext.Current.ServiceSecurityContext.WindowsIdentity.Name;
        }
        else if (entry.State == EntityState.Modified)
        {
            entity.Version.Revision++;
            entity.Modified = DateTime.Now;
            if (OperationContext.Current != null) entity.ModifiedBy = OperationContext.Current.ServiceSecurityContext.WindowsIdentity.Name;
        }
    }

    return base.SaveChanges(options);
}

Which isnt working... :(

The problem is that the entry.State is still unmodified even if I explicitly run the MarkAsModified()-method. I dont get this...

Why isnt change-tracking enabled by default? Im using self-tracking entities so why would I want it off and explicitly turn it on everytime? And why is entities persisted to db even if state is unmodified? And whats the difference between EntityState and ObjectState? At the moment Im making all changes and updates serverside but I will also use a WCF-service to transfer entities back and forth some time later... Is there a difference in how changes are treated here? If serverside all changes, no matter changetracking on/off, is persisted?? What I want is that nothing should ever be able to be stored without also updating Modified, Revision etc. These properties should always be set on the server when the object is received back and has changed. (Im not talking sql-server-side here but service-server-side)

Necktie answered 12/10, 2010 at 19:7 Comment(0)
C
3

Let's assume your Entity Type name is Product:

partial void OnContextCreated() {
    this.SavingChanges += Context_SavingChanges;
}

void Context_SavingChanges(object sender, EventArgs e) {

    IEnumerable objectStateEntries =
            from ose
            in this.ObjectStateManager.GetObjectStateEntries(EntityState.Added | 
                                                            EntityState.Modified)
            where ose.Entity is Product
            select ose;

    Product product = objectStateEntries.Single().Entity as Product;

    product.ModifiedDate = DateTime.Now;
    product.ComplexProperty.Revision++;
}


If you are looking to use this code for populating common field such as ModifiedDate or ModifiedBy for all of your entities then please take a look at this post:
Entity Framework - Auditing Activity

By the way, StoreGeneratedPattern and ConcurrencyMode has nothing to do with this, they are there for completely different and unrelated stuff.

Chlo answered 12/10, 2010 at 19:15 Comment(9)
Thanks I'll try it out, but is there any way of doing this on the sql-server on update?Necktie
Of course, you can write a database "trigger" that update the table on insert and update. This is a solution to do it on the client side with entity framework.Chlo
I've tried your method but I'm having trouble setting a int-property inside a ComplexType-property on the updated entity. I have a ComplexType Version with three ints and I only want to update the last value. Setting a new Version completely fails with an exception telling me I have to implement IExtendedDataRecord. Is that really necessary?Necktie
Also, this method is a bit complicated. Isnt there any way of working with my entity-object instead as Jacob suggested? That would be much simpler. But using his method I don't get any newly created objects, only existing ones. I guess they havent been added to the ObjectSet for real yet?Necktie
Ok, I posted a code snippet in my answer for your scenario. BTW, If you are using POCOs then you should first change the T4 template to call the OnContextCreated() method so that you can successfully subscribe to SavingChanges event.Chlo
Why not just override SaveChanges in a partial class? Whats the difference of using GetObjectStateEntries() and not my ObjectSets?Necktie
And whats the difference of working with FieldMetadata and the Entity itself? Am I messing with EF if setting new values directly to the entity in a SaveChages-override?Necktie
Of course you can override SaveChanges() and actually it has been made virtual in EF 4.0 just to let developers to override and plug in their custom code but remember SaveChanges is a general method on ObjectContext which is called for every entity and you need ObjectStateEntries to get a hold of your specific entity. I personally prefer to use SavingChanges event instead of overriding the SaveChanges.Chlo
To be more clear, GetObjectStateEntries is the ONLY way to access the entities in SavingChanges or SaveChanges. This method returns an IEnumerable<ObjectStateEntry> of entries managed by the context by filtering on a particular EntityState.Chlo
B
0

I'm partial to a client-side code-based solution, personally. Since you're using POCOs, it'd probably be simplest to have the object itself do the updating. Update the set accessor on the Name property to call an "objectModified" method that changes the Modified and Revision properties.

If that's unpalatable due to the time difference between the time the property is changed and the time the object is saved, you can tap into the partial class on your Entities object. Doing so will allow you to override the SaveChanges method where you can touch your entites in their ObjectSet<T> property on the Entities object. Something like:

public partial class YourEntities
{
    public override int SaveChanges(System.Data.Objects.SaveOptions options)
    {
        //update your objects here
        return base.SaveChanges(options);
    }
}
Bottomless answered 12/10, 2010 at 19:45 Comment(4)
But if I update my SQL from some other place not using EF4 i still would want to Revision-column to increase and it would be nice if the Modified-column also changed. I have to think this over, I may use a combined solution. Thanks for your reply!Necktie
I tried the method you suggested but when adding new objects they wont turn up in my context.Entities. Its empty... any idea why?Necktie
Noticed I had to explicitly run entity.StartTracking() to get it change state to modified. How can I get it to be on by default? And why does my changes get saved when running SaveChanges() when ChangeTracking is OFF and State == Unchanged??? Weird...Necktie
If you need to persist across multiple applications, you'll need to either share a library or use triggers at the database level. If you've added an object to your context or retrieved it through your context, then it should appear in the ObjectSet for that entity.Bottomless
S
0

We can use partial class and override SaveChanges method to achieve this.

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Core.Objects;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;


namespace TestDatamodel
{
    public partial class DiagnosisPrescriptionManagementEntities
    {
        public override int SaveChanges()
        {
            ObjectContext context = ((IObjectContextAdapter)this).ObjectContext;

            foreach (ObjectStateEntry entry in
                     (context.ObjectStateManager
                       .GetObjectStateEntries(EntityState.Added | EntityState.Modified)))
            {                    
                if (!entry.IsRelationship)
                {
                    CurrentValueRecord entryValues = entry.CurrentValues;
                    if (entryValues.GetOrdinal("ModifiedBy") > 0)
                    {
                        HttpContext currentContext = HttpContext.Current;
                        string userId = "nazrul";
                        DateTime now = DateTime.Now;

                        if (currContext.User.Identity.IsAuthenticated)
                        {
                            if (currentContext .Session["userId"] != null)
                            {
                                userId = (string)currentContext .Session["userId"];
                            }
                            else
                            {                                    
                                userId = UserAuthentication.GetUserId(currentContext .User.Identity.UserCode);
                            }
                        }

                        if (entry.State == EntityState.Modified)
                        {
                           entryValues.SetString(entryValues.GetOrdinal("ModifiedBy"), userId);
                           entryValues.SetDateTime(entryValues.GetOrdinal("ModifiedDate"), now);
                        }

                        if (entry.State == EntityState.Added)
                        {
                            entryValues.SetString(entryValues.GetOrdinal("CreatedBy"), userId);
                            entryValues.SetDateTime(entryValues.GetOrdinal("CreatedDate"), now);
                        }
                    }
                }
            }

            return base.SaveChanges();
        }
    }
}
Selfassertion answered 18/9, 2014 at 9:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.