LINQ aggregate multiple tables - c#

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()
});
}

Related

Linq, linq where in where in where

I have a SQL statement like this:
SELECT
projects.name, projects.id,
issues.subject, issues.description,
time_entries.spent_on, time_entries.hours
FROM
(time_entries
INNER JOIN
projects ON time_entries.project_id = projects.id)
INNER JOIN
issues ON time_entries.issue_id = issues.id
WHERE
(((projects.id) IN (26, 27))
AND ((issues.subject) NOT LIKE "*zlecane*"))
AND MONTH(spent_on) = MONTH(CURRENT_TIMESTAMP)
GROUP BY
name, id, subject, spent_on
and I need to write it in Linq.
I write it like this but it doesn't work - it returns an empty list:
ProjectIdsForBudgets - is list with 26 and 27
Projects.Include(x => x.Issues)
.ThenInclude(k => k.TimeEntries)
.Where(x => ProjectIdsForBudgets.Contains(x.Id) &&
x.Issues.Any(y => !y.Subject.Contains("zlecane") &&
y.TimeEntries.Any(K => K.SpentOn >= firstDayOfTheMonth)
)
)
Group by we can skip
Can you help me please?
So you have a sequence of Projects, and a sequence of Issues. Furthermore you have a sequence of TimeEntries where every TimeEntry belongs to exactly one Project using foreign key TimeEntry.ProjectId. Every TimeEntry also belongs to exactly one Issue using foreign key TimeEntry.IssueId
You want to join these three tables on their primary and foreign keys. You only want to keep some elements of the joined results (Where). The remaining elements should be grouped into groups with same name, id, subject and spentOn. Finally you want to select some properties from every group with its elements.
If you have something similar to entity framework, your Project will have a virtual collection of TimeEntries and every TimeEntry has a virtual reference to a Project. If that method is used, entity framework will understand that a join is needed. You can usee the references between the tables.
That is discussed later. First the method using the join three tables
Method: Join three tables
Normally I would use Method syntax. However method syntax looks hideous if you join three tables. Those are the only times I use Query syntax.
I do this in smaller steps. Feel free to make it one big linq
var joinedItems = from timeEntry in timeEntries
join project in project on timeEntry.ProjectId equals project.Id
join issue in issues on timeEntry.IssueId equals issue.Id
select new
{
TimeEntry = timeEntry,
Project = Project,
Issue = Issue,
};
Keep only some of the joined items:
int currentMonth = DateTime.Now.Month;
var subsetJoinedItems = joinedItems.Where(joinedItem =>
(joinedItem.Project.Id == 26 || joinedItem.Project.Id == 27)
&& joinedItem.Issue.Subject.Contains("zlecane") // consider ignore case
&& joinedItem.TimeEntry.SpentOn.Month == currentMonth);
Group the resulting elements into groups of same [name, id, subject, spent_on]:
var groups = subsetJoinedItems.GroupBy(joinedItem => new
{
ProjectId = joinedItem.Project.Id,
ProjectName = joinedItem.Project.Name,
Subject = joinedItem.Issue.Subject,
Spent_on = joinedItem.TimeEntry.SpentOn,
});
Finally, from every group select the items you want to keep:
var result = groups.Select(group => new
{
ProjectId = group.Key.ProjectId,
ProjectName = group.Key.ProjectName,
Subject = group.Key.Subject,
SpentOn = group.Key.SpentOn,
// remaining properties: SQL query seems incorrect
...
});
Using Class Relationships as in entity framework
If you have something similar to entity framework, then your one-to-many relationships would have been implemented using collections:
class TimeEntry
{
public int Id {get; set;}
// every TimeEntry belongs to exactly one Project using foreign key
public int ProjectId {get; set;}
public virtual Project Project {get; set;}
// every TimeEntry belongs to exactly one Issue using foreign key
public int IssueId {get; set;}
public virtual Issue Issue {get; set;}
}
If you have something like this, you don't have to do the joins yourself, entity framework would understand it:
var result = TimeEntries.Where(timeEntry =>
(timeEntry.ProjectId == 26 || timeEntry.ProjectId == 27)
&& timeEntry.Issue.Subject.Contains("zlecane") // consider ignore case
&& TimeEntry.SpentOn.Month == currentMonth)
.GroupBy(timeEntry => new
{
ProjectId = joinedItem.Project.Id,
ProjectName = joinedItem.Project.Name,
Subject = joinedItem.Issue.Subject,
Spent_on = joinedItem.TimeEntry.SpentOn,
})
.Select(group => new
{
ProjectId = group.Key.ProjectId,
ProjectName = group.Key.ProjectName,
Subject = group.Key.Subject,
SpentOn = group.Key.SpentOn,
...
});

C# linq lambda join and select syntax

I'm looking for the counterpart of Msql query:
SELECT per.*,add.addressDescription FROM Persons per
JOIN Address add ON per.AddressId = add.AddressId
I have this query:
var query = persons.JOIN(address,per = person.addressId,add = addressId
(per,add) =>
new Persons{
addressDescription = add.addressDescription,
PersonId = per.PersonId,
PersonFirstName = per.PersonFirstName
PersonLastName = per.PersonLastName})
Is there a way to populate Persons.addressDescription without assigning individually the other properties of Persons? Imagine if Persons have 10 more properties.
I would like to refrain from using loops like:
foreach(Person person in PersonList)
{
foreach(Address address in AddressList)
{
if(person.addressId == address.addressId){
person.addressDescription = address.addressDescription
}
}
}
var query = persons.join(address,
per = person.addressId,
add = addressId
(per,add) =>
{
per.addressDescription = add.addressDescription;
return per;
});
var id = 1;
var query = database.Posts // your starting point - table in the "from" statement
.Join(database.Post_Metas, // the source table of the inner join
post => post.ID, // Select the primary key (the first part of the "on" clause in an sql "join" statement)
meta => meta.Post_ID, // Select the foreign key (the second part of the "on" clause)
(post, meta) => new { Post = post, Meta = meta }) // selection
.Where(postAndMeta => postAndMeta.Post.ID == id); // where statement

Efficient way to do a grouping, then a join to data from a different context/db

Given the following objects from different databases, what would be the most efficient way of joining them with a LinQ statement and grouping them by QID and then SectionID. Ideally they should of course be in the same DB, but I gotta work with this. Using EF6, so separate context for each db.
Each Question can have many UserAnswer(s)
,there's also a TestResult object in the same DB as UserAnswer, so queries start from its navigation properties.
UserAnswer
Line ID [PK]
string Answer
Int QID
Int SectionID
Question
int QID [PK]
SectionID [FK]
string QuestionText
Other properties that I wish to project into anonymous type first
Something like the below should work, but was just wondering if there's a more efficient way than performing all the below queries.
var ColOfQDataForQ = Test.TestSections.SelectMany(x => x.TestQuestions.Select(q => new {q.QID, q.SID, AllMustBeCorrect = q.AllMustBeCorrect ?? false, Points = q.Points ?? 1, q.QuestionType, q.QuestionChoices }));
var ColOfUaByQgroups = from userAnswer in TestResult.UserAnswers
group userAnswer by userAnswer.QID into AnswersByQuestionCollection
select new { AnswersByQuestionCollection, QID =AnswersByQuestionCollection.First().QID };
var ColOfUaAndQdataJoin = from UaByQgroup in ColOfUaByQgroups join QData in ColOfQDataForQ
on UaByQgroup.QID equals QData.QID select new { Qdata = QData, UserAnswers = UaByQgroup.AnswersByQuestionCollection, SID = QData.SID, QID = QData.QID };
var ColOfUaQdataJoinGroupedBySec = from UaQdJoin in ColOfUaAndQdataJoin group
UaQdJoin by UaQdJoin.SID into UaQdJoinBySec
select new { UaQdJoinBySec, SID = UaQdJoinBySec.First().SID};
If you've got enough RAM for the data, an approach which will be efficient computationally, and in terms of database round trips, would be to read a table or joined tables from the first database into a Dictionary object then reference this in your LINQ to the the second database, something like:
var questions = db1.Questions.ToDictionary(x => x.QID);
var result = db2.UserAnswers
.Select(x => new { Question = questions[x.QID], Answer = x })
.Select(x => new { x.Question.QID, x.Question.QuestionText, x.Answer.LineId }); //etc.

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();

Join/Where with LINQ and Lambda

I'm having trouble with a query written in LINQ and Lambda. So far, I'm getting a lot of errors here's my code:
int id = 1;
var query = database.Posts.Join(database.Post_Metas,
post => database.Posts.Where(x => x.ID == id),
meta => database.Post_Metas.Where(x => x.Post_ID == id),
(post, meta) => new { Post = post, Meta = meta });
I'm not sure if this query is correct.
I find that if you're familiar with SQL syntax, using the LINQ query syntax is much clearer, more natural, and makes it easier to spot errors:
var id = 1;
var query =
from post in database.Posts
join meta in database.Post_Metas on post.ID equals meta.Post_ID
where post.ID == id
select new { Post = post, Meta = meta };
If you're really stuck on using lambdas though, your syntax is quite a bit off. Here's the same query, using the LINQ extension methods:
var id = 1;
var query = database.Posts // your starting point - table in the "from" statement
.Join(database.Post_Metas, // the source table of the inner join
post => post.ID, // Select the primary key (the first part of the "on" clause in an sql "join" statement)
meta => meta.Post_ID, // Select the foreign key (the second part of the "on" clause)
(post, meta) => new { Post = post, Meta = meta }) // selection
.Where(postAndMeta => postAndMeta.Post.ID == id); // where statement
You could go two ways with this. Using LINQPad (invaluable if you're new to LINQ) and a dummy database, I built the following queries:
Posts.Join(
Post_metas,
post => post.Post_id,
meta => meta.Post_id,
(post, meta) => new { Post = post, Meta = meta }
)
or
from p in Posts
join pm in Post_metas on p.Post_id equals pm.Post_id
select new { Post = p, Meta = pm }
In this particular case, I think the LINQ syntax is cleaner (I change between the two depending upon which is easiest to read).
The thing I'd like to point out though is that if you have appropriate foreign keys in your database, (between post and post_meta) then you probably don't need an explicit join unless you're trying to load a large number of records. Your example seems to indicate that you are trying to load a single post and its metadata. Assuming that there are many post_meta records for each post, then you could do the following:
var post = Posts.Single(p => p.ID == 1);
var metas = post.Post_metas.ToList();
If you want to avoid the n+1 problem, then you can explicitly tell LINQ to SQL to load all of the related items in one go (although this may be an advanced topic for when you're more familiar with L2S). The example below says "when you load a Post, also load all of its records associated with it via the foreign key represented by the 'Post_metas' property":
var dataLoadOptions = new DataLoadOptions();
dataLoadOptions.LoadWith<Post>(p => p.Post_metas);
var dataContext = new MyDataContext();
dataContext.LoadOptions = dataLoadOptions;
var post = Posts.Single(p => p.ID == 1); // Post_metas loaded automagically
It is possible to make many LoadWith calls on a single set of DataLoadOptions for the same type, or many different types. If you do this lots though, you might just want to consider caching.
Daniel has a good explanation of the syntax relationships, but I put this document together for my team in order to make it a little simpler for them to understand. Hope this helps someone
Your key selectors are incorrect. They should take an object of the type of the table in question and return the key to use in the join. I think you mean this:
var query = database.Posts.Join(database.Post_Metas,
post => post.ID,
meta => meta.Post_ID,
(post, meta) => new { Post = post, Meta = meta });
You can apply the where clause afterwards, not as part of the key selector.
Posting because when I started LINQ + EntityFramework, I stared at these examples for a day.
If you are using EntityFramework, and you have a navigation property named Meta on your Post model object set up, this is dirt easy. If you're using entity and don't have that navigation property, what are you waiting for?
database
.Posts
.Where(post => post.ID == id)
.Select(post => new { post, post.Meta });
If you're doing code first, you'd set up the property thusly:
class Post {
[Key]
public int ID {get; set}
public int MetaID { get; set; }
public virtual Meta Meta {get; set;}
}
I've done something like this;
var certificationClass = _db.INDIVIDUALLICENSEs
.Join(_db.INDLICENSECLAsses,
IL => IL.LICENSE_CLASS,
ILC => ILC.NAME,
(IL, ILC) => new { INDIVIDUALLICENSE = IL, INDLICENSECLAsse = ILC })
.Where(o =>
o.INDIVIDUALLICENSE.GLOBALENTITYID == "ABC" &&
o.INDIVIDUALLICENSE.LICENSE_TYPE == "ABC")
.Select(t => new
{
value = t.PSP_INDLICENSECLAsse.ID,
name = t.PSP_INDIVIDUALLICENSE.LICENSE_CLASS,
})
.OrderBy(x => x.name);
It could be something like
var myvar = from a in context.MyEntity
join b in context.MyEntity2 on a.key equals b.key
select new { prop1 = a.prop1, prop2= b.prop1};
This linq query Should work for you. It will get all the posts that have post meta.
var query = database.Posts.Join(database.Post_Metas,
post => post.postId, // Primary Key
meta => meat.postId, // Foreign Key
(post, meta) => new { Post = post, Meta = meta });
Equivalent SQL Query
Select * FROM Posts P
INNER JOIN Post_Metas pm ON pm.postId=p.postId
Query Syntax for LINQ Join
var productOrderQuery = from product in Product.Setup()//outer sequence
join order in OrderDetails.Setup()//inner sequence
on product.Id equals order.ProductId //key selector
select new//result selector
{
OrderId = order.Id,
ProductId = product.Id,
PurchaseDate = order.PurchaseDate,
ProductName = product.Name,
ProductPrice = product.Price
};
Method Syntax for LINQ Join
var productOrderMethod = Product.Setup().//outer sequence
Join(OrderDetails.Setup(), //inner sequence
product => product.Id//key selector
,order=> order.ProductId //key selector
,(product,order)=> //projection result
new
{
OrderId = order.Id,
ProductId = product.Id,
PurchaseDate = order.PurchaseDate,
ProductName = product.Name,
ProductPrice = product.Price
}
);
Product.cs for reference
class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public static IEnumerable<Product> Setup()
{
return new List<Product>()
{
new Product(){Id=1, Name="Bike", Price=30.33M },
new Product(){Id=2, Name="Car", Price=50.33M },
new Product(){Id=3, Name="Bus", Price=60.33M }
};
}
}
OrderDetails.cs class for reference
class OrderDetails
{
public int Id { get; set; }
public virtual int ProductId { get; set; }
public DateTime PurchaseDate { get; set; }
public static IEnumerable<OrderDetails> Setup()
{
return new List<OrderDetails>()
{
new OrderDetails(){Id=1, ProductId=1, PurchaseDate= DateTime.Now },
new OrderDetails(){Id=2, ProductId=1, PurchaseDate=DateTime.Now.AddDays(-1) },
new OrderDetails(){Id=3, ProductId=2, PurchaseDate=DateTime.Now.AddDays(-2) }
};
}
}
1 equals 1 two different table join
var query = from post in database.Posts
join meta in database.Post_Metas on 1 equals 1
where post.ID == id
select new { Post = post, Meta = meta };

Categories