tutorial sintaxis operadores framework español ejemplos consultas consulta anidadas c# linq linq-to-sql asynchronous

c# - sintaxis - Cómo escribir consultas asincrónicas LINQ?



operadores en linq c# (4)

Basado en la respuesta de Michael Freidgeim y la mencionada publicación de blog de Scott Hansellman y el hecho de que puede usar async / await , puede implementar el ExecuteAsync<T>(...) reutilizable, que ejecuta SqlCommand subyacente de forma asincrónica:

protected static async Task<IEnumerable<T>> ExecuteAsync<T>(IQueryable<T> query, DataContext ctx, CancellationToken token = default(CancellationToken)) { var cmd = (SqlCommand)ctx.GetCommand(query); if (cmd.Connection.State == ConnectionState.Closed) await cmd.Connection.OpenAsync(token); var reader = await cmd.ExecuteReaderAsync(token); return ctx.Translate<T>(reader); }

Y luego puedes (re) usarlo así:

public async Task WriteNamesToConsoleAsync(string connectionString, CancellationToken token = default(CancellationToken)) { using (var ctx = new DataContext(connectionString)) { var query = from item in Products where item.Price > 3 select item.Name; var result = await ExecuteAsync(query, ctx, token); foreach (var name in result) { Console.WriteLine(name); } } }

Después de leer un montón de cosas relacionadas con LINQ, de repente me di cuenta de que ningún artículo presentaba cómo escribir consultas LINQ asíncronas.

Supongamos que usamos LINQ to SQL, la declaración a continuación es clara. Sin embargo, si la base de datos SQL responde lentamente, el hilo que utiliza este bloque de código se verá obstaculizado.

var result = from item in Products where item.Price > 3 select item.Name; foreach (var name in result) { Console.WriteLine(name); }

Parece que la especificación actual de consulta LINQ no proporciona soporte para esto.

¿Hay alguna forma de hacer una programación asincrónica LINQ? Funciona como si hubiera una notificación de devolución de llamada cuando los resultados están listos para usar sin ningún retraso de bloqueo en E / S.


Inicié un proyecto simple de github llamado Asynq para realizar una ejecución asíncrona de consultas LINQ-to-SQL. La idea es bastante simple, aunque "frágil" en esta etapa (a partir del 16/08/2011):

  1. Deje que LINQ-to-SQL haga el trabajo "pesado" de traducir su IQueryable en un DbCommand través de DataContext.GetCommand() .
  2. Para SQL 200 [058], DbCommand instancia de DbCommand abstracta que obtuviste de GetCommand() para obtener un SqlCommand . Si está utilizando SQL CE no tiene suerte, ya que SqlCeCommand no expone el patrón asíncrono para BeginExecuteReader y EndExecuteReader .
  3. Utilice BeginExecuteReader y EndExecuteReader fuera de SqlCommand utilizando el patrón de E / S asíncronas de .NET Framework estándar para obtener un DbDataReader en el delegado de devolución de llamada de finalización que pase al método BeginExecuteReader .
  4. Ahora tenemos un DbDataReader que no tenemos idea de qué columnas contiene ni cómo volver a asignar esos valores al IQueryable de ElementType (lo más probable es que sea un tipo anónimo en el caso de las uniones). Claro, en este punto podría escribir a mano su propio mapeador de columnas que materializa sus resultados en su tipo anónimo o lo que sea. Tendría que escribir uno nuevo para cada tipo de resultado de consulta, dependiendo de cómo trate LINQ-to-SQL su IQueryable y el código SQL que genera. Esta es una opción bastante desagradable y no la recomiendo, ya que no es mantenible ni tampoco será siempre correcta. LINQ-to-SQL puede cambiar su formulario de consulta dependiendo de los valores de parámetros que pase, por ejemplo query.Take(10).Skip(0) produce SQL diferente de query.Take(10).Skip(10) , y quizás un esquema de resultados diferente. Su mejor apuesta es manejar este problema de materialización programáticamente:
  5. " DbDataReader " un materializador de objetos en tiempo de ejecución simplificado que saca columnas del DbDataReader en un orden definido de acuerdo con los atributos de mapeo LINQ-to-SQL de ElementType Type para IQueryable . Implementar esto correctamente es probablemente la parte más desafiante de esta solución.

Como otros han descubierto, el método DataContext.Translate() no maneja tipos anónimos y solo puede asignar un DbDataReader directamente a un objeto proxy LINQ-to-SQL atribuido correctamente. Dado que la mayoría de las consultas que valen la pena escribir en LINQ implican combinaciones complejas que inevitablemente requieren tipos anónimos para la cláusula de selección final, no tiene sentido utilizar el método diluido proporcionado DataContext.Translate() .

Esta solución presenta algunos inconvenientes menores cuando aprovecha el proveedor de IQueryable LINQ-SQL maduro existente:

  1. No puede asignar una única instancia de objeto a múltiples propiedades de tipo anónimo en la cláusula de selección final de su IQueryable , por ejemplo, from x in db.Table1 select new { a = x, b = x } . LINQ-to-SQL realiza un seguimiento interno de los ordinales de columna que se asignan a qué propiedades; no expone esta información al usuario final, por lo que no tiene idea de qué columnas del DbDataReader se reutilizan y cuáles son "distintas".
  2. No puede incluir valores constantes en la cláusula de selección final; estos no se traducen a SQL y estarán ausentes de DbDataReader por lo que tendría que crear una lógica personalizada para extraer estos valores constantes del árbol de Expression IQueryable, lo que haría ser bastante molesto y simplemente no es justificable.

Estoy seguro de que hay otros patrones de consulta que podrían romperse, pero estos son los dos más grandes que podría pensar que podrían causar problemas en una capa existente de acceso a datos LINQ-a-SQL.

Estos problemas son fáciles de superar, simplemente no los haga en sus consultas ya que ningún patrón proporciona ningún beneficio al resultado final de la consulta. Esperamos que este consejo se aplique a todos los patrones de consulta que podrían causar problemas de materialización del objeto :-P. Es un problema difícil de resolver al no tener acceso a la información de asignación de columnas de LINQ-to-SQL.

Un enfoque más "completo" para resolver el problema sería implementar de nuevo de manera efectiva casi todo LINQ-to-SQL, lo que requiere un poco más de tiempo :-P. A partir de una implementación de proveedores de LINQ-a-SQL de código abierto de calidad sería una buena manera de hacerlo aquí. La razón por la que debe volver a implementarla es para tener acceso a toda la información de asignación de columnas utilizada para materializar los resultados de DbDataReader en una instancia de objeto sin pérdida de información.


Las soluciones de TheSoftwareJedi y ulrikb (también conocido como user316318) son buenas para cualquier tipo de LINQ, pero (como señala Chris Moschini ) NO delegan en las llamadas asincrónicas subyacentes que aprovechan los puertos de finalización de E / S de Windows.

La publicación Asynchronous DataContext de Wesley Bakker (desencadenada por una publicación de blog de Scott Hanselman ) describe la clase para LINQ to SQL que usa sqlCommand.BeginExecuteReader / sqlCommand.EndExecuteReader, que aprovecha los puertos de finalización de E / S de Windows.

Los puertos de terminación de E / S proporcionan un modelo de subprocesamiento eficiente para procesar múltiples solicitudes de E / S asíncronas en un sistema multiprocesador.


Si bien LINQ no tiene esto en sí mismo, el marco en sí sí lo hace ... Puede ejecutar fácilmente su propio ejecutor de consultas asincrónicas en 30 líneas más o menos ... De hecho, acabo de lanzar esto para usted :)

EDITAR: al escribir esto, descubrí por qué no lo implementaron. No puede manejar tipos anónimos ya que tienen un alcance local. Por lo tanto, no tiene forma de definir su función de devolución de llamada. Esto es algo muy importante ya que muchas cosas de linq a sql las crean en la cláusula de selección. Cualquiera de las siguientes sugerencias sufre el mismo destino, ¡así que todavía creo que esta es la más fácil de usar!

EDITAR: La única solución es no usar tipos anónimos. Puede declarar la devolución de llamada simplemente tomando IEnumerable (sin args de tipo), y use reflection para acceder a los campos (ICK !!). Otra forma sería declarar la devolución de llamada como "dinámica" ... oh ... espera ... Aún no ha salido. :) Este es otro ejemplo decente de cómo se podría utilizar la dinámica. Algunos pueden llamarlo abuso.

Tíralo en tu biblioteca de utilidades:

public static class AsynchronousQueryExecutor { public static void Call<T>(IEnumerable<T> query, Action<IEnumerable<T>> callback, Action<Exception> errorCallback) { Func<IEnumerable<T>, IEnumerable<T>> func = new Func<IEnumerable<T>, IEnumerable<T>>(InnerEnumerate<T>); IEnumerable<T> result = null; IAsyncResult ar = func.BeginInvoke( query, new AsyncCallback(delegate(IAsyncResult arr) { try { result = ((Func<IEnumerable<T>, IEnumerable<T>>)((AsyncResult)arr).AsyncDelegate).EndInvoke(arr); } catch (Exception ex) { if (errorCallback != null) { errorCallback(ex); } return; } //errors from inside here are the callbacks problem //I think it would be confusing to report them callback(result); }), null); } private static IEnumerable<T> InnerEnumerate<T>(IEnumerable<T> query) { foreach (var item in query) //the method hangs here while the query executes { yield return item; } } }

Y podrías usarlo así:

class Program { public static void Main(string[] args) { //this could be your linq query var qry = TestSlowLoadingEnumerable(); //We begin the call and give it our callback delegate //and a delegate to an error handler AsynchronousQueryExecutor.Call(qry, HandleResults, HandleError); Console.WriteLine("Call began on seperate thread, execution continued"); Console.ReadLine(); } public static void HandleResults(IEnumerable<int> results) { //the results are available in here foreach (var item in results) { Console.WriteLine(item); } } public static void HandleError(Exception ex) { Console.WriteLine("error"); } //just a sample lazy loading enumerable public static IEnumerable<int> TestSlowLoadingEnumerable() { Thread.Sleep(5000); foreach (var i in new int[] { 1, 2, 3, 4, 5, 6 }) { yield return i; } } }

Voy a poner esto en mi blog ahora, bastante útil.