2009-03-16 10 views
6

¿Podría alguien ayudarme con esta excepción? No entiendo lo que significa o cómo solucionarlo ... Es una excepción de SQL con el siguiente mensaje:SqlException acerca de UNION, INTERSECT y EXCEPTO

Todas las consultas combinadas utilizando una UNION, INTERSECT o EXCEPT deben tener el mismo número de expresiones en sus listas de objetivos.

lo entiendo cuando se ejecuta una consulta en pseudo código con este aspecto:

// Some filtering of data 
var query = data.Subjects 
      .Where(has value) 
      .Where(has other value among some set of values); 

// More filtering, where I need to have two different options 
var a = query 
      .Where(some foreign key is null); 
var b = query 
      .Where(some foreign key is not null) 
      .Where(and that foreign key has a property which is what I want); 
query = a.Union(b); 

// Final filter and then get result as a list 
var list = query 
      .Where(last requirement) 
      .ToList(); 

Si quito los a.Union(b) partes, se ejecuta sin excepción. Entonces sé que el error está ahí. ¿Pero por qué lo consigo? ¿Y cómo puedo solucionarlo? ¿Estoy haciendo algo demasiado loco aquí? ¿He entendido mal cómo usar la cosa Union?

Básicamente lo que tengo es que algunas entidades que tienen una clave externa a alguna otra entidad. Y necesito obtener todas las entidades que tengan esa clave foránea establecida en null o donde esa entidad extranjera cumpla con algunos requisitos.

+0

¿Cuál es la excepción? –

+0

Es una SqlException. Actualicé la pregunta =) – Svish

+0

¿Estás seguro de que ninguna de las consultas está proyectando nada (seleccionar)? No me he encontrado con esto, pero en este momento no recuerdo si el sindicato que he hecho era linq2sql o en contra de los datos ya recuperados :( – eglasius

Respuesta

4

Dado que esto se parece a un problema con el SQL generado, usted debe tratar de utilizar un Analizador de SQL, o el uso this code for DebuggerWritter class para escribir el SQL en su ventana de salida en Visual Studio.

El error de SQL normalmente es causado por los campos recuperados para que UNION no sea el mismo para las 2 consultas. Por ejemplo, si la primera consulta puede tener 3 campos, pero la segunda consulta tiene 4 campos, se producirá este error. Entonces, ver el SQL generado definitivamente ayudará en este caso.

0

¿Puede escribirlo en una sola consulta?

.Where(row => row.ForeignKey == null || row.ForeignKey.SomeCondition); 

también hay formas de expresiones que se fusionan (OrElse), pero eso no es trivial.

No estoy seguro de dónde viene el error.

edit: no han probado, pero esto debe ser lógicamente equivalente a un UNION:

public static IQueryable<T> WhereAnyOf<T>(
    this IQueryable<T> source, 
    params Expression<Func<T, bool>>[] predicates) 
{ 
    if (source == null) throw new ArgumentNullException("source"); 
    if (predicates == null) throw new ArgumentNullException("predicates"); 
    if (predicates.Length == 0) return source.Where(row => false); 
    if (predicates.Length == 1) return source.Where(predicates[0]); 

    var param = Expression.Parameter(typeof(T), "row"); 
    Expression body = Expression.Invoke(predicates[0], param); 
    for (int i = 1; i < predicates.Length; i++) 
    { 
     body = Expression.OrElse(body, 
      Expression.Invoke(predicates[i], param)); 
    } 
    return source.Where(Expression.Lambda<Func<T, bool>>(body, param)); 
} 
+0

problema es que no es solo SomeCondition, sino SomeCondition y también el uso del método WhereBetween, con el que me ayudaste hace un tiempo, y cosas por el estilo. (http://stackoverflow.com/questions/553443/c-linq2sql-creating-a-predicate-to-find-elements-within-a-number-of-ranges) – Svish

+0

Quizás podría usar WhereAnyOf (arriba) con el la parte principal de WhereBetween (o una versión de WhereBetween que devuelve el predicado, en lugar de llamar a source.Where (predicate)) –

+0

que parece prometedor e interesante ... ¡lo probará mañana a primera hora! (¡Se acabó el trabajo y tiene que tomar un autobús!) – Svish

0

query = a.Union (b);

No es una buena idea mutar las variables capturadas ... Probablemente la causa del error.

ACTUALIZACIÓN: ok no

Aquí es otra idea. La pista está en el mensaje de error.

var a = query 
     .Where(some foreign key is null) 
     .Select(x => x); 

O jugar añadiendo otro 'falso' Donde hasta que no llegan a ser iguales :)

+0

mutate las variables capturadas? – Svish

+0

Tiene razón en que la reutilización introduce confusión, pero tenga en cuenta que ninguno de ellos se captura ... –

+1

Quiere decir - tiene var query2 = a.Union (b) y trabaja con query2 downstream. Sin embargo, dudo mucho que este sea el problema ** en esta ocasión **, ya que en realidad no se captura. –

0

Llamaría data.GetCommand(query) y analizaría el resultado DbCommand (especialmente la cadena SQL generada). Eso debería darte una pista de lo que va mal.

No hay proyección pasando en cualquier lugar por lo que sería de esperar dos listas de objetivos sean los mismos.

Puede intentar reducir su consulta a una más pequeña que todavía no funciona. Comience con query.Union(query) (esto al menos debería funcionar). Luego agregue sus llamadas Where una a una para ver cuándo deja de funcionar.

Debe ser una de sus llamadas Where que agregue columnas adicionales a su lista de selección.

+0

ya miró el sql, aunque lo obtuve durante la depuración. Lo copió y lo pegó en el administrador de sql en una nueva consulta. Y obtengo el mismo error allí. El problema es que la consulta es bastante grande, y no soy muy estable al leer sql ... – Svish

+0

Edité mi respuesta para incluir algunas sugerencias. –

0

¿Está por casualidad pasando un valor al lado 'seleccionar' en una variable, o está devolviendo el mismo campo más de una vez? SP1 introdujo un error donde trata de 'optimizar' tales cosas y que puede causar que las consultas sindicales se rompan (debido a que las partes de la consulta 'optimizan' diferentes parametros pasados).

Si publica su consulta real en lugar de pseudo código, es más fácil identificar si este es el caso.

(Y una solución si este es el caso es materializar primero las partes individuales y luego hacer una unión del lado del cliente (L2O)).

9

A juzgar por el error de SQL que mencionaste, es posible que estés teniendo el mismo problema que yo. Básicamente cuando consultas de Linq a SQL que usan el método de extensión Concat o Unión en dos consultas diferentes, parece que hay un error en Linq a SQL que optimiza cada proyección por separado sin tener en cuenta el hecho de que la proyección debe permanecer igual para lograr la Unión SQL.

Referencias:

LINQ to SQL produces incorrect TSQL when using UNION or CONCAT

Linq to SQL Union Same Fieldname generating Error

Si esto pasa a ser su problema, así que he encontrado una solución que funciona para mí, como se muestra a continuación.

var queryA = 
    from a in context.TableA 
    select new 
    { 
     id, 
     name, 
     onlyInTableA, 
    } 

var queryB = 
    from b in context.TableB 
    let onlyInTableA = default(string) 
    select new 
    { 
     id, 
     name, 
     onlyInTableA, 
    } 

var results = queryA.Union(queryB).ToList(); 
+0

Tenga en cuenta que debe definir una nueva instrucción let para cada valor calculado incluso si el real los valores literales son los mismos para que esta técnica funcione. Hasta ahora, esta es la única solución razonable que he encontrado para el problema. – jpierson

+2

Aquí hay algunas noticias horribles, parece que no solo el .NET Framework 4.0 no corrigió este problema sino que también eliminó la solución que presenté anteriormente. – jpierson

+3

Acepto, .NET 4 está optando incluso por las declaraciones de let. Sin embargo, un truco aún más antiestético que parece detener el optimizador en v4 es realizar una operación inútil en la instrucción let que no altera su resultado, como concatenar una cadena emptry o agregar cero. en este caso, algo como lo siguiente podría funcionar: let onlyInTableA = default (string) + default (string) –

0

jpierson tiene el problema resumido correctamente.
que también tenía el problema, esta vez causada por algunos literales en la instrucción SELECT:
Dim results = (From t in TestDataContext.Table1 _
Where t.ID = WantedID _
Select t.name, SpecialField = 0, AnotherSpecialField = 0, t.Address).Union _
From t in TestDataContext.Table1 _
Where t.SecondID = WantedSecondID _
Select t.name, SpecialField = 1, AnotherSpecialField = 0, t.Address)

El primer sub-consulta de "SpecialField = 0" y el "AnotherSpecialField = 0" fueron optimizados, lo que resulta en una campo en lugar de dos que se utilizan en la unión, que obviamente fallará.
Tuve que cambiar la primera consulta para que SpecialField & AnotherSpecialField tuviera valores diferentes, al igual que en la segunda sub consulta.

0

Bueno, tuve un problema con esto. Usando Sql 08 tuve dos funciones de tabla que devolvieron un int y una cadena en ambos casos. Creé un objeto complejo y utilicé linq para intentar una UNIÓN. Tuve un IEqualityComparer para hacer la comparación. Todo compilado bien, pero se estrelló con una sobrecarga no compatible. Bien, me di cuenta de que el problema discutido parecía dar la impresión de una ejecución desviada. Entonces obtengo las colecciones y coloco ToList(), luego hago UNION y todo está bien. No estoy seguro si esto es útil, pero funciona para mí

+2

Bueno, eso lo sé. Pero lo que estás haciendo es hacer la UNIÓN localmente. Eso puede o no ser una buena idea según tus circunstancias. – Svish