Estoy tratando de consulta de datos de la forma con LINQ-to-EF:

class Location {
    string Country;
    string City;
    string Address;
    
}

buscando un lugar por la tupla (País, Ciudad, Dirección). Traté de

var keys = new[] {
    new {Country=…, City=…, Address=…},
    
}

var result = from loc in Location
             where keys.Contains(new {
                 Country=loc.Country, 
                 City=loc.City, 
                 Address=loc.Address
             }

pero LINQ no quieren aceptar un tipo anónimo (que entiendo que es la manera de expresar las tuplas en LINQ) como el parámetro que Contiene().

Hay una «buena» manera de expresar esto en LINQ, mientras que ser capaz de ejecutar la consulta en la base de datos? Alternativamente, si sólo me iterado claves y Unión (la)-ed de consultas juntos, que sería malo para el rendimiento?

OriginalEl autor millimoose | 2011-08-02

11 Comentarios

  1. 6

    Cómo sobre:

    var result = locations.Where(l => keys.Any(k => 
                        k.Country == l.Country && 
                        k.City == l.City && 
                        k.Address == l.Address));

    ACTUALIZACIÓN

    Lamentablemente EF lanza NotSupportedException en que, lo que descalifica a esta respuesta si usted necesita la consulta se ejecute en DB lado.

    ACTUALIZACIÓN 2

    Probado todo tipo de combinaciones mediante clases personalizadas y las Tuplas – ninguno de los dos trabajos. ¿Qué volúmenes de datos estamos hablando? Si no es nada demasiado grande, usted podría proceso del lado del cliente (conveniente) o el uso de los sindicatos (si no más rápido, a menos que los datos se transmiten).

    ya que la pregunta es para Linq to entities dudo que esto iba a funcionar, de lo contrario, buena sugerencia.
    Lo estoy probando ahora mismo a ver si EF entiende que. Otro ORM puedo usar haría bien.
    Voy a aceptar esto como un detallado «esto no parece ser posible en LINQ-to-EF» como respuesta. El volumen de datos, en mi caso no es alto así que me fui con Union()-ción de las consultas juntos (porque forma dinámica la construcción de un predicado en LINQ es doloroso), y cruzando los dedos para que SQL Server puede averiguar todo se golpea contra el mismo índice.

    OriginalEl autor Jacek Gorgoń

  2. 6

    Aunque no pude conseguir @YvesDarmaillac del código de trabajo, se me señaló para esta solución.

    Usted puede construir una expresión y, a continuación, agregue cada condición por separado. Para ello, puede utilizar el Universal PredicateBuilder (fuente al final).

    Aquí está mi código:

    //First we create an Expression. Since we can't create an empty one,
    //we make it return false, since we'll connect the subsequent ones with "Or".
    //The following could also be: Expression<Func<Location, bool>> condition = (x => false); 
    //but this is clearer.
    var condition = PredicateBuilder.Create<Location>(x => false);
    
    foreach (var key in keys)
    {
        //each one returns a new Expression
        condition = condition.Or(
            x => x.Country == key.Country && x.City == key.City && x.Address == key.Address
        );
    }
    
    using (var ctx = new MyContext())
    {
        var locations = ctx.Locations.Where(condition);
    }

    Una cosa a tener cuidado, sin embargo, es que el filtro de la lista (el keys variable en este ejemplo) no puede ser demasiado grande, puede alcanzar los parámetros de límite, con una excepción como esta:

    SqlException: La solicitud entrante tiene demasiados parámetros. El servidor admite un máximo de 2100 parámetros. Reducir el número de parámetros y volver a enviar la solicitud.

    Así, en este ejemplo (con tres parámetros por línea), usted no puede tener más de 700 Ubicaciones de filtro.

    El uso de dos elementos de filtro, se generará 6 parámetros en la final de SQL. El SQL generado tendrá un aspecto como el de abajo (formato para ser más claro):

    exec sp_executesql N'
    SELECT 
    [Extent1].[Id] AS [Id], 
    [Extent1].[Country] AS [Country], 
    [Extent1].[City] AS [City], 
    [Extent1].[Address] AS [Address]
    FROM [dbo].[Locations] AS [Extent1]
    WHERE 
    (
    (
    ([Extent1].[Country] = @p__linq__0) 
    OR 
    (([Extent1].[Country] IS NULL) AND (@p__linq__0 IS NULL))
    )
    AND 
    (
    ([Extent1].[City] = @p__linq__1) 
    OR 
    (([Extent1].[City] IS NULL) AND (@p__linq__1 IS NULL))
    ) 
    AND 
    (
    ([Extent1].[Address] = @p__linq__2) 
    OR 
    (([Extent1].[Address] IS NULL) AND (@p__linq__2 IS NULL))
    )
    )
    OR
    (
    (
    ([Extent1].[Country] = @p__linq__3) 
    OR 
    (([Extent1].[Country] IS NULL) AND (@p__linq__3 IS NULL))
    )
    AND 
    (
    ([Extent1].[City] = @p__linq__4) 
    OR 
    (([Extent1].[City] IS NULL) AND (@p__linq__4 IS NULL))
    ) 
    AND 
    (
    ([Extent1].[Address] = @p__linq__5) 
    OR 
    (([Extent1].[Address] IS NULL) AND (@p__linq__5 IS NULL))
    )
    )
    ',
    N'
    @p__linq__0 nvarchar(4000),
    @p__linq__1 nvarchar(4000),
    @p__linq__2 nvarchar(4000),
    @p__linq__3 nvarchar(4000),
    @p__linq__4 nvarchar(4000),
    @p__linq__5 nvarchar(4000)
    ',
    @p__linq__0=N'USA',
    @p__linq__1=N'NY',
    @p__linq__2=N'Add1',
    @p__linq__3=N'UK',
    @p__linq__4=N'London',
    @p__linq__5=N'Add2'

    Observe cómo la inicial de «falsa» la expresión es ignorada correctamente y no se incluyen en la final de SQL por EntityFramework.

    Finalmente, aquí está el código para el Universal PredicateBuilder, para el registro.

    ///<summary>
    ///Enables the efficient, dynamic composition of query predicates.
    ///</summary>
    public static class PredicateBuilder
    {
    ///<summary>
    ///Creates a predicate that evaluates to true.
    ///</summary>
    public static Expression<Func<T, bool>> True<T>() { return param => true; }
    ///<summary>
    ///Creates a predicate that evaluates to false.
    ///</summary>
    public static Expression<Func<T, bool>> False<T>() { return param => false; }
    ///<summary>
    ///Creates a predicate expression from the specified lambda expression.
    ///</summary>
    public static Expression<Func<T, bool>> Create<T>(Expression<Func<T, bool>> predicate) { return predicate; }
    ///<summary>
    ///Combines the first predicate with the second using the logical "and".
    ///</summary>
    public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
    {
    return first.Compose(second, Expression.AndAlso);
    }
    ///<summary>
    ///Combines the first predicate with the second using the logical "or".
    ///</summary>
    public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
    {
    return first.Compose(second, Expression.OrElse);
    }
    ///<summary>
    ///Negates the predicate.
    ///</summary>
    public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expression)
    {
    var negated = Expression.Not(expression.Body);
    return Expression.Lambda<Func<T, bool>>(negated, expression.Parameters);
    }
    ///<summary>
    ///Combines the first expression with the second using the specified merge function.
    ///</summary>
    static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
    {
    //zip parameters (map from parameters of second to parameters of first)
    var map = first.Parameters
    .Select((f, i) => new { f, s = second.Parameters[i] })
    .ToDictionary(p => p.s, p => p.f);
    //replace parameters in the second lambda expression with the parameters in the first
    var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);
    //create a merged lambda expression with parameters from the first expression
    return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
    }
    class ParameterRebinder : ExpressionVisitor
    {
    readonly Dictionary<ParameterExpression, ParameterExpression> map;
    ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
    {
    this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
    }
    public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
    {
    return new ParameterRebinder(map).Visit(exp);
    }
    protected override Expression VisitParameter(ParameterExpression p)
    {
    ParameterExpression replacement;
    if (map.TryGetValue(p, out replacement))
    {
    p = replacement;
    }
    return base.VisitParameter(p);
    }
    }
    }

    OriginalEl autor Marcos Dimitrio

  3. 5

    Mi solución es la construcción de un nuevo método de extensión WhereOr que el uso de un ExpressionVisitor para construir la consulta :

    public delegate Expression<Func<TSource, bool>> Predicat<TCle, TSource>(TCle cle);
    public static class Extensions
    {
    public static IQueryable<TSource> WhereOr<TSource, TCle>(this IQueryable<TSource> source, IEnumerable<TCle> cles, Predicat<TCle, TSource> predicat)
    where TCle : ICle,new()
    {
    Expression<Func<TSource, bool>> clause = null;
    foreach (var p in cles)
    {
    clause = BatisseurFiltre.Or<TSource>(clause, predicat(p));
    }
    return source.Where(clause);
    }
    }
    class BatisseurFiltre : ExpressionVisitor
    {
    private ParameterExpression _Parametre;
    private BatisseurFiltre(ParameterExpression cle)
    {
    _Parametre = cle;
    }
    protected override Expression VisitParameter(ParameterExpression node)
    {
    return _Parametre;
    }
    internal static Expression<Func<T, bool>> Or<T>(Expression<Func<T, bool>> e1, Expression<Func<T, bool>> e2)
    {
    Expression<Func<T, bool>> expression = null;
    if (e1 == null)
    {
    expression = e2;
    }
    else if (e2 == null)
    {
    expression = e1;
    }
    else
    {
    var visiteur = new BatisseurFiltre(e1.Parameters[0]);
    e2 = (Expression<Func<T, bool>>)visiteur.Visit(e2);
    var body = Expression.Or(e1.Body, e2.Body);
    expression = Expression.Lambda<Func<T, bool>>(body, e1.Parameters[0]);
    }
    return expression;
    }
    }

    El siguiente genera limpia de código sql ejecutadas en la base de datos :

    var result = locations.WhereOr(keys, k => (l => k.Country == l.Country && 
    k.City == l.City && 
    k.Address == l.Address
    )
    );
    Enfoque interesante, me pregunto si esto podría ser implementado con LinqKit?
    He intentado utilizar su extensión, pero ICle es indefinido. Se puede incluir la definición de ICle?

    OriginalEl autor Yves Darmaillac

  4. 2
    var result = from loc in Location
    where keys.Contains(new {
    Country=l.Country, 
    City=l.City, 
    Address=l.Address
    }

    tendría que ser:

    var result = from loc in Location
    where keys.Contains(new {
    Country=loc.Country, 
    City=loc.City, 
    Address=loc.Address
    }
    select loc;
    Que fue un error que me hizo crear el ejemplo simplificado, me fijo la pregunta.
    Estoy plussing esta respuesta, lo único que le falta es la línea de selección y el final de la consulta.

    OriginalEl autor Chris Snowden

  5. 2

    Hay un EF extensión existe, que fue diseñado para muy similar caso. Es EntityFrameworkCore.MemoryJoin (nombre podría ser confuso, pero es compatible tanto con el EF6 y EF Core). Como se indicó en el autor del artículo modifica la consulta SQL se pasa al servidor y se inyecta VALORES construcción con datos de la lista local. Y la consulta se ejecuta en el servidor de DB.

    Así que para tu caso de uso podría ser como este

    var keys = new[] {
    new {Country=…, City=…, Address=…},
    
    }
    //here is the important part!
    var keysQueryable = context.FromLocalList(keys);
    var result = from loc in Location
    join key in keysQueryable on new { loc.Country, loc.City, loc.Address } equals new { key.Country, key.City, key.Address }
    select loc
    Esto se ve prometedor! Por desgracia, yo en realidad no puede comprobar si se había de hacer el trabajo porque estoy en un completo proyecto diferente ahora, pero lo tendré en mente cuando se produzca el problema.

    OriginalEl autor Tony

  6. 1

    ¿Has probado usando sólo la Tupla de la clase?

    var keys = new[] {
    Tuple.Create("Country", "City", "Address"),
    
    }
    var result = from loc in Location
    where keys.Contains(Tuple.Create(loc.Country, loc.City, loc.Address))
    Esto no compilar con: Delegado » del Sistema.Func<Ubicación,int,bool>’ no toma argumentos 1

    OriginalEl autor sellmeadog

  7. 1

    Si no vas a necesitar un montón de combinaciones de teclas, usted puede simplemente añadir un LocationKey propiedad a sus datos. Para evitar el desperdicio de una gran cantidad de almacenamiento, tal vez hacer que el código hash de la combinación de propiedades.

    A continuación, busque simplemente tendrás una condición en LocationKey. Por último, en el lado del cliente filtrar los resultados para soltar las entidades que tenía el mismo hash, pero no la misma ubicación.

    Sería algo parecido a:

    class Location 
    {
    private string country;
    public string Country
    {
    get { return country; }
    set { country = value; UpdateLocationKey(); }
    }
    private string city;
    public string City
    {
    get { return city; }
    set { city = value; UpdateLocationKey(); }
    }
    private string address;
    public string Address
    {
    get { return address; }
    set { address = value; UpdateLocationKey(); }
    }
    private void UpdateLocationKey()
    {
    LocationKey = Country.GetHashCode() ^ City.GetHashCode() ^ Address.GetHashCode();
    }
    int LocationKey;
    
    }

    A continuación, simplemente consulta en el LocationKey propiedad.

    No es ideal, pero debería funcionar.

    El esquema de base de datos que estoy trabajando con la realidad tiene el mapeo de ubicación de los componentes clave en la base de datos y la consulta que estoy construyendo es mirando aquellos. La idea de la mezcla de ellos juntos en lugar de almacenar una asignación explícita es buena, aunque.

    OriginalEl autor Ran

  8. 0

    No creo que funcione para usted, ya que cuando se newing un objeto en el Contains método que se cree un nuevo objeto cada vez. Dado que los objetos son anónimos la forma en que serán comparados están en contra de su referencia, que será diferente para cada objeto.

    También, busque en Jacek la respuesta.

    Hay un problema allí. De acuerdo a la msdn.microsoft.com/en-us/library/bb397696.aspx dos instancias del mismo tipo anónimo son iguales sólo si todas sus propiedades son iguales. lo que significa que Chris la forma en que debe trabajar demasiado.
    Contains utiliza el comparador de igualdad, que por anónimo tipos de usos de la propiedad de la igualdad – este no es el problema.
    Usted está totalmente a la derecha. Pero es @Jacek solución ayudar a usted?

    OriginalEl autor Tomas Jansson

  9. 0
        var keys = new[] {
    new {Country=…, City=…, Address=…},
    
    }    
    var result = from loc in Location
    where keys.Any(k=>k.Country == loc.Country 
    && k.City == loc.City 
    && k.Address == loc.Address) 
    select loc

    Dar a este un intento.

    Creo que este es el mismo que @Jacek la respuesta, que no funciona en LINQ-to-EF.

    OriginalEl autor AD.Net

  10. 0

    creo que la forma correcta de hacerlo es

    var result = from loc in Location
    where loc.Country = _country
    where loc.City = _city
    where loc.Address = _address
    select loc

    Parece unoptimized pero la consulta proveedor de salir y hacer la optimización cuando se transforma en la consulta sql. Cuando el uso de tuplas o de otras clases, el proveedor de consultas no saben cómo transformarlos en sql y que lo que causa el NotSupportedException

    -editar-

    Si usted tiene múltiples clave de tuplas creo que tienes un bucle a través de todos ellos y hacer la consulta anterior para cada uno de ellos. de nuevo, que podría parecer underoptimized, pero la consulta para retriving todos los lugares en una sola consulta probablemente terminan siendo bastante larga:

    select * from locations 
    where (locations.Country = @country1 and locations.City = @city1, locations.Adress = @adress1)
    or (locations.Country = @country2 and locations.City = @city2, locations.Adress = @adress2)
    or ...

    La manera más rápida de hacerlo es, probablemente, para hacer las consultas simples, pero les envían como una sola secuencia de comandos sql y el uso de múltiples conjuntos de resultados para obtener cada valor. No estoy seguro de que usted puede conseguir EF hacer que a pesar de.

    sí, generando una completa consulta en lugar de utilizar el or enfoque sería incluso más, pero uno podría hacer la consulta corta en una declaración preparada y, por lo tanto, ser más rápido. no estoy seguro de si alguna de que es compatible con EF a pesar de que

    OriginalEl autor aL3891

  11. -1

    Me gustaría reemplazar Contiene (que es un método específico para las listas y matrices) con el resto de la IEnumerable de Cualquier método de extensión:

    var result = Location
    .Where(l => keys.Any(k => l.Country == k.Country && l.City = k.City && l.Address == k.Address);

    Esto también puede ser escrito:

    var result = from l in Location
    join k in keys
    on l.Country == k.Country && l.City == k.City && l.Address == k.Address
    select l;
    Creo que varias personas ya dio esta respuesta, que no funciona en LINQ-to-EF.

    OriginalEl autor Evren Kuzucuoglu

Dejar respuesta

Please enter your comment!
Please enter your name here