Je souhaite rechercher tous les éléments d'une collection qui ne correspondent à aucune autre. Les collections ne sont pas du même type, cependant; Je veux écrire une expression lambda pour spécifier l'égalité.
A LINQPad exemple de ce que j'essaie de faire:
void Main()
{
var employees = new[]
{
new Employee { Id = 20, Name = "Bob" },
new Employee { Id = 10, Name = "Bill" },
new Employee { Id = 30, Name = "Frank" }
};
var managers = new[]
{
new Manager { EmployeeId = 20 },
new Manager { EmployeeId = 30 }
};
var nonManagers =
from employee in employees
where !(managers.Any(x => x.EmployeeId == employee.Id))
select employee;
nonManagers.Dump();
// Based on cdonner's answer:
var nonManagers2 =
from employee in employees
join manager in managers
on employee.Id equals manager.EmployeeId
into tempManagers
from manager in tempManagers.DefaultIfEmpty()
where manager == null
select employee;
nonManagers2.Dump();
// Based on Richard Hein's answer:
var nonManagers3 =
employees.Except(
from employee in employees
join manager in managers
on employee.Id equals manager.EmployeeId
select employee);
nonManagers3.Dump();
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Manager
{
public int EmployeeId { get; set; }
}
Ce qui précède fonctionne et renverra la facture de l’employé (n ° 10). Il ne semble pas élégant, cependant, et il peut être inefficace avec des collections plus grandes. En SQL, je ferais probablement une jointure gauche et trouverais les articles dont le deuxième identifiant était NULL. Quelle est la meilleure pratique pour faire cela dans LINQ?
EDIT: mis à jour pour éviter que les solutions dépendant de l'ID égal à l'index.
EDIT: Ajout de la solution Cdonner - Quelqu'un a quelque chose de plus simple?
EDIT: Ajout d'une variante à la réponse de Richard Hein, mon favori actuel. Merci à tous pour d'excellentes réponses!
C'est presque la même chose que d'autres exemples mais moins de code:
employees.Except(employees.Join(managers, e => e.Id, m => m.EmployeeId, (e, m) => e));
Ce n'est pas plus simple que employés.Where (e =>! Managers.Any (m => m.EmployeeId == e.Id)) ou votre syntaxe d'origine, cependant.
var nonManagers = (de e1 chez les employés sélectionnez e1) .Exception ( de m chez les gestionnaires de e2 chez les employés où m. EmployeeId == e2.Id Sélectionnez e2);
/// <summary>
/// This method returns items in a set that are not in
/// another set of a different type
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TOther"></typeparam>
/// <typeparam name="TKey"></typeparam>
/// <param name="items"></param>
/// <param name="other"></param>
/// <param name="getItemKey"></param>
/// <param name="getOtherKey"></param>
/// <returns></returns>
public static IEnumerable<T> Except<T, TOther, TKey>(
this IEnumerable<T> items,
IEnumerable<TOther> other,
Func<T, TKey> getItemKey,
Func<TOther, TKey> getOtherKey)
{
return from item in items
join otherItem in other on getItemKey(item)
equals getOtherKey(otherItem) into tempItems
from temp in tempItems.DefaultIfEmpty()
where ReferenceEquals(null, temp) || temp.Equals(default(TOther))
select item;
}
Je ne me souviens pas où j'ai trouvé cette méthode.
C'est un peu tard (je sais).
J'examinais le même problème et envisageais un HashSet en raison de divers indices de performances allant dans cette direction, inc. @ Skeet's Intersection de plusieurs listes avec IEnumerable.Intersect () - et demandé à mon bureau et le consensus était qu'un HashSet serait plus rapide et plus lisible:
HashSet<int> managerIds = new HashSet<int>(managers.Select(x => x.EmployeeId));
nonManagers4 = employees.Where(x => !managerIds.Contains(x.Id)).ToList();
Ensuite, une solution encore plus rapide utilisant des tableaux natifs pour créer une solution de type masque masqué (la syntaxe des requêtes de tableau natif me dissuaderait de les utiliser, sauf pour des raisons de performances extrêmes).
Pour donner un peu de crédibilité à cette réponse après une période extrêmement longue, j'ai étendu votre programme linqpad et vos données avec les temps afin que vous puissiez comparer les six options suivantes:
void Main()
{
var employees = new[]
{
new Employee { Id = 20, Name = "Bob" },
new Employee { Id = 10, Name = "Kirk NM" },
new Employee { Id = 48, Name = "Rick NM" },
new Employee { Id = 42, Name = "Dick" },
new Employee { Id = 43, Name = "Harry" },
new Employee { Id = 44, Name = "Joe" },
new Employee { Id = 45, Name = "Steve NM" },
new Employee { Id = 46, Name = "Jim NM" },
new Employee { Id = 30, Name = "Frank"},
new Employee { Id = 47, Name = "Dave NM" },
new Employee { Id = 49, Name = "Alex NM" },
new Employee { Id = 50, Name = "Phil NM" },
new Employee { Id = 51, Name = "Ed NM" },
new Employee { Id = 52, Name = "Ollie NM" },
new Employee { Id = 41, Name = "Bill" },
new Employee { Id = 53, Name = "John NM" },
new Employee { Id = 54, Name = "Simon NM" }
};
var managers = new[]
{
new Manager { EmployeeId = 20 },
new Manager { EmployeeId = 30 },
new Manager { EmployeeId = 41 },
new Manager { EmployeeId = 42 },
new Manager { EmployeeId = 43 },
new Manager { EmployeeId = 44 }
};
System.Diagnostics.Stopwatch watch1 = new System.Diagnostics.Stopwatch();
int max = 1000000;
watch1.Start();
List<Employee> nonManagers1 = new List<Employee>();
foreach (var item in Enumerable.Range(1,max))
{
nonManagers1 = (from employee in employees where !(managers.Any(x => x.EmployeeId == employee.Id)) select employee).ToList();
}
nonManagers1.Dump();
watch1.Stop();
Console.WriteLine("Any: " + watch1.ElapsedMilliseconds);
watch1.Restart();
List<Employee> nonManagers2 = new List<Employee>();
foreach (var item in Enumerable.Range(1,max))
{
nonManagers2 =
(from employee in employees
join manager in managers
on employee.Id equals manager.EmployeeId
into tempManagers
from manager in tempManagers.DefaultIfEmpty()
where manager == null
select employee).ToList();
}
nonManagers2.Dump();
watch1.Stop();
Console.WriteLine("temp table: " + watch1.ElapsedMilliseconds);
watch1.Restart();
List<Employee> nonManagers3 = new List<Employee>();
foreach (var item in Enumerable.Range(1,max))
{
nonManagers3 = employees.Except(employees.Join(managers, e => e.Id, m => m.EmployeeId, (e, m) => e)).ToList();
}
nonManagers3.Dump();
watch1.Stop();
Console.WriteLine("Except: " + watch1.ElapsedMilliseconds);
watch1.Restart();
List<Employee> nonManagers4 = new List<Employee>();
foreach (var item in Enumerable.Range(1,max))
{
HashSet<int> managerIds = new HashSet<int>(managers.Select(x => x.EmployeeId));
nonManagers4 = employees.Where(x => !managerIds.Contains(x.Id)).ToList();
}
nonManagers4.Dump();
watch1.Stop();
Console.WriteLine("HashSet: " + watch1.ElapsedMilliseconds);
watch1.Restart();
List<Employee> nonManagers5 = new List<Employee>();
foreach (var item in Enumerable.Range(1, max))
{
bool[] test = new bool[managers.Max(x => x.EmployeeId) + 1];
foreach (var manager in managers)
{
test[manager.EmployeeId] = true;
}
nonManagers5 = employees.Where(x => x.Id > test.Length - 1 || !test[x.Id]).ToList();
}
nonManagers5.Dump();
watch1.Stop();
Console.WriteLine("Native array call: " + watch1.ElapsedMilliseconds);
watch1.Restart();
List<Employee> nonManagers6 = new List<Employee>();
foreach (var item in Enumerable.Range(1, max))
{
bool[] test = new bool[managers.Max(x => x.EmployeeId) + 1];
foreach (var manager in managers)
{
test[manager.EmployeeId] = true;
}
nonManagers6 = employees.Where(x => x.Id > test.Length - 1 || !test[x.Id]).ToList();
}
nonManagers6.Dump();
watch1.Stop();
Console.WriteLine("Native array call 2: " + watch1.ElapsedMilliseconds);
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Manager
{
public int EmployeeId { get; set; }
}
var nonmanagers = employees.Select(e => e.Id)
.Except(managers.Select(m => m.EmployeeId))
.Select(id => employees.Single(e => e.Id == id));
Regardez la fonction Except () LINQ. Il fait exactement ce dont vous avez besoin.
C'est mieux si vous avez quitté rejoindre l'élément et filtrer avec une condition nulle
var finalcertificates = (from globCert in resultCertificate
join toExcludeCert in certificatesToExclude
on globCert.CertificateId equals toExcludeCert.CertificateId into certs
from toExcludeCert in certs.DefaultIfEmpty()
where toExcludeCert == null
select globCert).Union(currentCertificate).Distinct().OrderBy(cert => cert.CertificateName);
Les gestionnaires sont aussi des employés! Ainsi, la classe Manager
devrait être une sous-classe de la classe Employee
(ou, si vous n'aimez pas cela, alors elles devraient toutes les deux être une sous-classe d'une classe parent ou créer une classe NonManager
.
Ensuite, votre problème est aussi simple que de mettre en œuvre l'interface IEquatable
sur votre super classe Employee
(pour GetHashCode
, renvoyez simplement la EmployeeID
), puis d'utiliser ce code:
var nonManagerEmployees = employeeList.Except(managerList);