Eu tenho uma lista do ID das pessoas e seu primeiro nome, e uma lista do ID das pessoas e seu sobrenome. Algumas pessoas não têm nome e outras não, sobrenome; Eu gostaria de fazer uma junção externa completa nas duas listas.
Portanto, as seguintes listas:
ID FirstName
-- ---------
1 John
2 Sue
ID LastName
-- --------
1 Doe
3 Smith
Deve produzir:
ID FirstName LastName
-- --------- --------
1 John Doe
2 Sue
3 Smith
Eu sou novo no LINQ (então, desculpe-me se estiver sendo manco) e encontrei algumas soluções para 'LINQ Outer Joins' que parecem muito semelhantes, mas realmente parecem ser uniões externas.
Minhas tentativas até agora são mais ou menos assim:
private void OuterJoinTest()
{
List<FirstName> firstNames = new List<FirstName>();
firstNames.Add(new FirstName { ID = 1, Name = "John" });
firstNames.Add(new FirstName { ID = 2, Name = "Sue" });
List<LastName> lastNames = new List<LastName>();
lastNames.Add(new LastName { ID = 1, Name = "Doe" });
lastNames.Add(new LastName { ID = 3, Name = "Smith" });
var outerJoin = from first in firstNames
join last in lastNames
on first.ID equals last.ID
into temp
from last in temp.DefaultIfEmpty()
select new
{
id = first != null ? first.ID : last.ID,
firstname = first != null ? first.Name : string.Empty,
surname = last != null ? last.Name : string.Empty
};
}
}
public class FirstName
{
public int ID;
public string Name;
}
public class LastName
{
public int ID;
public string Name;
}
Mas isso retorna:
ID FirstName LastName
-- --------- --------
1 John Doe
2 Sue
O que estou fazendo de errado?
c#
.net
linq
outer-join
full-outer-join
ninjaPixel
fonte
fonte
Respostas:
Não sei se isso cobre todos os casos, logicamente parece correto. A idéia é criar uma junção externa esquerda e externa direita e, em seguida, realizar a união dos resultados.
Isso funciona como está escrito, pois está no LINQ to Objects. Se LINQ to SQL ou outro, o processador de consultas pode não suportar navegação segura ou outras operações. Você precisaria usar o operador condicional para obter condicionalmente os valores.
ou seja,
fonte
AsEnumerable()
antes de executar a união / concatenação. Tente isso e veja como isso acontece. Se esse não é o caminho que você deseja seguir, não tenho certeza se posso ajudar mais do que isso.Atualização 1: fornecendo um método de extensão verdadeiramente generalizado
FullOuterJoin
Atualização 2: opcionalmente aceitando um personalizado
IEqualityComparer
para o tipo de chaveAtualização 3 : esta implementação recentemente se tornou parte de
MoreLinq
- Obrigado pessoal!Editar Adicionado
FullOuterGroupJoin
( ideone ). Reutilizei aGetOuter<>
implementação, tornando esta uma fração menos eficiente do que poderia ser, mas estou buscando um código de 'alto nível', não otimizado de ponta, agora.Veja ao vivo em http://ideone.com/O36nWc
Imprime a saída:
Você também pode fornecer os padrões: http://ideone.com/kG4kqO
Impressão:
Explicação dos termos utilizados:
União é um termo emprestado do design de banco de dados relacional:
a
quantas vezes houver elementosb
com a chave correspondente (ou seja: nada se estiverb
vazio). A linguagem do banco de dados chama issoinner (equi)join
.a
quais não existe nenhum elemento correspondenteb
. (ou seja: resultados pares seb
estiverem vazios). Isso geralmente é chamado deleft join
.a
eb
se nenhum elemento correspondente existe no outro. (ou seja, resultados iguais sea
estivessem vazios)Algo geralmente não visto no RDBMS é uma junção de grupo [1] :
a
vários correspondentesb
, agrupa os registros com as chaves correspondentes. Isso geralmente é mais conveniente quando você deseja enumerar os registros 'unidos', com base em uma chave comum.Consulte também GroupJoin, que também contém algumas explicações gerais.
[1] (acredito que Oracle e MSSQL têm extensões proprietárias para isso)
Código completo
Uma classe de extensão 'drop-in' generalizada para este
fonte
FullOuterJoin
método de extensão fornecidoa.GroupBy(selectKeyA).ToDictionary();
comoa.ToLookup(selectKeyA)
eadict.OuterGet(key)
comoalookup[key]
. Obtendo a coleção de chaves é um pouco mais complicado, no entanto:alookup.Select(x => x.Keys)
.Eu acho que há problemas com a maioria deles, incluindo a resposta aceita, porque eles não funcionam bem com o Linq sobre o IQueryable devido a muitas viagens de ida e volta ao servidor e a muitos retornos de dados, ou a muita execução do cliente.
Para IEnumerable, não gosto da resposta de Sehe ou similar, porque ela tem uso excessivo de memória (um simples teste de 10000000 com duas listas executou o Linqpad sem memória na minha máquina de 32 GB).
Além disso, a maioria dos outros não implementa uma Junção Externa Completa adequada porque eles estão usando uma União com uma Junta Direita em vez de Concat com uma Junta Semi Semi Direita, o que não apenas elimina as linhas de junção interna duplicadas do resultado, mas também quaisquer duplicatas adequadas que existiam originalmente nos dados esquerdo ou direito.
Então, aqui estão minhas extensões que lidam com todos esses problemas, geram SQL e implementam a junção no LINQ to SQL diretamente, executando no servidor, e são mais rápidas e com menos memória que outras no Enumerables:
A diferença entre uma Anti-Semi-Join Direita é principalmente discutida com o Linq to Objects ou na fonte, mas faz a diferença no lado do servidor (SQL) na resposta final, removendo uma desnecessária
JOIN
.A codificação manual
Expression
para lidar com a fusão de umExpression<Func<>>
em um lambda poderia ser melhorada com o LinqKit, mas seria bom se o idioma / compilador tivesse adicionado alguma ajuda para isso. As funçõesFullOuterJoinDistinct
eRightOuterJoin
estão incluídas para garantir a integridade, mas ainda não as implementeiFullOuterGroupJoin
.Eu escrevi outra versão de uma junção externa completa
IEnumerable
para casos em que a chave pode ser solicitada, que é cerca de 50% mais rápida do que a combinação da junção externa esquerda com a anti junção anti certa, pelo menos em coleções pequenas. Ele percorre cada coleção após classificar apenas uma vez.Também adicionei outra resposta para uma versão que funciona com a EF, substituindo a
Invoke
por uma expansão personalizada.fonte
TP unusedP, TC unusedC
? Eles estão literalmente sem uso?TP
,TC
,TResult
para criar o bomExpression<Func<>>
. Eu acho que eu poderia substituí-los com_
,__
,___
em vez disso, mas isso não parece mais claro até que C # tem um curinga parâmetro adequado para usar em vez.The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.
. Há alguma restrição com este código? Eu quero executar uma junção completa sobre IQueryablesInvoke
por um personalizadoExpressionVisitor
para incorporar o, deInvoke
modo que ele funcione com a EF. Você pode tentar?Aqui está um método de extensão fazendo isso:
fonte
Union
remove duplicatas, portanto, se houver linhas duplicadas nos dados originais, elas não estarão no resultado.Acho que a abordagem da @sehe é mais forte, mas até que eu a entenda melhor, eu me pego pulando na extensão do @ MichaelSander. Modifiquei-o para corresponder à sintaxe e ao tipo de retorno do método interno Enumerable.Join () descrito aqui . Anexei o sufixo "distinto" em relação ao comentário de @ cadrell0 na solução de @ JeffMercado.
No exemplo, você usaria assim:
No futuro, à medida que aprender mais, tenho a sensação de que migrarei para a lógica da @ sehe, dada sua popularidade. Mas, mesmo assim, terei que ter cuidado, porque sinto que é importante ter pelo menos uma sobrecarga que corresponda à sintaxe do método ".Join ()" existente, se possível, por dois motivos:
Ainda sou novo com genéricos, extensões, instruções Func e outros recursos, portanto o feedback é certamente bem-vindo.
Edição: não demorou muito tempo para perceber que havia um problema com o meu código. Eu estava fazendo um .Dump () no LINQPad e olhando para o tipo de retorno. Era apenas IEnumerable, então tentei combinar. Mas quando eu realmente fiz um .Where () ou .Select () na minha extensão, recebi um erro: "'System Collections.IEnumerable' não contém uma definição para 'Select' e ...". Portanto, no final, consegui corresponder à sintaxe de entrada de .Join (), mas não ao comportamento de retorno.
EDIT: Adicionado "TResult" ao tipo de retorno para a função. Perdeu isso ao ler o artigo da Microsoft, e é claro que faz sentido. Com essa correção, agora parece que o comportamento do retorno está alinhado com meus objetivos, afinal.
fonte
Como você descobriu, o Linq não possui uma construção "junção externa". O mais próximo que você pode obter é uma junção externa esquerda usando a consulta que você declarou. Para isso, você pode adicionar qualquer elemento da lista de sobrenomes que não esteja representado na junção:
fonte
Gosto da resposta de ela, mas ela não usa execução adiada (as seqüências de entrada são ansiosamente enumeradas pelas chamadas para ToLookup). Então, depois de examinar as fontes .NET para LINQ-to-objects , vim com isso:
Esta implementação possui as seguintes propriedades importantes:
Essas propriedades são importantes, porque são o que alguém novo no FullOuterJoin, mas com experiência no LINQ, espera.
fonte
Decidi adicionar isso como uma resposta separada, pois não tenho certeza de que foi testado o suficiente. Esta é uma reimplementação do
FullOuterJoin
método usando essencialmente uma versão simplificada e personalizada deLINQKit
Invoke
/Expand
for,Expression
para que ele funcione no Entity Framework. Não há muita explicação, pois é praticamente a mesma que a minha resposta anterior.fonte
base.Visit(node)
não deve lançar uma exceção, pois isso apenas se repete na árvore. Eu posso acessar praticamente qualquer serviço de compartilhamento de código, mas não configurar um banco de dados de teste. Porém, executá-lo no meu teste LINQ to SQL parece funcionar bem.Guid
chave e umaGuid?
chave estrangeira?Executa uma enumeração de streaming na memória em ambas as entradas e chama o seletor para cada linha. Se não houver correlação na iteração atual, um dos argumentos do seletor será nulo .
Exemplo:
Requer um IComparer para o tipo de correlação, usa o Comparer.Default se não for fornecido.
Requer que 'OrderBy' seja aplicado aos enumeráveis de entrada
fonte
OrderBy
duas principais projeções.OrderBy
armazena em buffer a sequência inteira, pelas razões óbvias .Minha solução limpa para a situação em que a chave é única nos dois enumeráveis:
tão
saídas:
fonte
Junção externa completa para duas ou mais tabelas: Primeiro extraia a coluna na qual deseja ingressar.
Em seguida, use a junção externa esquerda entre a coluna extraída e as tabelas principais.
fonte
Eu escrevi essa classe de extensões para um aplicativo há talvez 6 anos e uso-a desde então em muitas soluções sem problemas. Espero que ajude.
editar: notei que alguns podem não saber como usar uma classe de extensão.
Para usar esta classe de extensão, basta referenciar seu espaço para nome na sua classe, adicionando a seguinte linha usando joinext;
^ isso deve permitir que você veja o sentido das funções de extensão em qualquer coleção de objetos IEnumerable que você usar.
Espero que isto ajude. Deixe-me saber se ainda não está claro, e espero escrever um exemplo de exemplo de como usá-lo.
Agora aqui está a classe:
fonte
SelectMany
não pode ser convertida em uma árvore de expressão digna de LINQ2SQL, ao que parece.Eu acho que a cláusula de junção LINQ não é a solução correta para esse problema, porque o objetivo da cláusula de junção não é acumular dados da maneira necessária para esta solução de tarefa. O código para mesclar coleções separadas criadas se torna muito complicado, talvez seja bom para fins de aprendizado, mas não para aplicativos reais. Uma das maneiras de resolver esse problema está no código abaixo:
Se coleções reais são grandes para a formação do HashSet, em vez disso, cada loop pode ser usado no código abaixo:
fonte
Obrigado a todos pelas postagens interessantes!
Eu modifiquei o código porque no meu caso eu precisava
Para os interessados, este é o meu código modificado (em VB, desculpe)
fonte
Ainda outra junção externa completa
Como não estava tão feliz com a simplicidade e a legibilidade das outras proposições, acabei com isso:
Ele não tem a pretensão de ser rápido (cerca de 800 ms para ingressar em 1000 * 1000 em uma CPU de 2020m: 2,4ghz / 2cores). Para mim, é apenas uma junção externa completa compacta e casual.
Funciona da mesma forma que uma junção externa completa do SQL (conservação duplicada)
Felicidades ;-)
A ideia é
Aqui está um teste sucinto que o acompanha:
Coloque um ponto de interrupção no final para verificar manualmente se ele se comporta conforme o esperado
}
fonte
Eu realmente odeio essas expressões linq, é por isso que o SQL existe:
Crie isso como sql view no banco de dados e importe-o como entidade.
É claro que a união (distinta) de junções esquerda e direita também será suficiente, mas é estúpido.
fonte