Como fazer junções no LINQ em vários campos na junção única

244

Preciso fazer uma consulta LINQ2DataSet que faça uma junção em mais de um campo (como

var result = from x in entity
join y in entity2 
       on x.field1 = y.field1 
and 
          x.field2 = y.field2

Eu ainda encontrei uma solução adequada (posso adicionar restrições adicionais a uma cláusula where, mas isso está longe de ser uma solução adequada ou usar essa solução, mas isso pressupõe um equijoin).

É possível no LINQ ingressar em vários campos em uma única associação?

EDITAR

var result = from x in entity
             join y in entity2
             on new { x.field1, x.field2 } equals new { y.field1, y.field2 }

é a solução que referi como assumindo um equijoin acima.

EDIT adicional

Para responder às críticas de que meu exemplo original era um equijoin, reconheço que, meu requisito atual é para um equijoin e já empreguei a solução a que me referi acima.

No entanto, estou tentando entender quais possibilidades e melhores práticas eu tenho / devo empregar com o LINQ. Vou precisar fazer uma junção de consulta de intervalo de datas com um ID de tabela em breve e estava antecipando esse problema. Parece que terei que adicionar o período na cláusula where.

Obrigado, como sempre, por todas as sugestões e comentários fornecidos

johnc
fonte
48
Apenas um FYI para quem está lendo isso, se você fizer uma junção de vários campos nas classes annon, DEVE nomear os campos nas duas classes annon da mesma forma, caso contrário, você obterá erros de compilação.
Mark
6
Ou melhor, você deve garantir que eles tenham nomes correspondentes. ou seja, você pode apenas nomear os campos de um dos tipos anon para fazê-los corresponder ao outro.
Tom Ferguson
1
Preste atenção a esta resposta stackoverflow.com/a/34176502/1704458
TS
Usei tuplas para ambos os lados dos iguais, em vez de objetos, e parecia funcionar também.
GHZ

Respostas:

89

A solução com o tipo anônimo deve funcionar bem. LINQ pode representar apenas equijoins (com cláusulas de junção, de qualquer maneira), e é isso que você disse que deseja expressar de qualquer maneira com base na sua consulta original.

Se você não gostar da versão com o tipo anônimo por algum motivo específico, explique esse motivo.

Se você quiser fazer algo diferente do que pediu originalmente, dê um exemplo do que você realmente deseja fazer.

EDIT: Respondendo à edição na pergunta: sim, para fazer uma junção de "período", você precisa usar uma cláusula where. Eles são semanticamente equivalentes, portanto, é apenas uma questão de otimizações disponíveis. Os equivalentes fornecem otimização simples (no LINQ to Objects, que inclui o LINQ to DataSets) criando uma pesquisa com base na sequência interna - pense nela como uma tabela de hash da chave para uma sequência de entradas correspondentes a essa chave.

Fazer isso com períodos é um pouco mais difícil. No entanto, dependendo exatamente do que você quer dizer com "ingresso no período", você poderá fazer algo semelhante - se estiver planejando criar "faixas" de datas (por exemplo, uma por ano), de modo que duas entradas que ocorram no o mesmo ano (mas não na mesma data) deve corresponder, então você pode fazê-lo usando essa banda como chave. Se for mais complicado, por exemplo, um lado da junção fornece um intervalo, e o outro lado da junção fornece uma única data, correspondente se estiver dentro desse intervalo, que seria melhor manipulada com uma wherecláusula (após um segundofromcláusula) IMO. Você poderia fazer uma mágica particularmente descolada, ordenando de um lado ou de outro para encontrar correspondências com mais eficiência, mas isso daria muito trabalho - eu só faria esse tipo de coisa depois de verificar se o desempenho é um problema.

Jon Skeet
fonte
Obrigado, sim, o desempenho é minha principal preocupação ao usar a cláusula where. Estou supondo que uma cláusula where após a junção execute um filtro em um conjunto de dados maior que poderia ter sido reduzido com a introdução do segundo parâmetro de junção. Eu gosto da idéia de ordenação para testar se eu conseguir ganhos de eficiência
JohnC
Quantos registros você terá? Não se esqueça que ordenação os resultados para começar terá uma certa quantidade de tempo para começar com ...
Jon Skeet
"Eles são semanticamente equivalentes realmente" - precisamos da palavra 'realmente' aí? Talvez você quis dizer, "Eles são realmente semanticamente equivalente" :)
onedaywhen
136
var result = from x in entity
   join y in entity2 on new { x.field1, x.field2 } equals new { y.field1, y.field2 }
KristoferA
fonte
Isto é o que eu estava procurando, pois as 101 amostras Linq não tinham isso ou pelo menos eu vi.
Chris Marisic
1
@PeterX na verdade, pode, ver a minha resposta aqui: stackoverflow.com/a/22176658/595157
niieani
13
O código acima não funcionou. Depois de adicionar, on new { X1= x.field1, X2= x.field2 } equals new { X1=y.field1, X2= y.field2 } trabalhou #
Ravi Ram
@ Ravi Ram .. Obrigado .. seu comentário ajudou
NMathur 5/17
80
var result = from x in entity1
             join y in entity2
             on new { X1= x.field1, X2= x.field2 } equals new { X1=y.field1, X2= y.field2 }

Você precisa fazer isso, se os nomes das colunas forem diferentes em duas entidades.

RealNapster
fonte
6
Obrigado por mencionar os diferentes nomes de colunas. Isso corrigiu minha expressão ruim.
Gaʀʀʏ
1
Isso funcionou para mim também. Se os nomes das colunas não corresponderem, você receberá este erro: "O tipo de uma das expressões na cláusula de associação está incorreto. Falha na inferência de tipo na chamada para 'GroupJoin'".
Humbads
Obrigado por usar o alias das principais variáveis.
precisa saber é o seguinte
Eu recebi o erro que os @humbads mencionaram quando não nomeei todas as propriedades de int como 'new {}'. Portanto, apenas se você nomear um, também deve nomear o resto.
Ethan Melamed
MUITO OBRIGADO
Charly H
51

Apenas para concluir isso com uma sintaxe de cadeia de método equivalente:

entity.Join(entity2, x => new {x.Field1, x.Field2},
                     y => new {y.Field1, y.Field2}, (x, y) => x);

Enquanto o último argumento (x, y) => xé o que você seleciona (no caso acima, selecionamos x).

niieani
fonte
31

Eu acho que uma opção mais legível e flexível é usar a função Where:

var result = from x in entity1
             from y in entity2
                 .Where(y => y.field1 == x.field1 && y.field2 == x.field2)

Isso também permite alterar facilmente da junção interna para a esquerda adicionando .DefaultIfEmpty ().

Alexei
fonte
Como um usuário lambda de longa data agora (ao contrário de quando eu fiz a pergunta), eu teria que concordar com
johnc
Isso seria mais lento?
precisa saber é o seguinte
1
Eu acho que deve ter o mesmo desempenho que a nova { ... } equals new { ... }sintaxe. LinqPad é uma grande ferramenta para ver como expressões estão se comportando (script SQL se LINQ2SQL é usado, árvores de expressão etc.)
Alexei
Tanto quanto eu notei que está produzindo CROSS JOIN em vez de INNER JOIN
Mariusz
@ Mariusz Sim, faz sentido gerar CROSS JOIN + WHERE em vez de INNER JOIN. Para consultas simples, espero que o analisador gere um resultado muito semelhante.
Alexei
10
var result = from x in entity
             join y in entity2
             on new { X1= x.field1, X2= x.field2 } equals new { X1=y.field1, X2= y.field2 }
             select new 
             {
               /// Columns
              };
user3966657
fonte
8

você poderia fazer algo como (abaixo)

var query = from p in context.T1

        join q in context.T2

        on

        new { p.Col1, p.Col2 }

        equals

         new { q.Col1, q.Col2 }

        select new {p...., q......};
Perpetualcoder
fonte
Como mencionei em questão, que requer um equijoin
johnc
7

Usando o operador de junção, você só pode executar equijóins. Outros tipos de junções podem ser construídos usando outros operadores. Não tenho certeza se a junção exata que você está tentando fazer seria mais fácil usando esses métodos ou alterando a cláusula where. A documentação da cláusula de junção pode ser encontrada aqui . O MSDN também tem um artigo sobre operações de associação com vários links para exemplos de outras associações.

tvanfosson
fonte
3

Se o nome do campo for diferente nas entidades

var result = from x in entity
   join y in entity2 on 
          new {
                field1=   x.field1,
               field2 =  x.field2 
             } 
          equals
         new { 
                field1= y.field1,
                field2=  y.myfield
              }
select new {x,y});
Mahesh
fonte
Obrigado. A correspondência de nome foi a peça que estava faltando.
Brett
2

Como uma cadeia de métodos completa, seria assim:

lista.SelectMany(a => listb.Where(xi => b.Id == a.Id && b.Total != a.Total),
                (a, b) => new ResultItem
                {
                    Id = a.Id,
                    ATotal = a.Total,
                    BTotal = b.Total
                }).ToList();
Adam Garner
fonte
-2
from d in db.CourseDispatches
                             join du in db.DispatchUsers on d.id equals du.dispatch_id
                             join u in db.Users on du.user_id equals u.id
                             join fr in db.Forumreports on (d.course_id + '_' + du.user_id)  equals  (fr.course_id + '_'+ fr.uid)

isso funciona para mim

user2745564
fonte
Isto é para múltiplos participar, ele quer fazer uma junção com vários campos em uma única juntar
theLaw
-3

Declare uma Classe (Tipo) para conter os elementos que você deseja unir. No exemplo abaixo, declare JoinElement

 public class **JoinElement**
{
    public int? Id { get; set; }
    public string Name { get; set; }

}

results = from course in courseQueryable.AsQueryable()
                  join agency in agencyQueryable.AsQueryable()
                   on new **JoinElement**() { Id = course.CourseAgencyId, Name = course.CourseDeveloper } 
                   equals new **JoinElement**() { Id = agency.CourseAgencyId, Name = "D" } into temp1
Ven
fonte
1
Isso já foi respondido há 9 anos ... que valor essa resposta traz?
Maciej Jureczko