0

I have a lot of Entities in my model that inherit from an abstract class called BaseEntity. Then there is an "Edit" controller that receives a Person object as a parameter and saves the changes. In the view for this "Edit" control the user can obviously edit some fields of the entity. The view only contains the PersonId as a hidden field and the Name as a textbox.

My Entities look something like this:

    public abstract class BaseEntity
    {
        public DateTime DateCreated { get; set; }
        public DateTime DateLastModified { get; set; }
        public int? UserIdLastModified { get; set; }
    }

    public class Person : BaseEntity
    {
        int PersonId { get; set; }
        string Name { get; set; }
    }

And this is the controller:

    [HttpPost]
    public ActionResult Edit(Person person, FormCollection collection)
    {
        /* ... do something with the FormCollection */
        db.Entry(person).State = EntityState.Modified;
        db.SaveChanges();
    }

I also override the db.SaveChanges() method so it will set the DateLastModified field automatically. But because the DateCreated will be DateTime.MinValue, SaveChanges() throws an exception.

For privacy and simplicity, I don't want to add the fields of the BaseEntity as hidden fields to the View. I know I could load the entity from the database in the Controller and only update the Name, but it doesn't strike me as the best solution because I would have to do that in a lot of "Edit" controllers. And everytime something changes in the Model I have to change the Controller and View.

What's a good way of getting DateCreated back to the Controller before saving it?

EDIT: Thanks for the answers already. However, this is my SaveChanges method. To answer some of the comments and explain a little further: the problem is that if I only set DateLastModified like described below, base.SaveChanges() will try to overwrite DateCreated with '1/1/0001 12:00:00 AM' (because it was never set). And this correctly throws the exception.

What I was trying to do now, is to add some logic to get the previous values of the BaseEntity in the SaveChanges method. Apparently I need to do another Database call somewhere and this way I have the logic only in one place instead of every Controller. If there is something wrong with that approach, please tell me.

    public override int SaveChanges()
    {
        ObjectContext context = ((IObjectContextAdapter)this).ObjectContext;

        //Find all Entities that are Added/Modified that inherit from my EntityBase
        var objectStateEntries =
            (from e in context.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified)
             where e.IsRelationship == false && e.Entity != null && typeof(BaseEntity).IsAssignableFrom(e.Entity.GetType())
             select e).ToList();

        foreach (var entry in objectStateEntries)
        {
            var entityBase = (BaseEntity)entry.Entity;
            if (entry.State == EntityState.Added)
            {
                entityBase.DateCreated = DateTime.UtcNow;
            }                   
            entityBase.DateModified = DateTime.UtcNow;

            //new code for getting the previous value
            if (entityBase.DateCreated == DateTime.MinValue)
            {
                //this is not working. just to show you my idea
                var currentDBItem = this.Entry(entry.Entity).Entity as BaseEntity; //get previous value from database
                entityBase.DateCreated = currentDBItem.DateCreated;
            }
        }
        return base.SaveChanges();
    }
webtopf
  • 304
  • 3
  • 12

3 Answers3

1

Sounds like a classic case when you need to favor ViewModels over Entities in your Views.

However, assuming you aren't going to change your architecture, you simply need not to mutate the DateCreated field unless it is in 'Added' state, and the DateLastModified only in 'Modified' state. Something like:

public override void SaveChanges()
{
    foreach (var entry in this.ChangeTracker.Entries<BaseEntity>())
    {
        switch (entry.State)
        {
            case EntityState.Added:
                entry.Entity.DateCreated = DateTime.Now;
                break;

            case EntityState.Modified:
                entry.Entity.DateLastModified = DateTime.Now;
                break;
        }
    }

    base.SaveChanges();
}

Also, you better make the DateLastModified property a nullable DateTime? because it will only have a value after first update.

As per your update:

Try this:

if (entry.State == EntityState.Added)
    entityBase.DateCreated = DateTime.UtcNow;
else
    entry.Property(x => x.DateCreated).IsModified = false;
haim770
  • 48,394
  • 7
  • 105
  • 133
  • Overriding the SaveChanges method is the best way I know of that is all-encompassing. Very useful for setups where all entities derive from the same base (possibly abstract though not required) entity. – Flater Apr 23 '14 at 09:20
  • @Flater, it is very convenience indeed. Yet, you're relying on your Repository / Infrastructure layer to inject data into your Entities, which is usually the role of the Domain layer. I would initialize `DateCreated` in the constructor instead and will make it immutable. – haim770 Apr 23 '14 at 09:25
  • For DateCreated, yes, because it is something that shouldn't ever change. But since Modified dates (and possible other meta fields like tracking the user etc) are better off in the SaveChanges override, it seems better for maintainability to keep all metafield configurations in the same place. *Note: Maybe my experience is skewed. The project I used this in really needed it in the SaveChanges as there were different applications with their own models, we couldn't guarantee metafield consistency otherwise.* – Flater Apr 23 '14 at 09:32
  • @Flater, as always, it depends on the needs of your project. For large/enterprise project that relies heavily on modularity and abstractions it may cause problems. – haim770 Apr 23 '14 at 09:35
  • Added something to the previous comment. In the project I used, it was really the best option. I haven't had to use it since then, so my opinion might be a bit narrow. – Flater Apr 23 '14 at 09:36
  • @haim770, Thank you for your update! This solved my problem. It might be worth noting, that the `entry` object in your update comes from `this.ChangeTracker.Entries()`. It took me a moment to find out `entry.Property` exists for `DbEntityEntry` but not for `ObjectStateEntry`. – webtopf Apr 23 '14 at 14:12
0

I think your best option is actually retrieving the original entity from the database, based on the id you stored in the hidden field and only update the value that could have changed. Basically what you proposed as solution.

I understand that it seems pretty dull, but it's a common practice.

By the way, I also suggest looking into view models.

Community
  • 1
  • 1
Henk Mollema
  • 44,194
  • 12
  • 93
  • 104
0

There is no "easy" solution (for example adding the DateCreated as a hidden field). It would be the best just to load the data from the database again or you could use view models and write your own ModelBinder.

leskovar
  • 661
  • 3
  • 8