Cuando EF o LINQ to SQL se ejecuta una consulta, es:

  1. Construye un árbol de expresión desde el código,
  2. Convierte el árbol de expresión en una consulta SQL,
  3. Ejecuta la consulta, se obtienen los resultados crudos de la base de datos y los convierte en el resultado para ser utilizado por la aplicación.

Mirando a la traza de la pila, yo no puedo averiguar dónde está la segunda parte sucede.

En general, es posible utilizar una existente por parte de EF o (preferiblemente) LINQ to SQL para convertir un Expression objeto a un parcial de consulta SQL (usando la sintaxis de Transact-SQL), o me tengo que reinventar la rueda?


Actualización: un comentario solicita a dar un ejemplo de lo que estoy tratando de hacer.

Realidad, la respuesta por Ryan Wright a continuación ilustra a la perfección lo que quiero lograr como resultado, excepto el hecho de que mi pregunta es específicamente sobre ¿cómo puedo hacerlo mediante el uso de mecanismos existentes de .NET Framework utilizado por EF y LINQ to SQL, en lugar de tener que reinventar la rueda y escribir miles de líneas de no-tan-a prueba de código de mí mismo para hacer algo similar.

Aquí es también un ejemplo. De nuevo, tenga en cuenta que no hay ORM-código generado.

private class Product
{
    [DatabaseMapping("ProductId")]
    public int Id { get; set; }

    [DatabaseMapping("Price")]
    public int PriceInCents { get; set; }
}

private string Convert(Expression expression)
{
    //Some magic calls to .NET Framework code happen here.
    //[...]
}

private void TestConvert()
{
    Expression<Func<Product, int, int, bool>> inPriceRange =
        (Product product, int from, int to) =>
            product.PriceInCents >= from && product.PriceInCents <= to;

    string actualQueryPart = this.Convert(inPriceRange);

    Assert.AreEqual("[Price] between @from and @to", actualQueryPart);
}

¿De dónde viene el nombre de Price vienen en la espera de la consulta?

El nombre puede ser obtenida a través de la reflexión, mediante la consulta de la costumbre DatabaseMapping atributo de Price propiedad de Product clase.

Donde hacer de los nombres de @from y @to vienen en la espera de la consulta?

Esos nombres son los nombres reales de los parámetros de la expresión.

¿De dónde between … and vienen en la espera de la consulta?

Este es un posible resultado de una expresión binaria. Tal vez EF o LINQ to SQL sería, en lugar de between … and declaración de palo, con [Price] >= @from and [Price] <= @to lugar. Es aceptar demasiado, realmente no importa ya que el resultado es, lógicamente, el mismo (no estoy mencionando rendimiento).

Por qué no hay where en la espera de la consulta?

Porque nada indica en la Expression que no debe ser un where palabra clave. Quizás el real expresión es sólo una de las expresiones que se ubicaría más tarde con operadores binarios para construir una más grande de consulta para anteponer con un where.

7 Comentarios

  1. 23

    La respuesta corta es que parece ser que usted no puede utilizar una parte de EF o LINQ to SQL como un acceso directo a la traducción. Usted necesita por lo menos una subclase de ObjectContext para llegar a la internal protected QueryProvider de la propiedad, lo que significa que toda la sobrecarga de crear el contexto, incluyendo todos los metadatos y así sucesivamente.

    Asumiendo que usted está bien con eso, para conseguir un parcial de consulta SQL, por ejemplo, sólo el WHERE cláusula, básicamente, va a necesitar el proveedor de consultas y la convocatoria IQueryProvider.CreateQuery() como LINQ en su aplicación de Consultable.Donde. Para obtener una visión más completa de consulta se pueden utilizar ObjectQuery.ToTraceString().

    Donde esto sucede, Proveedor de LINQ lo basico los estados en general que

    IQueryProvider devuelve una referencia a IQueryable < t > con la construcción de la expresión-árbol pasó por el LINQ marco, que se utiliza para las llamadas posteriores. En términos generales, cada consulta bloque se convierte en un montón de llamadas a métodos. Para cada llamada al método, hay algunas expresiones involucradas. Mientras que la creación de nuestro proveedor en el método IQueryProvider.CreateQuery – tenemos que ejecutar a través de las expresiones y de relleno de un objeto de filtro, que se utiliza en la IQueryProvider.Método Execute para ejecutar una consulta contra el almacén de datos

    y que

    la consulta puede ser ejecutado de dos maneras, ya sea mediante la aplicación del método GetEnumerator (definido en la interfaz IEnumerable) en la clase de Consulta, (que se hereda de IQueryable < t>); o puede ser ejecutado por el LINQ de tiempo de ejecución directamente

    Comprobación de EF, en el depurador es el ex.

    Si no quieres volver a inventar la rueda, y ni EF ni LINQ to SQL son las opciones, tal vez esta serie de artículos, sería de ayuda:

    Aquí están algunas de las fuentes para la creación de un proveedor de consultas que implican probablemente mucho más pesada de elevación de su parte para poner en práctica lo que desea:

    • No puedo creer que No existe una solución terminada para convertir una Expresión a un (string) Instrucción SQL? Buscado en GitHub y la galería de NuGet + de Google, pero sin ningún resultado… Si alguien sabe mantener la solución, por favor me avise!
    • Estoy de acuerdo, una solución como la que acaba de obtener un árbol de expresión y generar raw SQL traducción sería un increíble ajuste a la especificación de patrón. Me gustaría encontrar algo o tal vez obtener un pequeño grupo para empezar algo.
  2. 42

    Sí es posible, se puede analizar una expresión LINQ árbol mediante el visitante patrón. Usted necesita para construir una consulta traductor por subclases de ExpressionVisitor como el de abajo. Por enganchar en los puntos correctos puedes usar el traductor para la construcción de su cadena SQL a partir de su expresión LINQ. Tenga en cuenta que el código de abajo sólo se ocupa de basic donde/orderby/saltar/tomar cláusulas, pero se puede completar con más según sea necesario. Esperemos que sirve como un buen primer paso.

    public class MyQueryTranslator : ExpressionVisitor
    {
    private StringBuilder sb;
    private string _orderBy = string.Empty;
    private int? _skip = null;
    private int? _take = null;
    private string _whereClause = string.Empty;
    public int? Skip
    {
    get
    {
    return _skip;
    }
    }
    public int? Take
    {
    get
    {
    return _take;
    }
    }
    public string OrderBy
    {
    get
    {
    return _orderBy;
    }
    }
    public string WhereClause
    {
    get
    {
    return _whereClause;
    }
    }
    public MyQueryTranslator()
    {
    }
    public string Translate(Expression expression)
    {
    this.sb = new StringBuilder();
    this.Visit(expression);
    _whereClause = this.sb.ToString();
    return _whereClause;
    }
    private static Expression StripQuotes(Expression e)
    {
    while (e.NodeType == ExpressionType.Quote)
    {
    e = ((UnaryExpression)e).Operand;
    }
    return e;
    }
    protected override Expression VisitMethodCall(MethodCallExpression m)
    {
    if (m.Method.DeclaringType == typeof(Queryable) && m.Method.Name == "Where")
    {
    this.Visit(m.Arguments[0]);
    LambdaExpression lambda = (LambdaExpression)StripQuotes(m.Arguments[1]);
    this.Visit(lambda.Body);
    return m;
    }
    else if (m.Method.Name == "Take")
    {
    if (this.ParseTakeExpression(m))
    {
    Expression nextExpression = m.Arguments[0];
    return this.Visit(nextExpression);
    }
    }
    else if (m.Method.Name == "Skip")
    {
    if (this.ParseSkipExpression(m))
    {
    Expression nextExpression = m.Arguments[0];
    return this.Visit(nextExpression);
    }
    }
    else if (m.Method.Name == "OrderBy")
    {
    if (this.ParseOrderByExpression(m, "ASC"))
    {
    Expression nextExpression = m.Arguments[0];
    return this.Visit(nextExpression);
    }
    }
    else if (m.Method.Name == "OrderByDescending")
    {
    if (this.ParseOrderByExpression(m, "DESC"))
    {
    Expression nextExpression = m.Arguments[0];
    return this.Visit(nextExpression);
    }
    }
    throw new NotSupportedException(string.Format("The method '{0}' is not supported", m.Method.Name));
    }
    protected override Expression VisitUnary(UnaryExpression u)
    {
    switch (u.NodeType)
    {
    case ExpressionType.Not:
    sb.Append(" NOT ");
    this.Visit(u.Operand);
    break;
    case ExpressionType.Convert:
    this.Visit(u.Operand);
    break;
    default:
    throw new NotSupportedException(string.Format("The unary operator '{0}' is not supported", u.NodeType));
    }
    return u;
    }
    ///<summary>
    ///
    ///</summary>
    ///<param name="b"></param>
    ///<returns></returns>
    protected override Expression VisitBinary(BinaryExpression b)
    {
    sb.Append("(");
    this.Visit(b.Left);
    switch (b.NodeType)
    {
    case ExpressionType.And:
    sb.Append(" AND ");
    break;
    case ExpressionType.AndAlso:
    sb.Append(" AND ");
    break;
    case ExpressionType.Or:
    sb.Append(" OR ");
    break;
    case ExpressionType.OrElse:
    sb.Append(" OR ");
    break;
    case ExpressionType.Equal:
    if (IsNullConstant(b.Right))
    {
    sb.Append(" IS ");
    }
    else
    {
    sb.Append(" = ");
    }
    break;
    case ExpressionType.NotEqual:
    if (IsNullConstant(b.Right))
    {
    sb.Append(" IS NOT ");
    }
    else
    {
    sb.Append(" <> ");
    }
    break;
    case ExpressionType.LessThan:
    sb.Append(" < ");
    break;
    case ExpressionType.LessThanOrEqual:
    sb.Append(" <= ");
    break;
    case ExpressionType.GreaterThan:
    sb.Append(" > ");
    break;
    case ExpressionType.GreaterThanOrEqual:
    sb.Append(" >= ");
    break;
    default:
    throw new NotSupportedException(string.Format("The binary operator '{0}' is not supported", b.NodeType));
    }
    this.Visit(b.Right);
    sb.Append(")");
    return b;
    }
    protected override Expression VisitConstant(ConstantExpression c)
    {
    IQueryable q = c.Value as IQueryable;
    if (q == null && c.Value == null)
    {
    sb.Append("NULL");
    }
    else if (q == null)
    {
    switch (Type.GetTypeCode(c.Value.GetType()))
    {
    case TypeCode.Boolean:
    sb.Append(((bool)c.Value) ? 1 : 0);
    break;
    case TypeCode.String:
    sb.Append("'");
    sb.Append(c.Value);
    sb.Append("'");
    break;
    case TypeCode.DateTime:
    sb.Append("'");
    sb.Append(c.Value);
    sb.Append("'");
    break;
    case TypeCode.Object:
    throw new NotSupportedException(string.Format("The constant for '{0}' is not supported", c.Value));
    default:
    sb.Append(c.Value);
    break;
    }
    }
    return c;
    }
    protected override Expression VisitMember(MemberExpression m)
    {
    if (m.Expression != null && m.Expression.NodeType == ExpressionType.Parameter)
    {
    sb.Append(m.Member.Name);
    return m;
    }
    throw new NotSupportedException(string.Format("The member '{0}' is not supported", m.Member.Name));
    }
    protected bool IsNullConstant(Expression exp)
    {
    return (exp.NodeType == ExpressionType.Constant && ((ConstantExpression)exp).Value == null);
    }
    private bool ParseOrderByExpression(MethodCallExpression expression, string order)
    {
    UnaryExpression unary = (UnaryExpression)expression.Arguments[1];
    LambdaExpression lambdaExpression = (LambdaExpression)unary.Operand;
    lambdaExpression = (LambdaExpression)Evaluator.PartialEval(lambdaExpression);
    MemberExpression body = lambdaExpression.Body as MemberExpression;
    if (body != null)
    {
    if (string.IsNullOrEmpty(_orderBy))
    {
    _orderBy = string.Format("{0} {1}", body.Member.Name, order);
    }
    else
    {
    _orderBy = string.Format("{0}, {1} {2}", _orderBy, body.Member.Name, order);
    }
    return true;
    }
    return false;
    }
    private bool ParseTakeExpression(MethodCallExpression expression)
    {
    ConstantExpression sizeExpression = (ConstantExpression)expression.Arguments[1];
    int size;
    if (int.TryParse(sizeExpression.Value.ToString(), out size))
    {
    _take = size;
    return true;
    }
    return false;
    }
    private bool ParseSkipExpression(MethodCallExpression expression)
    {
    ConstantExpression sizeExpression = (ConstantExpression)expression.Arguments[1];
    int size;
    if (int.TryParse(sizeExpression.Value.ToString(), out size))
    {
    _skip = size;
    return true;
    }
    return false;
    }
    }

    A continuación, visitar la expresión llamando al:

    var translator = new MyQueryTranslator();
    string whereClause = translator.Translate(expression);
    • Mientras que un buen comienzo esta parece ser la falta de cualquier manipulación de cadenas o la capacidad para comparar cadenas. E. g. Contiene, StartsWith. De nuevo, buen comienzo.
    • Yo estaba buscando una manera de extraer sólo el ‘Salto’ y ‘Tomar’ dentro de la Query() reemplazo de una costumbre System.ServiceModel.DomainServices.Server.DomainService clase (RIA), y esto era exactamente lo que estaba buscando.
    • ¿Dónde está este «Evaluador» de la clase que se encuentran?
    • Evaluador es necesario para resolver las referencias a variables locales en sus expresiones. He utilizado la aplicación de aquí. También vale la pena aunque parcialmente evaluar la expresión anterior (es decir, en el Traducir método, y quite la llamada al Evaluador de ParseOrderByExpression como este): public string Translate(Expression expression) { expression = Evaluator.PartialEval(expression);
    • THX man esto es justo lo que necesitaba. He añadido contener y voy a añadir también 😁
    • Estoy recibiendo un mensaje de error al intentar traducir DateTime.Now. Para intance: t => t.Name == "NAME" && t.OpeningDate == DateTime.Now. ¿Hay alguna solución?
    • Cualquier persona que va a utilizar para generar una cláusula where para anexar a SQL , por favor, tenga en cuenta que las comillas simples no se escapan en la resultante de la cláusula. Para que el cambio sb.Append(c.Value); a sb.Append(c.Value.ToString.Replace('"","''")); en el VisitConstantmétodo

  3. 5

    En Linq2SQL puede utilizar:

    var cmd = DataContext.GetCommand(expression);
    var sqlQuery = cmd.CommandText;
    • Su muestra es inexacta. El argumento de GetCommand no es un Expression, pero un IQueryable, y la construcción de una IQueryable requiere tener un IQueryProvider. Así que el problema siguen siendo los mismos.
    • Así que usted desea crear una consulta SQL a partir de una expresión sin QueryProvider?
    • Más precisamente, quiero crear un parcial de consultas SQL a partir de una expresión sin tener que agregar tablas de la base de datos para EF/Linq2SQL. Si he entendido bien, los proveedores de consultas utilizado por Linq2SQL dependen de esas tablas. Estoy equivocado?
  4. 5

    No es completo, pero aquí están algunas ideas para ranura en si vienes por esto más adelante:

        private string CreateWhereClause(Expression<Func<T, bool>> predicate)
    {
    StringBuilder p = new StringBuilder(predicate.Body.ToString());
    var pName = predicate.Parameters.First();
    p.Replace(pName.Name + ".", "");
    p.Replace("==", "=");
    p.Replace("AndAlso", "and");
    p.Replace("OrElse", "or");
    p.Replace("\"", "\'");
    return p.ToString();
    }
    private string AddWhereToSelectCommand(Expression<Func<T, bool>> predicate, int maxCount = 0)
    {           
    string command = string.Format("{0} where {1}", CreateSelectCommand(maxCount), CreateWhereClause(predicate));
    return command;
    }
    private string CreateSelectCommand(int maxCount = 0)
    {
    string selectMax = maxCount > 0 ? "TOP " + maxCount.ToString() + " * " : "*";
    string command = string.Format("Select {0} from {1}", selectMax, _tableName);
    return command;
    }
    • Siento tu respuesta merecía más atención aquí. Pero usted podría encontrar problemas con las llamadas de método como, con la expresión (f)=> f.SomeList.Where((g)=> g.Epicness > 30) Otra cosa a notar es que (f)=> f.Name != Environtment.MachineName de salida será algo así como «f.Nombre != ‘Medio ambiente.Nombredeequipo'», esto podría ser contraproducente.
  5. 4

    Básicamente, tendrá que volver a inventar la rueda. El QueryProvider es lo que hace la traducción de la expresión de los árboles a la tienda de sintaxis nativa. Es la cosa que va a manejar situaciones especiales, así como de la cadena.Contains(), string.StartsWith(), y todos los de la especialidad de las funciones que se encargan de ello. También va a manejar los metadatos de las búsquedas en las distintas capas de su ORM (*.edml en el caso de la base de datos de primer o modelo-primera Entity Framework). Ya hay ejemplos y los marcos para la creación de comandos SQL. Pero lo que usted está buscando suena como una solución parcial.

    También entender que la tabla o vista de metadatos es necesario para determinar correctamente lo que es legal. La consulta de los proveedores son bastante complejas y hacer un montón de trabajo para que más allá de la simple expresión de árbol de conversiones en SQL.

    En respuesta a su de dónde viene la segunda parte suceder. La segunda parte ocurre durante la enumeración de los IQueryable < t>. IQueryables también IEnumerables y en última instancia, cuando GetEnumerator se llama, a su vez, se van a llamar al proveedor de consultas con el árbol de expresión que se va a utilizar sus metadatos para producir un comando sql. No es exactamente lo que sucede, pero la idea de cruzar.

  6. 3

    Puede utilizar el código siguiente:

    var query = from c in Customers
    select c;
    string sql = ((ObjectQuery)query).ToTraceString();

    Echar un vistazo a la siguiente información: Recuperar el SQL generado por la Entidad Proveedor.

    • Para la EF al menos, no que requieren de él establecer un ObjectContext o DbContext primera (lo que significa que la configuración de casi todo) y no sólo un parte de EF.
  7. 0

    No estoy seguro si esto es exactamente lo que necesita, pero parece que podría ser cerca de:

    string[] companies = { "Consolidated Messenger", "Alpine Ski House", "Southridge Video", "City Power & Light",
    "Coho Winery", "Wide World Importers", "Graphic Design Institute", "Adventure Works",
    "Humongous Insurance", "Woodgrove Bank", "Margie's Travel", "Northwind Traders",
    "Blue Yonder Airlines", "Trey Research", "The Phone Company",
    "Wingtip Toys", "Lucerne Publishing", "Fourth Coffee" };
    //The IQueryable data to query.
    IQueryable<String> queryableData = companies.AsQueryable<string>();
    //Compose the expression tree that represents the parameter to the predicate.
    ParameterExpression pe = Expression.Parameter(typeof(string), "company");
    //***** Where(company => (company.ToLower() == "coho winery" || company.Length > 16)) *****
    //Create an expression tree that represents the expression 'company.ToLower() == "coho winery"'.
    Expression left = Expression.Call(pe, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
    Expression right = Expression.Constant("coho winery");
    Expression e1 = Expression.Equal(left, right);
    //Create an expression tree that represents the expression 'company.Length > 16'.
    left = Expression.Property(pe, typeof(string).GetProperty("Length"));
    right = Expression.Constant(16, typeof(int));
    Expression e2 = Expression.GreaterThan(left, right);
    //Combine the expression trees to create an expression tree that represents the
    //expression '(company.ToLower() == "coho winery" || company.Length > 16)'.
    Expression predicateBody = Expression.OrElse(e1, e2);
    //Create an expression tree that represents the expression
    //'queryableData.Where(company => (company.ToLower() == "coho winery" || company.Length > 16))'
    MethodCallExpression whereCallExpression = Expression.Call(
    typeof(Queryable),
    "Where",
    new Type[] { queryableData.ElementType },
    queryableData.Expression,
    Expression.Lambda<Func<string, bool>>(predicateBody, new ParameterExpression[] { pe }));
    //***** End Where *****
    //***** OrderBy(company => company) *****
    //Create an expression tree that represents the expression
    //'whereCallExpression.OrderBy(company => company)'
    MethodCallExpression orderByCallExpression = Expression.Call(
    typeof(Queryable),
    "OrderBy",
    new Type[] { queryableData.ElementType, queryableData.ElementType },
    whereCallExpression,
    Expression.Lambda<Func<string, string>>(pe, new ParameterExpression[] { pe }));
    //***** End OrderBy *****
    //Create an executable query from the expression tree.
    IQueryable<string> results = queryableData.Provider.CreateQuery<string>(orderByCallExpression);
    //Enumerate the results.
    foreach (string company in results)
    Console.WriteLine(company);

Dejar respuesta

Please enter your comment!
Please enter your name here