recorrer framework examples example ejemplos c# .net linq entity-framework iqueryable

framework - recorrer iqueryable c#



Implementar el contenedor IQueryable para traducir objetos de resultados (2)

Muy bien aquí es mi mejor para responder a esta

¿Por qué el IQueryable tiene un proveedor que a su vez devuelve un IQueryable nuevamente? ¿No exige esto una recursión sin fin? Desea devolver un IQueryable para esta instancia

SomeEnumerable.Where(x=>x.Field == something).Select(x=>x.SomeOtherField) Piense JQuery si está familiarizado con el encadenamiento

¿Por qué no es suficiente implementar IEnumerator? ¿Por qué FirstOrDefault, por ejemplo, no usa el enumerador para obtener el elemento? Cuando depuré, la aplicación GetEnumerator () no fue llamada por FirstOrDefault () en mi consulta.

Como IQueryable tiene 2 propiedades especiales, proveedor de consultas y expresiones de consulta:

¿Cuál es la diferencia entre IQueryable <T> y IEnumerable <T>?

Como el enumerador no se usa en todos los casos, ¿dónde está el punto correcto para llamar a la función de traducción? Los métodos de ejecución del QueryProvider parecían ser el lugar correcto. ¿Pero todavía necesito la llamada de traducción en el Enumerador para algunos casos?

Ejecutar es el lugar correcto, tendrá que analizar el árbol de expresiones porque el proveedor no tiene idea de qué hacer cuando ejecuta.

También he agregado este enlace que me ayudó enormemente cuando implementé mi propio proveedor de consultas http://blogs.msdn.com/b/mattwar/archive/2008/11/18/linq-links.aspx

Lo que podría lograr es usar la misma forma en que el autor en esta publicación toma el dbReader y lo convierte en objetos reales, pero en lugar del lector, toma su DBEntity y la convierte en su BusinessEntity, pero no estoy seguro si Esto es posible. Debido a que cada vez que agrega una cláusula linq (Select, Where ...), se crea una nueva consulta de ese tipo de retorno, por lo que si tenía una entidad de "Entidades" DBEntity y hacía entidades. Seleccione (x => x.someField) y algún campo es, digamos, de tipo int, ahora es IQueryable, ahora su modelo no funciona porque está esperando int y está recibiendo DBEntitity

Actualización 2013-08-22:

Después de echar un vistazo a ''Construyendo una serie de proveedores de IQueryable'' (¡Gracias por el enlace!), Llegué un poco más lejos. Actualicé el código en consecuencia. Sin embargo, todavía no está funcionando completamente. Si entiendo el tutorial correctamente, se invoca el GetEnumerator en caso de que se soliciten múltiples elementos (por ejemplo, mediante una llamada ToList() en el consultable, o cualquier función de agregación). Por lo tanto, toda la implementación de GetEnumerator del envoltorio debe hacer es llamar a Execute en el proveedor y pasar la expresión del consultable. En el otro caso, si solo se solicita un único elemento, Execute se llama directamente. La expresión de la consulta también refleja si es para uno o varios elementos. ¿Es esto correcto?

Desafortunadamente, ahora recibo una InvalidOperationException que dice ''La secuencia contiene más de un elemento'' cuando se llama a Execute en el proveedor de consultas de origen. ¿Qué significa esto? Acabo de pasar la expresión sin ninguna traducción ya que los mismos tipos están involucrados como se mencionó anteriormente. El bit de traducción con IEnumerable en el código es probablemente incompleto, pero por ahora ni siquiera llego a ese punto.

Estoy tratando de implementar un contenedor de IQueryable simple utilizando un único IQueryable subyacente como fuente de datos que llama a una función de traducción para cada objeto de resultado.

Pensé que esto sería relativamente trivial ya que lo único que tiene que hacer el envoltorio es traducir. Sin embargo no pude hacer que mi implementación funcionara.

Vea abajo para lo que tengo hasta ahora. Para algunas consultas funciona, pero recibo una excepción StackOverflowException InvalidOperationException en algún momento. Supongo que esto sucede debido a la asociación cíclica entre mi consultable y mi proveedor de consultas. Pero no entiendo cómo implementar esto correctamente.

Aquí mis preguntas y pensamientos al respecto:

1. ¿Por qué el IQueryable tiene un proveedor que a su vez devuelve un IQueryable nuevamente? ¿No exige esto una recursión sin fin?

2. ¿Por qué no es suficiente implementar IEnumerator? ¿Por qué FirstOrDefault, por ejemplo, no usa el enumerador para obtener el elemento? Cuando depuré, la aplicación GetEnumerator () no fue llamada por FirstOrDefault () en mi consulta.

3. Como el enumerador no se usa en todos los casos, ¿dónde está el punto correcto para llamar a la función de traducción? Los métodos de ejecución del QueryProvider parecían ser el lugar correcto. ¿Pero todavía necesito la llamada de traducción en el Enumerador para algunos casos? Actualización: Sé que me di cuenta de que necesito proporcionar mi propia implementación IEnumerable proporcione el TranslatingEnumerator y devolver esto enumerable desde mi método Execute . Para obtener el enumerador, GetEnumerator llama a Execute (ver más abajo). El código LINQ que solicita el enumerador parece asegurarse de que la expresión realmente devuelva un IEnumerable .

Algunas notas sobre el código:

  • El tipo de origen de la traducción se denomina TDatabaseEntity , el tipo de destino de la traducción se llama TBusinessEntity .

  • Básicamente, estoy proporcionando un IQueryable que toma los objetos de resultados recuperados de un IQueryable subyacente y los traduce a los objetos de tipo TBusinessEntity.

  • Soy consciente de que la Expresión también necesita ser traducida. Sin embargo, puse esto a un lado ya que en mi aplicación real estoy usando los mismos tipos para TBusinessEntity y TDatabaseEntity, por lo que la expresión se puede pasar directamente.

  • Sin embargo, los objetos de resultado aún deben traducirse a otras instancias, a pesar de ser del mismo tipo. Actualización: Mi capa de traducción ya está funcionando dentro de mi aplicación y también se ocupa de las entidades relacionadas. Es solo la cosa de ''implementar un envoltorio IQueryable'' con la que me quedo.

  • Me temo que toda la implementación es incorrecta : las TODO en el código son solo mis propias notas.

Antecedentes: Estoy implementando mi propia separación de entidades recibidas de DbContext dentro de mi capa de acceso a datos para evitar que mi capa de negocios se ponga en contacto con las entidades reales, debido a algunos errores con EF y otros requisitos que no puedo directamente Utilice entidades separadas EF.

¡Gracias por tu ayuda!

Implementación IQueryable

internal class TranslatingQueryable<TDatabaseEntity, TBusinessEntity> : IQueryable<TBusinessEntity> { private readonly IQueryProvider _provider; private readonly IQueryable<TDatabaseEntity> _source; internal TranslatingQueryable(TranslatingQueryProvider provider, IQueryable<TDatabaseEntity> source) { Guard.ThrowIfArgumentNull(provider, "provider"); Guard.ThrowIfArgumentNull(source, "source"); _provider = provider; _source = source; } internal TranslatingQueryable(Func<object, object> translateFunc, IQueryable<TDatabaseEntity> databaseQueryable) : this(new TranslatingQueryProvider(translateFunc, databaseQueryable.Provider), databaseQueryable) { } public IEnumerator<TBusinessEntity> GetEnumerator() { return ((IEnumerable<TBusinessEntity>)Provider.Execute(Expression)).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)Provider.Execute(Expression)).GetEnumerator(); } public Expression Expression { get { return _source.Expression; } } public Type ElementType { get { return typeof(TBusinessEntity); } } public IQueryProvider Provider { get { return _provider; } } }

Implementación de IQueryProvider

public class TranslatingQueryProvider : IQueryProvider { private readonly Func<object, object> _translateFunc; private readonly IQueryProvider _databaseQueryProvider; public TranslatingQueryProvider(Func<object, object> translateFunc, IQueryProvider databaseQueryProvider) { _translateFunc = translateFunc; _databaseQueryProvider = databaseQueryProvider; } public IQueryable CreateQuery(Expression expression) { var databaseQueryable = _databaseQueryProvider.CreateQuery<object>(expression); return new TranslatingQueryable<object, object>(this, databaseQueryable); } public IQueryable<TElement> CreateQuery<TElement>(Expression expression) { var databaseQueryable = _databaseQueryProvider.CreateQuery<object>(expression); return new TranslatingQueryable<object, TElement>(this, databaseQueryable); } public object Execute(Expression expression) { return Execute<object>(expression); } public TResult Execute<TResult>(Expression expression) { // TODO This call throws an InvalidOperationException if an enumeration is requested var databaseResult = _databaseQueryProvider.Execute<TResult>(expression); var databaseEnumerable = databaseResult as IEnumerable; if (databaseEnumerable != null) { if (typeof(TResult).IsAssignableFrom(typeof(IEnumerable))) { throw new InvalidOperationException(); } return (TResult)(object)new TranslatingEnumerable(databaseEnumerable, _translateFunc); } else { return (TResult)_translateFunc(databaseResult); } } private class TranslatingEnumerable : IEnumerable { private readonly TranslatingEnumerator _enumerator; public TranslatingEnumerable(IEnumerable databaseEnumerable, Func<object, object> translateFunc) { _enumerator = new TranslatingEnumerator(translateFunc, databaseEnumerable.GetEnumerator()); } public IEnumerator GetEnumerator() { return _enumerator; } } }

Implementación de IEnumerator

internal class TranslatingEnumerator : IEnumerator { private readonly Func<object, object> _translateFunc; private readonly IEnumerator _databaseEnumerator; internal TranslatingEnumerator(Func<object, object> translateFunc, IEnumerator databaseEnumerator) { _translateFunc = translateFunc; _databaseEnumerator = databaseEnumerator; } public bool MoveNext() { return _databaseEnumerator.MoveNext(); } public void Reset() { _databaseEnumerator.Reset(); } public object Current { get { return _translateFunc(_databaseEnumerator.Current); } } object IEnumerator.Current { get { return Current; } } }


Ya descubrí por qué recibí una excepción cada vez que se enumeraba la consulta: la infraestructura IQueryable de Entity Framework se implementa de manera muy diferente al patrón descrito en Creación de una serie de proveedores IQueryable, pt. 1 .

  • La publicación del blog sugiere implementar GetEnumerator() llamando a Execute() en el proveedor .

  • En contraste, en la infraestructura de EF, el método Execute() ObjectQueryProvider solo acepta expresiones que devuelven un solo objeto de resultado, pero no una enumerable colección de objetos de resultado (esto incluso está documentado en el código fuente). En consecuencia, el método GetEnumerator() ObjectQuery no llama a Execute() sino a otro método que obtiene el resultado directamente de la base de datos .

Por lo tanto, cualquier implementación de IQueryable de traducción que use una consulta de base de datos subyacente para obtener los objetos debe usar el mismo patrón: el GetEnumerator() traducción GetEnumerator() simplemente llama a GetEnumerator() en la consulta de base de datos subyacente e inyecta esto en un nuevo TranslatingEnumerator .