Мне нужно сделать несколько Breeze SaveChanges в одной транзакции - PullRequest
2 голосов
/ 06 января 2020

У меня есть веб-страница, которая принимает оплату, и она должна вставлять 6 строк, распределенных по 4 таблицам. Мне пришлось разбить INSERTS на два отдельных SaveChanges, но мне нужно, чтобы они оба были в одной транзакции базы данных, чтобы все INSERTS и UPDATES откатывались в случае возникновения проблем.

Я использую Breeze 1.6 поверх Entity Framework 6.2, поверх Oracle MgD Data Access 12.2 в шаблоне SPA.

4 таблицы - A, B, C и D. Таблицы B, C и D являются дочерними элементами A, каждый из которых содержит PK A в качестве внешнего ключа. Первоначально я закодировал INSERTS в этой последовательности, как того требует мое приложение A1, B1, C1, C2, C3, D1, а затем последовал один Breeze SaveChanges. C3 имеет триггеры Oracle, которые обновляют несколько столбцов в A1 и B1, когда C3 вставлен. Моя проблема в том, что Entity Framework не вставлял в последовательности, которую я кодировал (и я понимаю, что не могу контролировать последовательность). Я на самом деле получал эту последовательность: A1, C1, C2, C3, B1, D1, и, так как C3 имеет триггер, который обновляет A и B, он столкнулся с проблемой, потому что B еще не был вставлен.

Итак Я нашел этот вопрос: Какой лог c определяет порядок вставки Entity Framework 6 и предлагает использовать несколько SaveChanges для получения некоторого контроля. Работает следующим образом:

  • A1, B1 SaveChanges
  • C1, C2, C3, D1 SaveChanges Все триггеры, включая обновление C3 для A и B, работают отлично.

НО Breeze / Entity Framework рассматривает каждый SaveChanges как транзакцию, поэтому теперь я не могу откатить обе части в случае ошибок во 2-м SaveChanges. Должен ли я сам кодировать откат части 1 в случае сбоя части 2? Или есть секрет, который я не знаю?

Я знаком с BeginTransaction, Commit и Rollback, но не уверен, как и где его совместить с Breeze и Entity Framework в миксе. Breeze захватывает каждое SaveChanges и автоматически оборачивает его в транзакцию.

Мне нужно объединить несколько SaveChanges в одну транзакцию.

Спасибо за любую помощь.

1 Ответ

3 голосов
/ 08 января 2020

Я думаю, что в этом случае лучше всего позволить Breeze client рассматривать его как одно сохранение, в то время как server разбивает пакет сохранения, чтобы сохранить его в правильный порядок.

Пусть Breeze ContextProvider десериализует пакет сохранения, а затем использует хук BeforeSaveEntities для внесения изменений вручную. Наконец, пометьте сущности как Unchanged, чтобы ContextProvider не пытался их снова сохранить.

Вот пример реализации.

Сервер

На сервере, в контроллере Breeze создайте новый метод сохранения, который добавляет BeforeSaveEntitiesDelegate , который используется только для этого специального сохранения:

[HttpPost]
public SaveResult SaveFeePayment(JObject saveBundle) {
{
    contextProvider.BeforeSaveEntitiesDelegate += BeforeSaveFeePayment;
    return contextProvider.SaveChanges(saveBundle);
}

private Dictionary<Type, List<EntityInfo>> BeforeSaveFeePayment(Dictionary<Type, List<EntityInfo>> entities)
{
    var context = contextProvider.Context;

    // Get the list of EntityInfo for each type.  Throws exception if not found.
    // A fee payment save bundle must have A, B, C, and D or it is invalid.
    var ainfos = entities[typeof(A)];
    var binfos = entities[typeof(B)];
    var cinfos = entities[typeof(C)];
    var dinfos = entities[typeof(D)];

    using (var tx = context.Database.BeginTransaction()) 
    {               
        // Save A and B
        Attach(context, ainfos);
        Attach(context, binfos);
        context.SaveChanges();

        // Save C and D
        Attach(context, cinfos);
        Attach(context, dinfos);
        context.SaveChanges();

        tx.Commit();
    }

    // Set all states to Unchanged, so Breeze won't try to save them again
    ainfos.ForEach(info => info.EntityState = Breeze.ContextProvider.EntityState.Unchanged);
    binfos.ForEach(info => info.EntityState = Breeze.ContextProvider.EntityState.Unchanged);
    cinfos.ForEach(info => info.EntityState = Breeze.ContextProvider.EntityState.Unchanged);
    dinfos.ForEach(info => info.EntityState = Breeze.ContextProvider.EntityState.Unchanged);

    // Return the entities, so Breeze will return them back to the client
    return entities;
}

// Map Breeze EntityState to EF EntityState
private static Dictionary<Breeze.ContextProvider.EntityState, System.Data.Entity.EntityState> entityStateMap =
    new Dictionary<Breeze.ContextProvider.EntityState, System.Data.Entity.EntityState> {
      { Breeze.ContextProvider.EntityState.Added, System.Data.Entity.EntityState.Added },
      { Breeze.ContextProvider.EntityState.Deleted, System.Data.Entity.EntityState.Deleted },
      { Breeze.ContextProvider.EntityState.Detached, System.Data.Entity.EntityState.Detached },
      { Breeze.ContextProvider.EntityState.Modified, System.Data.Entity.EntityState.Modified },
      { Breeze.ContextProvider.EntityState.Unchanged, System.Data.Entity.EntityState.Unchanged }
    };

// Attach entities to the DbContext in the correct entity state 
private static void Attach(DbContext context, List<EntityInfo> infos)
{
    foreach(var info in infos)
    {
        var efState = entityStateMap[info.EntityState];
        context.Entry(info.Entity).State = efState;
    }
}

Клиент

На клиенте вы нужно будет вызвать конечную точку SaveFeePayment, используя с именем save . Вы будете делать это только при сохранении комиссионного платежа . Продолжайте использовать обычную SaveChanges конечную точку для других сохранений.

Вызовите специальную конечную точку, указав resourceName:

var saveOptions = new SaveOptions({ resourceName: "SaveFeePayment" });
return myEntityManager.saveChanges(null, saveOptions);

Транзакции?

У меня нет протестировал этот пример на 100% о поведении транзакции. Я не уверен, должны ли мы использовать существующий contextProvider.Context или создавать новый DbContext в начале метода. Разница в том, как обрабатываются соединения с базой данных. См. руководство Microsoft и надеемся, что оно работает так же с Oracle.

Надеемся, что ваше предыдущее решение по управлению транзакциями можно применить к методу BeforeSaveFeePayment, описанному выше.

Надеюсь, это поможет.

...