Reversión de transacciones de Entity Framework 6

82

Con EF6 tiene una nueva transacción que se puede utilizar como:

using (var context = new PostEntityContainer())
        {
            using (var dbcxtransaction = context.Database.BeginTransaction())
            {
                try
                {
                    PostInformation NewPost = new PostInformation()
                    {
                        PostId = 101,
                        Content = "This is my first Post related to Entity Model",
                        Title = "Transaction in EF 6 beta"
                    };
                    context.Post_Details.Add(NewPost);
                    context.SaveChanges();
                    PostAdditionalInformation PostInformation = new PostAdditionalInformation()
                    {
                        PostId = (101),
                        PostName = "Working With Transaction in Entity Model 6 Beta Version"
                    };

                    context.PostAddtional_Details.Add(PostInformation);
                    context.SaveChanges();

                    dbcxtransaction.Commit();
                }
                catch
                {
                    dbcxtransaction.Rollback();
                }
            }
        }

¿Es realmente necesaria la reversión cuando las cosas van de lado? Tengo curiosidad porque la descripción de Commit dice: "Confirma la transacción de la tienda subyacente".

Mientras que la descripción de Rollback dice: "Retrotrae la transacción de la tienda subyacente".

Esto me da curiosidad, porque me parece que si no se llama a Commit, los comandos ejecutados anteriormente no se almacenarán (lo que me parece lógico). Pero si ese es el caso, ¿cuál sería la razón para llamar a la función Rollback? En EF5 utilicé TransactionScope que no tenía una función Rollback (solo un Completo) que me parecía lógico. Debido a razones de MS DTC, ya no puedo usar TransactionScope, pero tampoco puedo usar una captura de prueba como en el ejemplo anterior (es decir, solo necesito la confirmación).

El perro de las galletas
fuente
1
¿Ha leído sobre transacciones en sql ? EF intenta imitar eso. AFAIK, si no confirma una transacción en sql, se revierte.
gunr2171
Consulte también esta pregunta .
gunr2171
Sí, conozco las transacciones en SQL. Tenía curiosidad por lo que hace EF, pero si lo imitan, tiene sentido. Veré si puedo solucionarlo. ¡Gracias!
The Cookies Dog
SaveChanges () siempre ocurre en una transacción que se revertirá si ocurre una excepción. En su caso, no es necesario tratar de manejar esto manualmente (en este caso particular, sería aún mejor agregar todas las entidades primero y SaveChangessolo una vez).
Pawel
Solo quiero que se guarden elementos de ambos SaveChanges cuando ambos no fallan, así que sí, necesito una sola transacción alrededor de ambos.
The Cookies Dog

Respuestas:

117

No necesita llamar Rollbackmanualmente porque está usando la usingdeclaración.

DbContextTransaction.Disposese llamará al método al final del usingbloque. Y revertirá automáticamente la transacción si la transacción no se confirma correctamente (no se llama o no se encuentran excepciones). A continuación se muestra el código fuente del SqlInternalTransaction.Disposemétodo ( DbContextTransaction.Disposefinalmente se lo delegará cuando use el proveedor SqlServer):

private void Dispose(bool disposing)
{
    // ...
    if (disposing && this._innerConnection != null)
    {
        this._disposing = true;
        this.Rollback();
    }
}

Verá, verifica si _innerConnectionno es nulo, si no, deshace la transacción (si se confirma, _innerConnectionserá nulo). Veamos que Commithace:

internal void Commit() 
{
    // Ignore many details here...

    this._innerConnection.ExecuteTransaction(...);

    if (!this.IsZombied && !this._innerConnection.IsYukonOrNewer)
    {
        // Zombie() method will set _innerConnection to null
        this.Zombie();
    }
    else
    {
        this.ZombieParent();
    }

    // Ignore many details here...
}

internal void Zombie()
{
    this.ZombieParent();

    SqlInternalConnection innerConnection = this._innerConnection;

    // Set the _innerConnection to null
    this._innerConnection = null;

    if (innerConnection != null)
    {
        innerConnection.DisconnectTransaction(this);
    }
}
Mouhong Lin
fuente
23

Siempre que siempre utilice SQL Server con EF, no es necesario utilizar explícitamente la captura para llamar al método Rollback. Permitir que el bloque de uso retroceda automáticamente en cualquier excepción siempre funcionará.

Sin embargo, cuando lo piensa desde el punto de vista de Entity Framework, puede ver por qué todos los ejemplos usan la llamada explícita para deshacer la transacción. Para EF, el proveedor de la base de datos es arbitrario y conectable y el proveedor puede ser reemplazado por MySQL o cualquier otra base de datos que tenga una implementación de proveedor EF. Por lo tanto, desde el punto de vista de EF, no hay garantía de que el proveedor revertirá automáticamente la transacción dispuesta, porque el EF no conoce la implementación del proveedor de la base de datos.

Por lo tanto, como una mejor práctica, la documentación de EF recomienda que se deshaga explícitamente, en caso de que algún día cambie de proveedor a una implementación que no se deshaga automáticamente al eliminar.

En mi opinión, cualquier proveedor bueno y bien escrito revertirá automáticamente la transacción en el desecho, por lo que el esfuerzo adicional para envolver todo dentro del bloque using con un try-catch-rollback es excesivo.

Rwb
fuente
1
Gracias por esta información. Me sumergí en el código y terminaste en la clase abstracta DbTransaction's Dispose, que se anula en SqlTransaction, que a su vez llama a SqlInternalTransaction mencionado por Mouhong Lin.
ShawnFumo
4
  1. Dado que ha escrito un bloque 'using' para crear una instancia de la transacción, no es necesario que mencione la función Rollback explícitamente, ya que se revertiría automáticamente (a menos que se comprometiera) en el momento de la eliminación.
  2. Pero si lo instancia sin un bloque de uso, en ese caso es esencial revertir la transacción en caso de una excepción (precisamente en un bloque de captura) y eso también con una verificación nula para un código más robusto. El funcionamiento de BeginTransaction es diferente al alcance de la transacción (que solo necesita una función completa si todas las operaciones se completaron con éxito). En cambio, es similar al funcionamiento de las transacciones SQL.
roopaliv
fuente