microsoft aspnet c# linq transactions asp.net-membership

c# - microsoft aspnet identity



Membership.GetUser() dentro de TransactionScope lanza TransactionPromotionException (2)

El siguiente código arroja una TransactionAbortedException con el mensaje "La transacción ha abortado" y una TransactionPromotionException interna con el mensaje "Error al intentar promover la transacción":

using ( TransactionScope transactionScope = new TransactionScope() ) { try { using ( MyDataContext context = new MyDataContext() ) { Guid accountID = new Guid( Request.QueryString[ "aid" ] ); Account account = ( from a in context.Accounts where a.UniqueID.Equals( accountID ) select a ).SingleOrDefault(); IQueryable < My_Data_Access_Layer.Login > loginList = from l in context.Logins where l.AccountID == account.AccountID select l; foreach ( My_Data_Access_Layer.Login login in loginList ) { MembershipUser membershipUser = Membership.GetUser( login.UniqueID ); } [... lots of DeleteAllOnSubmit() calls] context.SubmitChanges(); transactionScope.Complete(); } } catch ( Exception E ) { [... reports the exception ...] } }

El error ocurre en la llamada a Membership.GetUser() .

Mi cadena de conexión es:

<add name="MyConnectionString" connectionString="Data Source=localhost/SQLEXPRESS;Initial Catalog=MyDatabase;Integrated Security=True" providerName="System.Data.SqlClient" />

Todo lo que he leído me dice que TransactionScope debería aplicarse mágicamente a las llamadas de Membresía. El usuario existe (esperaría un retorno nulo de lo contrario)


En un nivel, es correcto; la transacción siempre se cancela (no está llamando a Complete ()). ¿Es ese el código exacto?

Además, tener el DataContext fuera de TransactionScope me hace sospechar que podría estar haciendo cosas raras, ya que la transacción no está allí cuando se crea el contexto de datos. Has probado (ambos):

  • revertir el orden de creación, por lo que TransactionScope abarca el DataContext
  • llamando Complete

?

using ( TransactionScope transactionScope = new TransactionScope() ) using ( MyDataContext context = new MyDataContext() ) { /* ... */ transactionScope.Complete(); }


La clase TransactionScope enmascara excepciones. Lo más probable es que algo dentro de ese alcance esté fallando (lanzando una excepción), y la excepción TransactionAbortedException es simplemente un efecto secundario que ocurre cuando el control sale del bloque que using .

Intente envolver todo dentro de TransactionScope en un bloque try-catch , con un nuevo lanzamiento dentro del catch , y establezca un punto de interrupción allí; deberías poder ver cuál es el error real.

Otra cosa, TransactionScope.Complete debe ser la última instrucción ejecutada antes del final del bloque de using que contiene TransactionScope . En este caso, probablemente deberías estar bien, ya que en realidad no estás haciendo ningún trabajo después, pero poner la llamada a Complete dentro de un alcance interno tiende a generar un código más propenso a errores.

Actualizar:

Ahora que sabemos cuál es la excepción interna (transacción de promoción de fallas), está más claro qué está sucediendo.

El problema es que dentro de TransactionScope , en realidad está abriendo otra conexión de base de datos con GetUser . El proveedor de membresía no sabe cómo reutilizar el DataContext que ya tiene abierto; tiene que abrir su propia conexión, y cuando TransactionScope ve esto, intenta promocionar a una transacción distribuida.

Está fallando porque es probable que haya inhabilitado MSDTC en el servidor web, el servidor de la base de datos o ambos.

No hay forma de evitar la transacción distribuida si va a abrir dos conexiones separadas, por lo que en realidad hay algunas formas de solucionar este problema:

  1. Mueva las llamadas a GetUser fuera de TransactionScope . Es decir, "leer" primero a los usuarios del proveedor de membresía en una lista, luego iniciar la transacción cuando realmente necesite comenzar a hacer modificaciones.

  2. Elimine las llamadas a GetUser completo y lea la información del usuario directamente desde la base de datos, en el mismo DataContext o al menos con la misma conexión.

  3. Habilite DTC en todos los servidores que participan en la transacción (el rendimiento se verá afectado cuando se promueva una transacción).

Creo que la opción n. ° 1 será la mejor en este escenario; es muy poco probable que los datos que necesita leer del proveedor de membresía se cambien entre el momento en que lo lea y el momento en que comienza la transacción.