2010-09-15 6 views
7

Según NHProf, el uso de transacciones implícitas se desanima:¿Cómo guardo un IQueryable <> dentro de una transacción usando el patrón de repositorio?

http://nhprof.com/Learn/Alerts/DoNotUseImplicitTransactions

Sin embargo, NHibernate LINQ devuelve un IQueryable<> al leer objetos de la base de datos, y esto se evalúa pereza. Tengo este método en un repositorio:

public IQueryable<T> GetAll<T>() 
{ 
    using (var transaction = _session.BeginTransaction()) 
    { 
     var data = _session.Linq<T>(); 
     transaction.Commit(); 
     return data; 
    } 
} 

El problema aquí es que el método va a confirmar la transacción antes de evaluar data. ¿Hay alguna manera de utilizar el patrón de repositorio y mantener el IQueryable<> en una transacción explícita? ¿O es aceptable que las operaciones de lectura usen transacciones implícitas?

+4

¿Por qué querría una transacción en torno a una operación de lectura? –

+3

Ah, del artículo: "Incluso si solo estamos leyendo datos, deberíamos usar una transacción, porque el uso de transacciones asegura que obtengamos resultados consistentes de la base de datos. NHibernate supone que todo el acceso a la base de datos se realiza en una transacción, y desaconseja fuertemente cualquier uso de la sesión sin una transacción ". Sin embargo, todavía no entiendo por qué el autor está haciendo esta declaración. –

Respuesta

5

El repositorio debe no crear una transacción. Esa es una responsabilidad de una capa separada (que depende del tipo de aplicación).

+0

Acepto, supongo que su explicación de por qué cree que es responsabilidad de una capa separada es algo así como: Los límites de las transacciones deberían definirse por la forma en que el usuario u otras aplicaciones interactúan con la aplicación. Ejemplo para aplicaciones web por solicitud, para servicios por llamada de servicio, para aplicaciones de Windows por "hacer clic en un botón", para juegos por jugada, etc. Cuando las transacciones son creadas por el repositorio, será mucho más difícil hacer el repositorio Independiente de la forma en que la aplicación está interactuando con él, y también puede hacer que sea más difícil de usar en general. – Paco

3

Refactorizaría esto para permitir el control de la transacción externa. El Repositorio no puede conocer el alcance de la unidad de trabajo de la que varias llamadas de lectura/escritura son parte, a menos que el código que las hace lo indique. Considere configurar un patrón de "unidad de trabajo": sin revelar detalles específicos de la implementación del almacén de datos, permita que los objetos que dependen del repositorio especifiquen que están comenzando, cancelando o completando una "unidad de trabajo".

public interface IRepository 
{ 
    public UnitOfWork BeginUnitOfWork() 

    public void CommitUOW(UnitOfWork unit) 

    public void AbortUOW(UnitOfWork unit) 

    public IQueryable<T> GetAll<T>(UnitOfWork unit) 

    public List<T> GetAll<T>() 

    public void Store<T>(T theObject, UnitOfWork unit) 

    public void Store<T>(T theObject) 
} 

su repositorio, probablemente implementar esta manteniendo un diccionario privado de transacciones SQL, cada uno afinado a un objeto UnitOfWork (esto puede ser tan simple como una clase instanciable vacío, o puede proporcionar información sobre el marco agnóstica sobre el estado o métricas). Al realizar una operación de base de datos, las personas que llaman primero pedirán que comiencen una UoW, y se les dará un token que usarán para identificar el contexto dentro del cual están haciendo una llamada al DB. El objeto que obtiene el token puede pasarlo a otras clases que necesitan realizar operaciones de DB en el mismo contexto operacional. La unidad de trabajo permanecerá abierta hasta que la clase dependiente le indique al Depósito que está terminada, lo que permite cargas perezosas y procedimientos de operación múltiple atómicos.

Observe que hay sobrecargas que no requieren unidades de trabajo. Es posible, y tal vez necesario, hacer llamadas simples sin iniciar explícitamente una unidad de trabajo. En estos casos, su Repositorio puede crear un UOW interno, realizar la operación solicitada y devolver los resultados. Sin embargo, la carga diferida será difícil o imposible en estos casos; Tendrás que recuperar todo el conjunto de resultados en una Lista antes de finalizar la UoW interna.

1

Estoy con Diego en este caso: el repositorio no puede conocer el alcance de la transacción.

También me preocupa la posibilidad de devolver IQueryable, según tengo entendido, tiene una tonelada de métodos de consulta adicionales que pueden ser muy difíciles de probar por unidad. Prefiero devolver IEnumerable y encapsular consultas más complejas en métodos de repositorio. De lo contrario, deberá probar un todo tipo de variaciones de consultas con respecto a la salida de GetAll().

+1

¿Realmente es su trabajo probar todos los métodos en IQueryable? ¿No deberías solo ser el código de prueba unitaria que escribiste? – Gabe

+0

@Gabe: Mi preocupación no es la prueba unitaria de IQueryable, es la unidad que prueba los _resultados_ de las consultas utilizadas en la miríada de lugares. He leído que otros dicen que es 6 de 1 en cuanto a dónde vive la lógica de consulta, pero prefiero un método como GetFulfilledOrders() sobre una consulta complicada de linq para devolver lo mismo. Más fácil de probar y más claro. – n8wrl

Cuestiones relacionadas