Linq self-join query - c#

I need some help with Linq self-join.
I have the following classs:
public class Owner
{
public string OwnerId {get;set;}
public string Name {get;set;}
public string Area {get;set;}
public string City {get;set;}
public string Sex {get;set;}
public List<Dog> dog {get;set;}
}
And table....
ID OwnerId OwnerName TypeId TypeName TypeValue TypeCodeId
1 1 John 1 Area United States 440
2 1 John 2 City Los-Angeles 221
3 1 John 3 Sex Female 122
4 2 Mary 1 Area Mexico 321
4 2 Mary 2 City Cancun 345
............................................................
I need to parse results of table1 into list of owners the fastest way possible.
Note: Type can be null, but I still need to show that owner (So, I assume left join should work).
Here's What I do. (owners is a webservice class that contains table1 results)
public IEnumerable<Owner> GetOwners() {
return (from owner in owners
join area in owners into owner_area
from oa in owner_area.DefaultIfEmpty()
join City in owners into owner_city
from oc in owner_city.DefaultIfEmpty()
join sex in owners into owner_sex
from os in owner_sex.DefaultIfEmpty()
where oa.TypeId == 1 && oc.TypeId ==2 && os.TypeId ==3
select new Owner() {OwnerId = owner.OwnerId,
Name = owner.Name,
Area = oa.TypeValue,
City = oc.TypeValue,
Sex = os.TypeValue}).Distinct();
}
This query has several issues:
It returns multiple results and distinct does not work as expected
I've tried to use GroupBy but it says that cannot implicitly convert Owner into IEnumerable <int, Owner>
It is super slow
How can I get distinct record with self join and improve performance?
Thanks
UPDATE:
Thank you guys for your ansewers, testing now, but I figured out that I forgot to supply one more thing. I've added a new column called TypeCodeId to the table layout(see above)
User can filter values based on their selection. So, I have TypeCodeId + TypeValue dictionaries for Area, City and Sex. All of those parameters are optional (If the user didn't select any, I just show them all records.
So, Assume that the user has selected filter Area: Unites States and filter City: Los Angeles
them my query would look like this:
Select Projects where Area equals United States(440) and City equals Los Angeles(221)
If Only Area:Mexico was selected then my query would read something like this:
Select Projects where Area equals Mexico(321)
I'm not sure how to do optional where clauses with what you've provided in the examples.

Since the table isn't normalized we need to get the distict users from the objects/table. This can be accomplished with:
owners.Select(o => new { o.OwnerId, o.OwnerName }).Distinct()
And then we need to join the "types" with two matching values, one for the ownerId and another for the specific type.
var ownerQuery =
from o in owners.Select(o => new { o.OwnerId, o.OwnerName }).Distinct()
join area in owners on new { o.OwnerId, TypeId = 1 } equals new { area.OwnerId, area.TypeId } into areas
from area in areas.DefaultIfEmpty()
join city in owners on new { o.OwnerId, TypeId = 2 } equals new { city.OwnerId, city.TypeId } into cities
from city in cities.DefaultIfEmpty()
join sex in owners on new { o.OwnerId, TypeId = 3 } equals new { sex.OwnerId, sex.TypeId } into sexes
from sex in sexes.DefaultIfEmpty()
select new
{
owner = o,
Area = (area != null) ? area.TypeValue : null,
City = (city != null) ? city.TypeValue : null,
Sex = (sex != null) ? sex.TypeValue : null,
};
You may need to change the projection in the above example.

For best performance if think this is the way to do it.
public IEnumerable<Owner> GetOwners(IEnumerable<Tuple<int, int>> filter)
{
var q = (from o in owners
join f in filter on
new {o.TypeId, o.TypeCodeId} equals
new {TypeId = f.Item1, TypeCodeId = f.Item2}
select o).ToList();
var dic = q.ToDictionary (o => new {o.OwnerId, o.TypeId}, o => o.TypeValue);
foreach (var o in q.Select(o => new { o.OwnerId, o.OwnerName }).Distinct())
{
var owner = new Owner()
{
OwnerId = o.OwnerId,
Name = o.OwnerName
};
string lookup;
if(dic.TryGetValue(new {o.OwnerId, TypeId = 1}, out lookup))
owner.Area = lookup;
if(dic.TryGetValue(new {o.OwnerId, TypeId = 2}, out lookup))
owner.City = lookup;
if(dic.TryGetValue(new {o.OwnerId, TypeId = 3}, out lookup))
owner.Sex = lookup;
yield return owner;
}
}
To get even a little more performance you can write an IEqualityComparer class that only compares int OwnerId and send it into the Distinct function

Related

How to programmatically add JOINs to query from list using EF Core

I am trying to query data from database using EF Core, but the scenery is a bit complicated for me. I will try to be clear and synthesize what I want to accomplish.
There are three tables involved:
Table WORK_TO_DO - Columns: ID, DESCRIPTION
Table PARAM_DEFINITIONS_FOR_WORK - Columns: ID, NAME
Table PARAM_VALUES_FOR_WORK - Columns: WORK_TO_DO_ID, PARAM_DEFINITION_ID, VALUE
Let's say these tables have their classes as below.
public class WorkToDo
{
public int Id { get; set; }
public string Description { get; set; }
}
public class ParamDefinition
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ParamValue
{
public int WorkToDoId { get; set; }
public int ParamDefinitionId { get; set; }
public string Value { get; set; }
}
I have a list of ParamValue items with ParamDefinitionId and Value populated, but without WorkToDoId.
I want to query all WorkToDo items that match the ParamValue items, considering all the ParamValue items and not just any of them.
Let me explain with example records on each table:
WORK_TO_DO
ID
DESCRIPTION
1
Work Example A
2
Work Example B
PARAM_DEFINITIONS_FOR_WORK
ID
NAME
101
Param Definition X
102
Param Definition Y
103
Param Definition W
104
Param Definition Z
105
Param Definition +
PARAM_VALUES_FOR_WORK
WORK_TO_DO_ID
PARAM_DEFINITION_ID
VALUE
1
101
Param Value J
1
102
Param Value K
2
103
Param Value L
2
104
Param Value M
2
105
Param Value N
So, let's say my list of ParamValues has two items: ParamDefinitionId = 101, Value = "Param Value J" and ParamDefinitionId = 102, Value = "Param Value K". I would like to retrieve the WorkToDo of Id = 1.
If my list of ParamValues had, instead, three items:
ParamDefinitionId = 103, Value = "Param Value L"
ParamDefinitionId = 104, Value = "Param Value M"
ParamDefinitionId = 105, Value = "Param Value N"
Then I would like my query to retrieve the WorkToDo of Id = 2.
Note that the size of ParamValues list is variable!
I'd like to say that I have tried a solution, but the truth is I don't even know how to begin. I've searched on the web but had no luck.
I only have an idea of how I would do this using SQL:
SELECT DISTINCT WORK_TO_DO.ID, WORK_TO_DO.DESCRIPTION
FROM WORK_TO_DO
INNER JOIN PARAM_VALUES_FOR_WORK PV1 ON PV1.WORK_TO_DO_ID = WORK_TO_DO.ID
INNER JOIN PARAM_VALUES_FOR_WORK PV2 ON PV2.WORK_TO_DO_ID = WORK_TO_DO.ID
(... Adding as many INNER JOINs as needed based on list of ParamValues)
INNER JOIN PARAM_VALUES_FOR_WORK PVX ON PVX.WORK_TO_DO_ID = WORK_TO_DO.ID
WHERE PV1.PARAM_DEFINITION_ID = :ParamValues[0].ParamDefinitionId
AND PV1.VALUE = :ParamValues[0].Value
AND PV2.PARAM_DEFINITION_ID = :ParamValues[1].ParamDefinitionId
AND PV2.VALUE = :ParamValues[1].Value
(... Adding as many conditions as needed based on list of ParamValues)
AND PVX.PARAM_DEFINITION_ID = :ParamValues[X].ParamDefinitionId
AND PVX.VALUE = :ParamValues[X].Value
Basically I want to add JOINs and filters to the query based on my list of ParamValues. How can I do this?
Use FilterByItems extension and you can generate desired query:
var requiredCount = ParamValues.Count();
var query = context.WorkToDo
.Where(w => context.ParamValue
.Where(pv = pv.WorkToDoId == w.Id)
.FilterByItems(ParamValues, (pv, v) => pv.ParamDefinitionId == v.ParamDefinitionId && pv.Value == v.Name, true)
.Count() >= requiredCount
);

LINQ aggregate multiple tables

I have the following database. A list of companies. Each company has multiple employees and multiple contractors.
dbo.Companies (CompanyId, Name)
dbo.Employees (Id, CompanyId, Name ...)
dbo.Contractors(Id, CompanyId, Name...)
I want to get output like so
CompanyName #Employees #Contractors
abc 0 10
xyz 25 999
I am trying to avoid doing 2 queries, one to get contractors and one to get employees and then merging them. Is there a way to get it done in one go?
n.b. i have
class CompanySummary{
string Name {get; set;}
int EmpCount {get; set;}
int ConCount {get; set;}
}
so I can use a collection of this type as result
If you have defined navigation properties (and if you haven't, may be it's a good time to do that), the query should be quite simple:
var query = from c in db.Companies
select new CompanySummary
{
Name = c.Name,
EmpCount = c.Employees.Count(),
ConCount = c.Contractors.Count(),
};
Of course you can do that manually, but the above is the preferred way with EF:
var query = from c in db.Companies
select new CompanySummary
{
Name = c.Name,
EmpCount = db.Employees.Count(e => e.CompanyId == c.Id),
ConCount = db.Contractors.Count(cc => cc.CompanyId == c.Id),
};
In both cases you'll get a single SQL query.
If you are using Entity Framework to communicate with the database and have the tables linked with foreign keys you can probably do it in one query. It would look something like this:
IEnumerable<CompanySummary> companySummary = null;
using (CompanyEntities dbContext = new CompanyEntities())
{
companySummary = dbContext.Companies
.Include(company => company.Employees)
.Include(company => company.Contractors)
.Select(company => new CompanySummary
{
Name = company.Name,
EmpCount = company.Employees.Count(),
ConCount = company.Contractors.Count()
});
}

Inner Join on Entity Framework

There is a table name Product:
|ProductID|ProductName|
1 abc
2 xyz
and there is another table Product Status.
The column Result in the below has three values:
Result
Value | Meta
0 Not checked
1 Failed
2 Passed
ProductCheckList
ID(AutoGenerated) | ProductID(Foreign Key) | Stage | Result |
1 1 1 1
2 1 2 2
In this table every product has to go through five different stages. If the product passes all the stages then the product is given quality checked status , If the product fails any on of the stages is is given as quality failed and send back to production.
I have to show a list of all the products with there quality status and depending on its quality state highlight the row with different row color. We are using Entity Framework and I am new to this.
I have thought of making a wrapper class for this.
Public class ProductWrapper
{
public Product product{get;set;}
Public string QualityStatus{get;set;}
public string BgColor {get;set;}
}
I am writing this LINQ query:
UtilitiesEntities context = new UtilitiesEntities();
List<ProductWrapper> wrapperList = new List<ProductWrapper>();
var request = from product in context.Product
join productCheck in context.ProductCheckList
on product.productId equals productCheck .productID
// may be do group by or something i get the result and assign the values.
select new List<ProductWrapper>
{
};
I am not able to write the query and add the where condition to fetch the result a list of wrapper class to pass to my view with the desired result.
If I understood correctly your request, you want something like this:
string goodQualityColor = "Green";
string badQualityColor = "Red";
string notCheckedColor = "Gray";
string notCheckedStatus = "Not Checked";
string failedStatus = "Failed";
string passedStatus = "Passed";
Dictionary<int,string> results= new Dictionary<int,string>();
results.Add(2,goodQualityColor);
results.Add(1,badQualityColor);
results.Add(0,notCheckedColor);
Dictionary<int,string> qualityStatuses = new Dictionary<int,string>();
results.Add(2,passedStatus);
results.Add(1,failedStatus);
results.Add(0,notCheckedStatus);
var request = (from product in context.Product
join productCheck in context.ProductCheckList
on product.productId equals productCheck.productID
select new
{
Product = product,
QualityStatus = productCheck.Result,
Result = productCheck.Result
}).ToList();
var finalResults = (from req in request
select new ProductWrapper
{
Product = req.Product,
QualityStatus = qualityStatuses.FirstOrDefault(s => s.Key == req.QualityStatus).Value,
BgColor = results.FirstOrDefault (s => s.Key == req.Result).Value
}).ToList<ProductWrapper>();
I would create an outer join using the into keyword and then do my statusCheck in the select part.
Could would look something like this :
var data = (from product in context.Product
join productCheck in context.ProductCheckList on product.productId equals productCheck.productID into productChecks
select new ProductWrapper
{
product = product,
passedBool = productChecks.All(pc => pc.Result == 2) && productChecks.Count() == 5
}).ToList();

How to Avoid Recreating Object When Using Let with LINQ

Here is my data:
private List<Department> Data
{
get
{
return new List<Department>
{
new Department{
Id = 1,
Name = "Tech",
Employees = new List<Employee>{
new Employee{Name = "x", Id = 1 },
new Employee{ Name = "y", Id = 2}
}
},
new Department{
Id = 2,
Name = "Sales",
Employees = new List<Employee>{
new Employee{Name = "a", Id = 3},
new Employee {Name = "b", Id = 4}
}
}
};
}
}
and here I am getting a list of all employees with their appropriate departments:
List<Employee> employees = (from department in Departments
let d = department
from e in d.Employees
select new Employee{
Id = e.Id,
Name = e.Name
Department = d
}).ToList();
What is bothering me is that I have to recreate my Employee object in order to attach the appropriate department to it. Is there a way that I could write my LINQ statement where I don't have to recreate the Employee?
There might be a better way to phrase this question-- so feel free to let me know is there is.
Edit
The reason I'm going down this path is that I'm storing my data by serializing my department:
[
{
"Id":1,
"Name":"Sales",
"Employees":[{"Id":2,"Name":"x"},{"Id":1,"Name":"y"}]
},
{
"Id":2,
"Name":"Tech",
"Employees":[{"Id":3,"Name":"d"},{"Id":4,"Name":"f"}]
}
]
It looks like you want to use LINQ to update an instance. This is not the intended use. Use LINQ to query the instances you want to have, and then loop over the results to update. (non-nested) Loops are not evil.
var query =
from d in Departments
from e in d.Employees
select new { Employee = e, Department = d };
foreach(var x in query)
{
x.Employee.Department = x.Department;
}
You should not have this problem in the first place - You should fully construct your Employee instances when you initially create them, not sometime later - if an employee needs a department to be used, you should add a constructor that allows/enforces providing it:
public Employee(int id, string name, Department department)
{
...
}
You could, if you really, really want, use a let-clause for a side-effect, since assignment expressions return a value:
List<Employee> employees = (from department in Departments
from e in department.Employees
let _ = e.Department = department
select e).ToList();
Also I fully agree with BrokenGlass...
Using let is redundant and not useful in your example query.
Besides, LINQ is not the right tool here. You want to affect the state of the objects you're querying (i.e. creating side-effects), which is generally not recommended.
By direct comparison, this is a better alternative to what you're trying do to:
foreach(var department in Departments)
foreach(var employee in department.Employees)
employee.Department = department;
If you can however, you should do the department assignment at the time you add the employees to the department, either in an AddEmployee method in the Department class, or maybe in a Employee.Department property setter.

Querying 2 Sets of Complex-Objects Using Linq

I have two lists comprised of different complex-objects, and each one is from 2 separate data-sources. One list may-or-may-not contain records. When any records exist in the "optional" list I need the "normal" list to be further-filtered.
Unfortunately, I can only find very simple examples here and online, which is why I am asking this question.
The Pseudo-Logic Goes Like This:
When QuickFindMaterial records exist, get all DataSource records where query.Name is in the QuickFindMaterial.Material collection. If no QuickFindMaterial records exist do not affect the final result. Lastly, select all distinct DataSourcerecords.
The Classes Looks Like:
public class QuickFindMaterial
{
public string SiteId { get; set; }
public string Material { get; set; }
}
The Code Looks Like:
I have commented-out my failed WHERE logic below
var dataSource = DocumentCollectionService.ListQuickFind();
var quickFindMaterial = ListMaterialBySiteID(customerSiteId);
var distinct = (from query in dataSource
select new
{
ID = query.DocumentID,
Library = query.DocumentLibrary,
ModifiedDate = query.DocumentModifiedDate,
Name = query.DocumentName,
Title = query.DocumentTitle,
Type = query.DocumentType,
Url = query.DocumentUrl,
})
//.Where(x => x.Name.Contains(quickFindMaterial.SelectMany(q => q.Material)))
//.Where(x => quickFindMaterial.Contains(x.Name))
.Distinct();
I think this is what you want:
.Where(x => !quickFindMaterial.Any() || quickFindMaterial.Any(y => x.Name == y.Material))
You could join on Name -> Material
Example:
var distinct = (from query in dataSource
join foo in quickFindMaterial on query.Name equals foo.Material
select new
{
ID = query.DocumentID,
Library = query.DocumentLibrary,
ModifiedDate = query.DocumentModifiedDate,
Name = query.DocumentName,
Title = query.DocumentTitle,
Type = query.DocumentType,
Url = query.DocumentUrl,
}).Distinct();

Categories