получить идентификатор новой записи в событии SavingChanges в EntityFramework

Я пытаюсь реализовать некоторую общую регистрацию в EntityFramework 4.0, используя событие SavingChanges в контексте сущности.

Я хочу записать сведения о любых созданных/обновленных и удаленных записях, включая идентификатор изменяемой записи (который всегда является полем идентификации int). Во время процессов обновления и удаления я могу получить идентификатор записи, используя

    Dim AddedItems = Statemanager.GetObjectStateEntries(EntityState.Added)
    For Each entry As ObjectStateEntry In DirectCast(sender, ObjectContext).ObjectStateManager.GetObjectStateEntries(EntityState.Added)

        NewLogItem.RecordId = CInt(entry.EntityKey.EntityKeyValues(0).Value.ToString())
    Next

Но я, очевидно, не могу получить идентификатор записи, которую нужно вставить, во время вставки, потому что она еще не была записана в базу данных, есть ли простой способ обойти это?


person Julian Slater    schedule 21.07.2010    source источник


Ответы (3)


Один из вариантов, который вы можете сделать, это переопределить метод SaveChanges и вызвать SaveChanges в базу, не принимая изменения в объектном контексте. таким образом вы можете проверить изменения в ObjectStateManager.

 override void SaveChanges(SaveOptions)
 {
context.SaveChanges(SaveOptions.DetectChangesBeforeSave);
//query the object statemanager

 }

Ключ вызывает сохранение изменений в базу с правильными параметрами сохранения. По умолчанию будут приняты все изменения в ObjectStateManager, поэтому все записи будут в неизмененном состоянии, и вы не сможете узнать, что было добавлено, удалено или изменено. после цикла через менеджер состояний вы можете явно вызвать AcceptChanges. Я также рассматриваю эту концепцию в своей книге.

12-1 Выполнение кода при вызове SaveChanges()

person zeeshanhirani    schedule 22.07.2010
comment
А, интересно! Мне всегда было интересно, какова цель AcceptAllChanges в отличие от SaveChanges. Теперь я вижу полезное применение этому методу! - person Slauma; 23.07.2010

Это то, что у меня получилось, не идеально в том смысле, что у меня получилось два вызова saveChanges и, следовательно, две транзакции, но я не вижу другого способа обойти это, если мы не регистрируем удаленные элементы перед сохранением, мы не можем получить к ним правильный доступ после savechanges() (объекты больше не будут существовать, и это отражается в записи состояния для удаленных элементов)

Открытая функция SaveAndLogChanges() как целое число

    Dim Statemanager As ObjectStateManager = Me.ObjectStateManager

    Dim AddedItems As IEnumerable(Of ObjectStateEntry) = Statemanager.GetObjectStateEntries(EntityState.Added)
    Dim UpdatedItems As IEnumerable(Of ObjectStateEntry) = Statemanager.GetObjectStateEntries(EntityState.Modified)
    Dim DeletedItems As IEnumerable(Of ObjectStateEntry) = Statemanager.GetObjectStateEntries(EntityState.Deleted)

    For Each entry As ObjectStateEntry In UpdatedItems
        ' dont do anything for any of the log file entries or we will end up in an infinate loop
        If (Not entry.EntitySet.Name = "LogItem" And Not entry.EntitySet.Name = "LogTransaction") Then
            createLogEntry(LogItemType.ItemType.Modify, entry)
        End If
    Next

    ' deleted entities
    For Each entry As ObjectStateEntry In DeletedItems
        ' dont do anything for any of the log file entries or we will end up in an infinate loop
        If (Not entry.EntitySet.Name = "LogItem" And Not entry.EntitySet.Name = "LogTransaction") Then
            createLogEntry(LogItemType.ItemType.Delete, entry)
        End If
    Next

    ' need to save changes here to ensure we have ids for the inserted records
    Me.SaveChanges()

    ' inserted enties
    For Each entry As ObjectStateEntry In AddedItems
        ' dont do anything for any of the log file entries or we will end up in an infinate loop
        If (Not entry.EntitySet.Name = "LogItem" And Not entry.EntitySet.Name = "LogTransaction") Then
            createLogEntry(LogItemType.ItemType.Insert, entry)
        End If
    Next

    Me.SaveChanges()

    Return iRet
End Function
person Julian Slater    schedule 22.07.2010

Я думаю, что нет никакого способа получить идентификатор новой вставленной записи в событии SavingChanges, поскольку событие просто вызывается до того, как база данных вообще будет затронута и создан идентификатор. К сожалению, нет события SavedChanges, которое вызывается после сохранения данных.

Возможным вариантом может быть использование того факта, что одной из перегрузок метода SaveChanges ObjectContext является virtual (или overridable в VB), а именно:

public virtual int SaveChanges(SaveOptions options)

(Примечание: виртуальная только в EF 4, а не в более ранней версии! И только эта перегрузка является виртуальной, две другие перегрузки SaveChanges — нет!)

Таким образом, вы можете переопределить этот метод в ObjectContext вашей модели. В этом переопределенном методе вы вызываете SaveChanges базового класса, и после этого ваши новые объекты должны иметь созданные идентификаторы из базы данных, которые вы затем можете регистрировать.

(Это просто грубая идея, я никогда не тестировал и не использовал эту виртуальную перегрузку. Я создал аналогичный механизм ведения журнала в SavingChanges, который вы только реализуете, и у меня была та же проблема. Но это было в EF 3.5, где этот виртуальный метод не пока не существует. Наконец, после вызова SaveChanges я зарегистрировал вставки только в важных местах, но тогда это, конечно, не было «универсальным». Возможно, новый виртуальный метод - хороший шанс улучшить это сейчас.)

Изменить

Немного точнее, что я имел в виду (в синтаксисе С#):

public partial class MyEntitiesContext : ObjectContext
{
    // ...

    public override int SaveChanges(SaveOptions options)
    {
        // Log something BEFORE entities are stored in DB

        int result = base.SaveChanges(options);

        // Log something AFTER entities are stored in DB, especially new entities
        // with auto-incrementing identity key which have been inserted in the DB
        // should have the final primary key value now

        return result;
    }

    // ...
}

Важно позвонить base.SaveChanges(options). (Конечно, вызов только SaveChanges(options) без base. заканчивается StackOverflow.)

person Slauma    schedule 21.07.2010
comment
Привет, Слаума, спасибо за вашу помощь, не смог заставить это работать, похоже, что SaveChanges (параметры SaveOptions) может быть вызван вызовом метода SaveChanges() без параметров (который мне нужно будет вызвать из моего переопределенного метода), потому что, когда я вызвал это из моего переопределенного метода SaveChanges (SaveOptions options), это привело к StackOverflowExcpetion. Однако это заставило меня переосмыслить, как это сделать, и в итоге я написал свой собственный метод SaveAndLogChanges в контексте. - person Julian Slater; 22.07.2010
comment
@Julian: Описанное вами StackOverflowException очень похоже на то, что вы не вызывали метод SaveChanges базового класса в своей переопределенной реализации. Я отредактировал свой ответ и добавил детали, чтобы подчеркнуть это. - person Slauma; 22.07.2010
comment
Hi Public Overrides Function SaveChanges(ByVal options As SaveOptions) As Integer Return MyBase.SaveChanges(options) End Function - person Julian Slater; 22.07.2010
comment
Привет, Слаума. Это более или менее то, что я делал, за исключением вызова MyBase.SaveChanges(), который изменился только на приведенный ниже код, и он пытается создать дублирующую запись при извлечении, редактировании и последующем сохранении объекта. Публичные функции переопределения SaveChanges(ByVal options As SaveOptions) As Integer Return MyBase.SaveChanges(options) End Function, даже если бы я заработал, мне все равно пришлось бы сделать 2 вызова savechanges(), чтобы добавить записи журнала для вставленных строк. Спасибо за вашу помощь, это действительно ценится - person Julian Slater; 22.07.2010