Как автоматически обновить свойство Modified в Entity в Entity Framework 4 при сохранении?

Я использую EF4 в VS2010, POCO и подход, ориентированный на модель.

Моя сущность имеет следующие свойства: Id:Guid, Name:String, Created:DateTime, Modified:DateTime, Revision:Int32.

Я создаю свою сущность, задаю имя и сохраняю ее в базу данных, используя EF4-контекст. Это должно установить Id на новый Guid (работает с Identity-SGP), Created установить на now, Modified left as null, Revision установить на 0. Я получаю объект, меняю имя и сохраняю его снова. На этот раз значение Modified должно быть установлено на now, а ревизия должна быть равна 1.

Как мне лучше всего добиться этого, используя EF4 с EDMX-дизайнером?

Обновление:

Это было то, что я в итоге использовал:

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);
}

Который не работает... :(

Проблема в том, что entry.State по-прежнему не изменяется, даже если я явно запускаю метод MarkAsModified(). я не понимаю этого...

Почему отслеживание изменений не включено по умолчанию? Я использую самоотслеживающие объекты, так зачем мне их отключать и явно включать каждый раз? И почему сущности сохраняются в БД, даже если состояние не изменено? И в чем разница между EntityState и ObjectState? На данный момент я делаю все изменения и обновления на стороне сервера, но я также буду использовать WCF-сервис для передачи объектов туда и обратно через некоторое время... Есть ли разница в том, как здесь обрабатываются изменения? Если все изменения на стороне сервера, независимо от включения/выключения отслеживания изменений, сохраняются?? Я хочу, чтобы ничто никогда не могло быть сохранено без обновления Modified, Revision и т. д. Эти свойства всегда должны устанавливаться на сервере, когда объект возвращается и изменяется. (Здесь я говорю не о sql-сервере, а о сервис-сервере)


person Andreas Zita    schedule 12.10.2010    source источник


Ответы (3)


Предположим, что ваше имя типа объекта — это продукт:

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++;
}


Если вы хотите использовать этот код для заполнения общего поля, такого как ModifiedDate или ModifiedBy, для всех ваших объектов, ознакомьтесь с этим сообщением:
Entity Framework — активность аудита

Кстати, StoreGeneratedPattern и ConcurrencyMode не имеют к этому никакого отношения, они предназначены для совершенно разных и не связанных друг с другом вещей.

person Morteza Manavi    schedule 12.10.2010
comment
Спасибо, я попробую, но есть ли способ сделать это на sql-сервере при обновлении? - person Andreas Zita; 12.10.2010
comment
Конечно, вы можете написать триггер базы данных, который обновляет таблицу при вставке и обновлении. Это решение сделать это на стороне клиента с помощью Entity Framework. - person Morteza Manavi; 12.10.2010
comment
Я пробовал ваш метод, но у меня возникли проблемы с установкой свойства int внутри свойства ComplexType для обновленного объекта. У меня есть версия ComplexType с тремя целыми числами, и я хочу обновить только последнее значение. Установка новой версии полностью завершается сбоем, за исключением сообщения о том, что мне нужно реализовать IExtendedDataRecord. Это действительно необходимо? - person Andreas Zita; 13.10.2010
comment
Кроме того, этот метод немного сложен. Нет ли способа работать с моим объектом-сущностью, как предложил Джейкоб? Это было бы намного проще. Но используя его метод, я не получаю вновь созданных объектов, а только существующие. Я предполагаю, что они еще не были добавлены в ObjectSet по-настоящему? - person Andreas Zita; 13.10.2010
comment
Хорошо, я разместил фрагмент кода в своем ответе для вашего сценария. Кстати, если вы используете POCO, вам следует сначала изменить шаблон T4, чтобы вызвать метод OnContextCreated(), чтобы вы могли успешно подписаться на событие SavingChanges. - person Morteza Manavi; 13.10.2010
comment
Почему бы просто не переопределить SaveChanges в частичном классе? В чем разница использования GetObjectStateEntries(), а не моих ObjectSets? - person Andreas Zita; 13.10.2010
comment
И в чем разница работы с FieldMetadata и самой Entity? Я связываюсь с EF, если устанавливаю новые значения непосредственно для объекта в переопределении SaveChages? - person Andreas Zita; 13.10.2010
comment
Конечно, вы можете переопределить SaveChanges(), и на самом деле он был сделан виртуальным в EF 4.0 только для того, чтобы разработчики могли переопределять и подключать свой собственный код, но помните, что SaveChanges — это общий метод в ObjectContext, который вызывается для каждой сущности, и вам нужны ObjectStateEntries для овладеть вашей конкретной сущностью. Я лично предпочитаю использовать событие SavingChanges вместо переопределения SaveChanges. - person Morteza Manavi; 14.10.2010
comment
Чтобы быть более ясным, GetObjectStateEntries — это ЕДИНСТВЕННЫЙ способ доступа к сущностям в SavingChanges или SaveChanges. Этот метод возвращает IEnumerable‹ObjectStateEntry› записей, управляемых контекстом путем фильтрации по определенному EntityState. - person Morteza Manavi; 14.10.2010

Лично я неравнодушен к решению на основе кода на стороне клиента. Поскольку вы используете POCO, вероятно, было бы проще, если бы сам объект выполнял обновление. Обновите метод доступа set для свойства Name, чтобы вызвать метод objectModified, который изменяет свойства Modified и Revision.

Если это неприятно из-за разницы во времени между временем изменения свойства и временем сохранения объекта, вы можете подключиться к разделяемому классу вашего объекта Entities. Это позволит вам переопределить метод SaveChanges, в котором вы можете касаться своих сущностей в их свойстве ObjectSet‹T› объекта Entities. Что-то типа:

public partial class YourEntities
{
    public override int SaveChanges(System.Data.Objects.SaveOptions options)
    {
        //update your objects here
        return base.SaveChanges(options);
    }
}
person Jacob Proffitt    schedule 12.10.2010
comment
Но если я обновлю свой SQL из какого-либо другого места, не используя EF4, я все равно захочу, чтобы столбец Revision увеличился, и было бы неплохо, если бы столбец Modified также изменился. Я должен подумать над этим, я могу использовать комбинированное решение. Спасибо за ответ! - person Andreas Zita; 13.10.2010
comment
Я попробовал предложенный вами метод, но при добавлении новых объектов они не появятся в моем контексте. Объекты. Он пустой... есть идеи, почему? - person Andreas Zita; 13.10.2010
comment
Заметил, что мне пришлось явно запустить entity.StartTracking(), чтобы изменить его состояние на измененное. Как я могу сделать так, чтобы он был включен по умолчанию? И почему мои изменения сохраняются при запуске SaveChanges(), когда отслеживание изменений выключено, а состояние == без изменений??? Странный... - person Andreas Zita; 13.10.2010
comment
Если вам нужно сохраняться в нескольких приложениях, вам нужно либо совместно использовать библиотеку, либо использовать триггеры на уровне базы данных. Если вы добавили объект в свой контекст или получили его через свой контекст, он должен появиться в ObjectSet для этой сущности. - person Jacob Proffitt; 13.10.2010

Для этого мы можем использовать частичный класс и переопределить метод SaveChanges.

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();
        }
    }
}
person Md. Nazrul Islam    schedule 18.09.2014