How to perform a Linq2Sql query on the following dataset - c#

I have the following tables:
Person(Id, FirstName, LastName)
{
(1, "John", "Doe"),
(2, "Peter", "Svendson")
(3, "Ola", "Hansen")
(4, "Mary", "Pettersen")
}
Sports(Id, Name)
{
(1, "Tennis")
(2, "Soccer")
(3, "Hockey")
}
SportsPerPerson(Id, PersonId, SportsId)
{
(1, 1, 1)
(2, 1, 3)
(3, 2, 2)
(4, 2, 3)
(5, 3, 2)
(6, 4, 1)
(7, 4, 2)
(8, 4, 3)
}
Looking at the tables, we can conclude the following facts:
John plays Tennis
John plays Hockey
Peter plays Soccer
Peter plays Hockey
Ola plays Soccer
Mary plays Tennis
Mary plays Soccer
Mary plays Hockey
Now I would like to create a Linq2Sql query which retrieves the following:
Get all Persons who play Hockey and Soccer
Executing the query should return: Peter and Mary
Anyone has any idea's on how to approach this in Linq2Sql?

One of the great things about Linq is that you don't HAVE to write this all as one monolithic query because it won't actually execute until you enumerate the results anyway. You could write a single query, but you don't have to. Instead, you can write this as multiple, separate queries, increasing the readability, and clarifying your intent.
var sportIds = Sports
.Where(s => s.Name == "Hockey" || s.Name == "Soccer")
.Select(s => s.Id);
var people = Person.Where(p => SportsPerPerson
.Count(spp => (spp.PersonId == p.Id)
&& sportIds.Contains(spp.SportId)) == 2);
First, we get the collection of sport Ids we're interested in. Then, we find all the people with two sports in the first list. Although it's expressed as multiple queries, Linq will compress it all into one operation for us when we finally enumerate the results.
EDIT: Here is a complete test class illustrating the query:
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace L2STest
{
public class Sport
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class SportPerPerson
{
public int Id { get; set; }
public int PersonId { get; set; }
public int SportId { get; set; }
}
[TestClass]
public class SportsTest
{
private List<Person> persons;
private List<Sport> sports;
private List<SportPerPerson> sportsPerPerson;
[TestInitialize]
public void MyTestInitialize()
{
persons = new List<Person>
{
new Person {Id = 1, FirstName = "John", LastName = "Doe"},
new Person {Id = 2, FirstName = "Peter", LastName = "Svendson"},
new Person {Id = 3, FirstName = "Ola", LastName = "Hansen"},
new Person {Id = 4, FirstName = "Marv", LastName = "Petterson"},
};
sports = new List<Sport>
{
new Sport {Id = 1, Name = "Tennis"},
new Sport {Id = 2, Name = "Soccer"},
new Sport {Id = 3, Name = "Hockey"},
};
sportsPerPerson = new List<SportPerPerson>
{
new SportPerPerson {Id = 1, PersonId = 1, SportId = 1},
new SportPerPerson {Id = 2, PersonId = 1, SportId = 3},
new SportPerPerson {Id = 3, PersonId = 2, SportId = 2},
new SportPerPerson {Id = 4, PersonId = 2, SportId = 3},
new SportPerPerson {Id = 5, PersonId = 3, SportId = 2},
new SportPerPerson {Id = 6, PersonId = 3, SportId = 1},
new SportPerPerson {Id = 7, PersonId = 4, SportId = 2},
new SportPerPerson {Id = 8, PersonId = 4, SportId = 3},
};
}
[TestMethod]
public void QueryTest()
{
var sportIds = sports
.Where(s => s.Name == "Hockey" || s.Name == "Soccer")
.Select(s => s.Id);
var people = persons.Where(p => sportsPerPerson
.Count(spp => (spp.PersonId == p.Id)
&& sportIds.Contains(spp.SportId)) == 2);
Assert.AreEqual(2, people.Count());
Assert.AreEqual("Peter", people.First().FirstName);
Assert.AreEqual("Marv", people.Last().FirstName);
}
}
}

Related

Is there a way in C# to use EXCEPT to compare 2 lists when one doesn't have Id yet?

I get listA from the database. I get listB from a file that the end user uploads, which I convert to the right class in a list. For the example I give you a list that came from the database and one that the user uploaded. You can find samples of these below.
I need to check if listB is in listA, but when I use Except I get the entire list because Id isn't in listB (auto numeration in database). I have a solution for now, but is there a better way to do this?
What I have tried so far:
List<CustomerGroup> listC = listA.Except(listB).ToList();
//Returns listA in listC because Id isn't the same
This is what I am using now, but it seems so redundant.
foreach (CustomerGroup itemToCheck in listB)
{
var foundItem = listA.Find(dbItem => dbItem.FirstName== itemToCheck.FirstName &&
dbItem.FamilyName== itemToCheck.FamilyName &&
dbItem.Quantity == itemToCheck.Quantity &&
dbItem.Discount == itemToCheck.Discount);
if(foundItem != null)
{
listA.Remove(foundItem);
listB.Remove(itemToCheck);
}
}
foreach (CustomerGroup itemToCheck in listB)
{
// other code to check here
}
List<CustomerGroup> listA = new List<CustomerGroup>(){
new CustomerGroup {Id = 1, FirstName = "Anna", FamilyName = "Shrek", Quantity = 5, Discount = 10},
new CustomerGroup {Id = 2, FirstName = "Elsa", FamilyName = "Fiona", Quantity = 5, Discount = 10},
new CustomerGroup {Id = 3, FirstName = "Olaf", FamilyName = "Donkey", Quantity = 5, Discount = 10},
new CustomerGroup {Id = 4, FirstName = "Sven", FamilyName = "Dragon", Quantity = 5, Discount = 5},
new CustomerGroup {Id = 5, FirstName = "Kristoff", FamilyName = "Puss", Quantity = 5, Discount = 10},
new CustomerGroup {Id = 6, FirstName = "Sven", FamilyName = "Dragon", Quantity = 10, Discount = 15},
new CustomerGroup {Id = 7, FirstName = "Kristoff", FamilyName = "Puss", Quantity = 10, Discount = 30}
};
List<CustomerGroup> listB = new List<CustomerGroup>(){
new CustomerGroup { FirstName = "Anna", FamilyName = "Shrek", Quantity = 5, Discount = 10},
new CustomerGroup { FirstName = "Elsa", FamilyName = "Fiona", Quantity = 5, Discount = 8},
new CustomerGroup { FirstName = "Sven", FamilyName = "Dragon", Quantity = 5, Discount = 5},
new CustomerGroup { FirstName = "Kristoff", FamilyName = "Puss", Quantity = 5, Discount = 10},
new CustomerGroup { FirstName = "Sven", FamilyName = "Dragon", Quantity = 10, Discount = 15},
new CustomerGroup { FirstName = "Kristoff", FamilyName = "Puss", Quantity = 10, Discount = 30},
new CustomerGroup { FirstName = "Hans", FamilyName = "Farquaad", Quantity = 20, Discount = 40}
};
public class CustomerGroup{
public int Id {get; set;}
public string FirstName {get; set;}
public string FamilyName {get; set;}
public int Quantity{get; set;}
public int Discount {get; set;}
}
Implement IEqualityComparer<> for class CustomerGroup and then use Except(),
like,
public class CompareCustomerGroup : IEqualityComparer<CustomerGroup>
{
public bool Equals(CustomerGroup dbCustomer, CustomerGroup uploadedCustomer)
{
if(dbCustomer == null || uploadedCustomer == null)
return false;
else
return dbCustomer.FirstName == uploadCustomer.FirstName &&
dbCustomer.FamilyName == uploadCustomer.FamilyName &&
dbCustomer.Quantity == uploadCustomer.Quantity &&
dbCustomer.Discount == uploadCustomer.Discount;
}
public int GetHashCode(CustomerGroup customGroup)
{
return HashCode.Combine(customGroup.FirstName, customGroup.FamilyName , customGroup.Quantity, customGroup.Discount);
}
}
Now try Except(),
List<CustomerGroup> listC = listA.Except(listB, new CompareCustomerGroup()).ToList();
You can use your own IEqualityComparer, that ignores the ID property:
class MyEqualityComparer : IEqualityComparer<CustomerGroup>
{
public bool Equals(CustomerGroup x, CustomerGroup y)
{
if(x is null) return y is null;
if(y is null) return false;
return x.FirstName == y.FirstName && x.FamilyName == y.FamilyName && x.Quantity == y.Quantity && x.Discount == y.Discount;
}
public int GetHashCode(CustomerGroup codeh)
{
return 0;
}
}
Pass this to Except:
List<CustomerGroup> listC = listA.Except(listB, new MyEqualityComparer()).ToList();
Online demo: https://dotnetfiddle.net/gogy9g

Linq query to return object list with multiple child arrays

Incoming list
var list = new List<Franchise>()
{
new Franchise()
{Id = 10, Name = "Franchise1", Code= "DD1", IsDomestic= 1, ParentCompanyId=1, GroupId=100 },
new Franchise()
{Id = 10, Name = "Franchise1", Code= "DD1", IsDomestic= 1, ParentCompanyId=2, GroupId=100 },
new Franchise()
{Id = 10, Name = "Franchise1", Code= "DD1", IsDomestic= 1, ParentCompanyId=3, GroupId=200 },
new Franchise()
{Id = 15, Name = "Franchise5", Code= "FD1", IsDomestic= 0, ParentCompanyId=4, GroupId=300 },
new Franchise()
{Id = 15, Name = "Franchise5", Code= "FD1", IsDomestic= 0, ParentCompanyId=3, GroupId=300 },
new Franchise()
{Id = 15, Name = "Franchise5", Code= "FD1", IsDomestic= 0, ParentCompanyId=2, GroupId=400 },
};
I want this to be transformed to list of the class below using LINQ
public class FranchiseNew
{
public int Id { get; set; }
public string Name{ get; set; }
public int[] CategoryIds { get; set; }
public int[] DivisionIds { get; set; }
public int IsDomestic{ get; set; }
}
output - one row per franchise with ParentCompanyIds and GroupIds in arrays
var list = new List<Franchise>()
{
new Franchise()
{Id = 10, Name = "Franchise1", Code= "DD1", IsDomestic= 1, ParentCompanyIds=[1, 2, 3], GroupIds = [100, 200 ]},
new Franchise()
{Id = 15, Name = "Franchise2", Code= "FD1", IsDomestic= 0, ParentCompanyIds=[4, 3, 2], GroupIds = [300, 400] }
};
What is the efficient LINQ query to achieve this? Thanks in advance.
You can try like below:
var collectionGroup = list.GroupBy(x => new { x.Name, x.Id, x.Code, x.IsDomestic }).ToList();
var result = collectionGroup.Select(x => new FranchiseNew
{
Id = x.Key.Id,
Name = x.Key.Name,
Code = x.Key.Code,
IsDomestic = x.Key.IsDomestic,
CategoryIds = x.GroupBy(s => s.ParentCompanyId).Select(y => y.Key).ToArray(),
DivisionIds = x.GroupBy(s => s.GroupId).Select(y => y.Key).ToArray()
}).ToList();
And in you're FranchiseNew model, add Code field.

Filter tree list retaining parent using LINQ

I have a model class which looks something like this:
public class Employee
{
public int Id {get;set;}
public int ParentId {get;set;}
public string Name{get;set;}
public string Designation {get;set;}
public List<Employee> Reportees {get;set;}
}
using which I simulated a demo tree list with data:
var employees = new List<Employee>
{
new Employee
{
Id = 1,
ParentId = 0,
Name = "A",
Designation = "CEO",
Reportees = new List<Employee>
{
new Employee { Id = 2, ParentId = 1,Name = "B",Designation = "Manager",Reportees = new List<Employee>
{
new Employee { Id = 4, ParentId = 2, Name = "D", Designation = "Lead", Reportees = new List<Employee>
{
new Employee { Id = 6, ParentId = 4, Name = "F", Designation = "Developer", Reportees = new List<Employee>() },
new Employee { Id = 7, ParentId = 4, Name = "G", Designation = "Developer", Reportees = new List<Employee>() }
}},
new Employee { Id = 3, ParentId = 1,Name = "C",Designation = "Manager",Reportees = new List<Employee>
{
new Employee { Id = 5, ParentId = 3, Name = "E", Designation = "Lead", Reportees = new List<Employee>
{
new Employee { Id = 8, ParentId = 5, Name = "H", Designation = "Developer", Reportees = new List<Employee>() }
}}
}}
}}
}
}
};
I need to filter the above list to show even the parent object if the child satisfies the filter condition. Also a catch here is if it is a parent which satisfies the condition and the children do not they are omitted.
Are there any extensions available or can I use LINQ to do this?
To make it more clear this is what is the expected filtered list in case the filter search criteria are the Ids 6 and 7:
var employees = new List<Employee>
{
new Employee {Id = 1, ParentId = 0, Name = "A",Designation = "CEO", Reportees = new List<Employee>
{
new Employee {Id = 2, ParentId = 1, Name = "B",Designation = "Manager", Reportees = new List<Employee>
{
new Employee {Id = 4, ParentId = 2, Name = "D", Designation = "Lead", Reportees = new List<Employee>
{
new Employee {Id = 6, ParentId = 4, Name = "F", Designation = "Developer", Reportees = new List<Employee>() },
new Employee {Id = 7, ParentId = 4, Name = "G", Designation = "Developer", Reportees = new List<Employee>() }
}}
}}
}}
};
and if the Id to filter is 6:
new List<Employee> employees
{
new Employee{Id = 1,ParentId = 0,Name = "A",Designation = "CEO",Reportees = new List<Employee>{
new Employee{Id = 2,ParentId = 1,Name = "B",Designation = "Manager",Reportees = new List<Employee>{
new Employee{Id = 4, ParentId = 2, Name = "D", Designation = "Lead", Reportees = new List<Employee>
{
new Employee{Id = 6, ParentId = 4, Name = "F", Designation = "Developer", Reportees = new List<Employee> () }
}
}
}
};
and if the Id to filter is 2:
new List<Employee> employees
{
new Employee{Id = 1,ParentId = 0,Name = "A",Designation = "CEO",Reportees = new List<Employee>{
new Employee{Id = 2,ParentId = 1,Name = "B",Designation = "Manager",Reportees = new List<Employee>()}
}
};C#
Here's the method that you need:
List<Employee> FilterEmployees(List<Employee> source, Func<Employee, bool> predicate) =>
source
.Select(e => new { employee = e, reportees = FilterEmployees(e.Reportees, predicate) })
.Where(x => predicate(x.employee) || x.reportees.Any())
.Select(x => new Employee
{
Id = x.employee.Id,
ParentId = x.employee.ParentId,
Name = x.employee.Name,
Designation = x.employee.Designation,
Reportees = x.reportees,
})
.ToList();
The two crucial lines of this method are the first Select and the Where.
The first Select keeps track of the current employee and recursively calls FilterEmployees to get any Reportees that match.
The Where decides if we want to keep the current employee - the criteria being did the employee meet the predicate or did any of its Reportees.
Finally, it just builds the new Employee to return.
For FilterEmployees(employees, e => e.Id == 6 || e.Id == 7) we get:
For FilterEmployees(employees, e => e.Id == 6) we get:
For FilterEmployees(employees, e => e.Id == 2) we get:
I have Two solution for you:
1)Create a function to access employee object from its ID
2)create a childsID property like:
new Employee{Id = 1,ParentId = 0, childsID=new list<int>(){2,4,6,7}, ...}
after both you can easily do the job with linq

Recursive query in EntityFramework throws NotSupportedException

I want to build a recursive query in EntityFramework 6.2.0 to get an employee and all his "direct" (one level) and all other subordinates down all the hierarchy.
My point was to use List<IQueryable<T>> to build a whole query and then run it just one time with one rount-trip to the database.
Here is my attempt to do it:
private static List<IQueryable<Employee>> queryables = new List<IQueryable<Employee>>();
static void Main(string[] args)
{
using (var db = new EmployeeContext())
{
IQueryable<Employee> managers = db.Employee.Where(x => x.Id == 1);
GetSlaves(managers);
// the System.NotSupportedException occurs in this line
IQueryable<Employee> employees = queryables.Aggregate(Queryable.Union);
// but throws here
var res = (
from e in employees
join d in db.EmployeeDoc on e.Id equals d.EmployeeId
select new { e.Id, e.EmployeeName, d.DocNumber }).ToList();
}
Console.ReadLine();
}
static void GetSlaves(IQueryable<Employee> managers)
{
if (managers != null)
{
queryables.Add(managers);
foreach (var m in managers)
{
Console.WriteLine($"{m.Id} {m.EmployeeName} {m.Position}");
GetSlaves(m.Slaves.AsQueryable());
}
}
}
But I get an System.NotSupportedException: 'Unable to create a constant value of type 'EF6.Employee'. Only primitive types or enumeration types are supported in this context.'
The above C# code was an attempt to replace the following SQL code:
declare #managerId int = 1
;with Employees(Id, EmployeeName)
as
(
select e.Id, e.EmployeeName from Employee as e
where Id = #managerId
union all
select e.Id, e.EmployeeName from Employee as e
inner join Employees as em on e.ManagerId = em.Id
)
select e.Id, e.EmployeeName, d.DocNumber
from Employees e
inner join EmployeeDocuments d on e.Id = d.EmployeeId
UPDATED:
Here is a SQL script that I use:
create table Employee
(
Id int not null identity(1,1) primary key,
EmployeeName varchar(20),
Position varchar(30),
ManagerId int constraint FK_Employee foreign key references Employee(Id)
)
insert into Employee (EmployeeName, Position, ManagerId) values
('John', 'CEO', NULL),
('Marry', 'Head of sales division', 1),
('Mike', 'Head of HR division', 1),
('Jack', 'Sales manager', 2),
('Olivia', 'Sales manager', 2),
('Sophia', 'Sales manager', 2),
('Nadya', 'HR manager', 3),
('Tim', 'HR manager', 3),
('Jim', 'Salesman', 4),
('Sergey', 'Salesman', 4),
('Dmitry', 'Salesman', 5),
('Irina', 'Salesman', 5),
('William', 'Assistant', 8)
select * from Employee
Create table EmployeeDocuments
(
Id int not null identity(1,1) primary key,
DocNumber varchar(20),
EmployeeId int not null constraint FK_Docs_Employee foreign key references Employee(Id)
)
insert into EmployeeDocuments (DocNumber, EmployeeId) values
('1/2019-01-15', 1), ('3/2019-02-25', 3), ('4/2019-01-31', 4), ('9/2019-02-28', 9)
select * from EmployeeDocuments
Here is Employee class:
[Table("Employee")]
public partial class Employee
{
public Employee()
{
Slaves = new HashSet<Employee>();
}
public int Id { get; set; }
[StringLength(20)]
public string EmployeeName { get; set; }
[StringLength(30)]
public string Position { get; set; }
public int? ManagerId { get; set; }
public virtual ICollection<Employee> Slaves { get; set; }
public virtual Employee Manager { get; set; }
public virtual ICollection<EmployeeDoc> EmployeeDocs { get; set; }
}
The error is due to the query being static. Not sure why you need a query when the code is very simple. See below
class Program
{
static void Main(string[] args)
{
Employee.GetSlaves();
}
}
public partial class Employee
{
public static List<Employee> employees = new List<Employee>() {
new Employee() {Id = 1, EmployeeName = "John", Position = "CEO", ManagerId = null},
new Employee() {Id = 2,EmployeeName = "Marry", Position = "Head of sales division", ManagerId = 1},
new Employee() {Id = 3,EmployeeName = "Mike", Position = "Head of HR division", ManagerId = 1},
new Employee() {Id = 4,EmployeeName = "Jack", Position = "Sales manager", ManagerId = 2},
new Employee() {Id = 5,EmployeeName = "Olivia", Position = "Sales manager", ManagerId = 2},
new Employee() {Id = 6,EmployeeName = "Sophia", Position = "Sales manager", ManagerId = 2},
new Employee() {Id = 7,EmployeeName = "Nadya", Position = "HR manager", ManagerId = 3},
new Employee() {Id = 8,EmployeeName = "Tim", Position = "HR manager", ManagerId = 3},
new Employee() {Id = 9,EmployeeName = "Jim", Position = "Salesman", ManagerId = 4},
new Employee() {Id = 10,EmployeeName = "Sergey", Position = "Salesman", ManagerId = 4},
new Employee() {Id = 11,EmployeeName = "Dmitry", Position = "Salesman", ManagerId = 5},
new Employee() {Id = 12,EmployeeName = "Irina", Position = "Salesman", ManagerId = 5},
new Employee() {Id = 13,EmployeeName = "William", Position = "Assistant", ManagerId = 8}
};
public static void GetSlaves()
{
Employee ceo = employees.Where(x => x.ManagerId == null).First();
GetSlavesRecursive(ceo);
}
public int Id { get; set; }
public string EmployeeName { get; set; }
public string Position { get; set; }
public int? ManagerId { get; set; }
public virtual ICollection<Employee> Slaves { get; set; }
public virtual Employee Manager { get; set; }
//public virtual ICollection<EmployeeDoc> EmployeeDocs { get; set; }
static void GetSlavesRecursive(Employee manager)
{
manager.Slaves = employees.Where(x => x.ManagerId == manager.Id).ToList();
foreach (Employee slave in manager.Slaves)
{
GetSlavesRecursive(slave);
}
}
}

Linq Expressing a query as a Lambda query

I have created a query that only shows students that have a date of birth that is less than 1990. I am trying to express this same query as a Lambda query but do not know how to go about it. This is my code so far:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Student
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int ID { get; set; }
public DateTime DateOfBirth { get; set; }
}
class LINQ2
{
static void Main()
{
IEnumerable<Student> students = new List<Student>()
{
new Student {FirstName = "Jim", LastName = "Smith", DateOfBirth = new DateTime(1990, 5, 21), ID = 1},
new Student {FirstName = "Diane", LastName = "Sawyer", DateOfBirth = new DateTime(1992, 11, 1), ID = 2},
new Student {FirstName = "Steve", LastName = "Thomas", DateOfBirth = new DateTime(1994, 4, 4), ID = 3},
new Student {FirstName = "Pablo", LastName = "Dicaz", DateOfBirth = new DateTime(1973, 3, 30), ID = 4},
new Student {FirstName = "Hannu", LastName = "Korppi", DateOfBirth = new DateTime(1988, 6, 16), ID = 5},
new Student {FirstName = "Marie", LastName = "St. Claude", DateOfBirth = new DateTime(1982, 1, 19), ID = 6}
};
IEnumerable<Student> query = from s in students
where s.DateOfBirth.Year < 1990
orderby s.FirstName
select s;
foreach (Student stud in query)
{
Console.WriteLine(stud.FirstName);
Console.ReadLine();
}
}
}
}
If you mean the extension method syntax by Lambda query then you can do it like this:
var query = students
.Where(s => s.DateOfBirth.Year < 1990)
.OrderBy(s => s.FirstName);

Categories