LINQ - Grouping by left join queries - c#

I have three tables as follows:
public class Employee
{
public Guid Id { get; set; }
public string FirstName { get; set; } = null!;
public string LastName { get; set; } = null!;
}
public class Answer
{
public Guid Id { get; set; }
public Guid QuestionId { get; set; }
public Guid AppraiserId { get; set; }//Employee who appraises
public Guid AppraisedId { get; set; }//Employee who has been appraised
}
public class FinalizedEmployee
{
public long Id { get; set; }
public Guid AppraiserId { get; set; }
public Guid AppraisedId { get; set; }
}
Every employee has an entry in the answers table by default. and when they answer all the questions we add a record in FinalizedEmployees.
Now I want to write a query to show all employees whether they have answered all the questions or not.
The output will be something like this:
Employee
Has answered all the questions
Employee 1
True
Employee 2
False
Employee 3
True
This is what I have tried so far:
var attendees = from answers in dbContext.Answers
join employees in dbContext.Employees
on answers.AppraiserId equals employees.Id
join finalize in dbContext.FinalizedEmployees
on answers.AppraiserId equals finalize.AppraisedId into finalized
from completed in finalized.DefaultIfEmpty()
where answers.AppraisalId == request.appraisalId
group employees by new { appraiser=answers.AppraiserId,hasComplete=completed!=null} into attendee
select new {attendee.Key.appraiser,attendee.Key.hasComplete };
The output is correct but only gives me employee Ids instead of the employee itself.
How can I write this query?
Is there any better way?

Every employee has an entry in answers table. and if they have answered all the questions there is a record in FinalizedEmployee table.
To check whether that the Employee has answered all the questions, you need a LEFT JOIN query for Employee to FinalizedEmployee tables.
With LEFT JOIN:
Guarantee all the records from Employee (LEFT) table will be queried.
If the employee's Id exists in FinalizedEmployee, the HasAnsweredAllQuestion will be true.
(from a in dbContext.Employees
join b in dbContext.FinalizedEmployees on a.Id equals b.AppraisedId into ab
from b in ab.DefaultIfEmpty()
select new
{
Id = a.Id,
Name = a.FirstName + " " + a.LastName,
HasAnsweredAllQuestion = b != null
}).ToList();
Another approach suggested by #Corey which was achieved with EXISTS will be:
(from a in dbContext.Employees
select new
{
Id = a.Id,
Name = a.FirstName + " " + a.LastName,
HasAnsweredAllQuestion = dbContext.FinalizedEmployees.Any(x => x.AppraisedId == a.Id)
}).ToList();

Related

LINQ To Entity - Inner Join issue

I have two related tables like below :
Users :
public partial class Users
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Users()
{
}
public int ID { get; set; }
public int UserType_ID { get; set; }
public string Email { get; set; }
public virtual UserTypes UserTypes { get; set; }
}
UserTypes :
public partial class UserTypes
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public UserTypes()
{
this.Users = new HashSet<Users>();
}
public int ID { get; set; }
public string Name { get; set; }
public string Title { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Users> Users { get; set; }
}
For access Name of UserType i wrote this linq to entity :
string[] UserTypes = new string[1];
using (Crypto_Entities entities = new Crypto_Entities())
{
int User_ID_Integer = int.Parse(User_ID.Trim());
var user = (from User in entities.Users
//join UserType in entities.UserTypes on User.UserType_ID equals UserType.ID
where User.ID == User_ID_Integer
select User).FirstOrDefault();
if (user != null)
{
UserTypes[0] = user.UserTypes.Name;
}
}
My question is why user.Name does not work for my purpose and what is the benefit of join in linq to entity?
If i remove join as i did in my query i still can see Name field of UserType with user.UserTypes.Name.
You do not need join if you have defined correctly navigation properties. And if you just need Name, do not retrieve full entity.
string[] UserTypes = new string[1];
using (Crypto_Entities entities = new Crypto_Entities())
{
int User_ID_Integer = int.Parse(User_ID.Trim());
var query =
from User in entities.Users
where User.ID == User_ID_Integer
select User.UserTypes.Name;
var name = query.FirstOrDefault();
if (name != null)
{
UserTypes[0] = name;
}
}
If you use navigation property in query, EF automatically generates all needed joins. But if you just select whole entity without defining Include - EF will not load related data. It makes sense, because otherwise you may load almost whole database if there are a lot of relations.
Since you have set up the relations in your entities you don't need to manually write join to load related data:
var user = entities.Users
.Include(u => u.UserTypes)
.Where(u => u.ID == User_ID_Integer)
.FirstOrDefault();
As for your join being useless - EF Core translates the code into actual SQL (which you can check) and since you are not selecting any data from the joined table - it is as useless as it would be in SQL query where you have selected fields only from one table of join result.

Eager loading of virtual link being lost when using linq 'select new ...'

I have a problem with my virtual link. Entity framework doesn't seem to eager load in cases where the UserID = 0. I'm using Entity Framework 6 .NET Framework 4.5.2 and VS2013
public class Booking
{
[Key]
public int Booking_ID { get; set; }
public int Customer_ID { get; set; }
public DateTime StartDate_DT { get; set; }
public DateTime BookingDate_DT { get; set; }
public int UserID { get; set; }
public virtual UserProfile UserProfile { get; set; }
}
This does not include any record from the Booking table with a UserID of 0:
var list = (from booking in _db.Booking
where booking.Customer_ID == customerId
orderby booking.StartDate_DT, booking.BookingDate_DT
select new CustomerBookingLine
{
BookingId = b.Booking_ID,
StartDate = b.StartDate_DT,
BookingDate = b.BookingDate_DT,
UserName = b.UserProfile != null ? b.UserProfile.UserName : null
}).ToList();
return list;
This does return the booking records including eager-loaded user profile records
var list = (from b in _db.Booking
where b.Customer_ID == customerId
orderby booking.StartDate_DT, booking.BookingDate_DT
select b).ToList();
return (from b in bookingList
select new CustomerBookingLine
{
BookingId = b.Booking_ID,
StartDate = b.StartDate_DT,
BookingDate = b.BookingDate_DT,
UserName = b.UserProfile != null ? b.UserProfile.UserName : null
}).ToList();
I've tried using includes but the 2nd method is the only way I can get it to work. Can anyone explain why?

Convert simple Left Outer Join and group by SQL statement into Linq

2 tables: User and Alarm
Table:User
UserID(int),
FullName(varchar)
Table:Alarm
AssignedTo(int),
Resolved(bool)
Query:
SELECT u.Fullname, COUNT(resolved) as Assigned, SUM(CONVERT(int,Resolved)) as Resolved, COUNT(resolved) - SUM(CONVERT(int,Resolved)) as Unresolved
FROM Alarm i LEFT OUTER JOIN Users u on i.AssignedTo = u.UserID
GROUP BY u.Fullname
Results:
Fullname Assigned Resolved Unresolved
User1 204 4 200
User2 39 9 30
User3 235 200 35
User4 1 0 1
User5 469 69 400
For the life of me I can't figure out how to make this into a Linq query. I am having trouble with the grouping function.
I've looked a countless examples and none have my combination of Left Outer join with grouping or they are so complicated that I can't figure out how to make it work with mine. Any help here would be Greatly appreciated!!!
Update:
I may not have been clear in what I'm looking for. I am looking for the alarms grouped by the AssignedTo Column which is a userid... Except, I want to replace that userid with the FullName that is located in the users table. Someone had posted and deleted something close except it gave me all users in the user table which is not what I'm looking for..
Update 2: See my answer below
Assuming that you have the following models:
This is the model for Alarm:
public class Alarm
{
public int id { get; set; }
public int AssignedTo { get; set; }
[ForeignKey("AssignedTo")]
public virtual User User { get; set; }
public bool Resolved { get; set; }
}
This is the model for User:
public class User
{
public int UserID { get; set; }
public string FullName { get; set; }
public virtual ICollection<Alarm> Alarms { get; set; }
public User()
{
Alarms = new HashSet<Alarm>();
}
}
This is the model that will hold the alarm statistics for each user:
public class UserStatistics
{
public string FullName { get; set; }
public int Assigned { get; set; }
public int Resolved { get; set; }
public int Unresolved { get; set; }
}
You can then do the following:
var query = context.Users.Select(
user =>
new UserStatistics
{
FullName = user.FullName,
Assigned = user.Alarms.Count,
Resolved = user.Alarms.Count(alarm => alarm.Resolved),
Unresolved = user.Alarms.Count(alarm => !alarm.Resolved)
});
var result = query.ToList();
By the way, you can also modify the query and remove Unresolved = user.Alarms.Count(alarm => !alarm.Resolved), and then make the Unresolved property a calculated property like this:
public class UserStatistics
{
public string FullName { get; set; }
public int Assigned { get; set; }
public int Resolved { get; set; }
public int Unresolved
{
get { return Assigned - Resolved; }
}
}
This will make the generated SQL query simpler.
I finally figured it out.
This:
var results = alarms.GroupBy(x => x.AssignedTo)
.Join(users, alm => alm.Key , usr => usr.UserID, (alm, usr) => new {
Fullname = usr.FullName,AssignedNum = alm.Count(),
Resolved = alm.Where(t=>t.resolved == true).Select(y => y.resolved).Count(),
Unresolved = alm.Where(t=>t.resolved == false).Select(y => y.resolved).Count() });
Reproduces This:
SELECT u.Fullname, COUNT(resolved) as Assigned, SUM(CONVERT(int,Resolved)) as Resolved,
COUNT(resolved) - SUM(CONVERT(int,Resolved)) as Unresolved
FROM Alarm i LEFT OUTER JOIN Users u on i.AssignedTo = u.UserID
GROUP BY u.Fullname
The result is grouped by the AssignedTo (int) but AssignedTo is not selected. Instead FullName is selected from the joined user table.
Many thanks to everyone that tried to help! I learned a lot from your answers.
For bonus points, how would I write my lamdbda answer in a SQL like syntax?
Try this :
from u in context.User
join a in context.Alarm on u.UserID equals a.AssignedTo into g1
from g2 in g1.DefaultIfEmpty()
group g2 by u.Fullname into grouped
select new { Fullname = grouped.Key, Assigned = grouped.Count(t=>t.Resolved != null), Resolved = grouped.Sum
(t => int.Parse(t.Resolved)), Unresolved = (grouped.Count(t=>t.Resolved != null) - grouped.Sum
(t => int.Parse(t.Resolved)))}
I guess it is not necessarily to use "Grouping" for this query in Linq because the combination of "LEFT JOIN" + "GROUP BY" changed them over to "INNER JOIN".
var results =
from u in users
join a in alarms on u.UserID equals a.AssignedTo into ua
select new
{
Fullname = u.FullName,
Assigned = ua.Count(),
Resolved = ua.Count(a => a.Resolved),
Unresolved = ua.Count(a => !a.Resolved)
};
foreach (var r in results)
{
Console.WriteLine(r.Fullname + ", " + r.Assigned + ", " + r.Resolved + ", " + r.Unresolved);
}

How to join two customer lists into one list

I have two classes:
1. a customer class, which contains the id and name of the customer
2. a customer_details class, which contain the address of the customer and so on
class customer
{
public int customer_id { get; set; }
public string name { get; set; }
//and so on
}
class customer_details
{
public int customer_id { get; set; }
public string address { get; set; }
//and so on
}
My question is, how can I join a List<customer> and a List<customer_details> to a single list, so it shows me the customer_id, name and address in a single list...
Thanks for your answers
You must use the Join Method
Here is an example with your classes
List<customer> customers = new List<customer>();
List<customer_details> details = new List<customer_details>();
var query = (from c in customers
join d in details on c.customer_id equals d.customer_id
select new
{
c.customer_id,
c.name,
d.address,
}).ToList();
Following is the example using Fluent syntax, which is rather compact and preferred:
customers.Join(
details,
c=>c.customer_id,
d=>d.customer_id,
(c,d)=>new {c.customer_id, c.name, d.address}
).ToList()

LINQ + join with nested foreach razor output writing out title lines from groupby

I have a basic store model. I have Items, Items have a category, and categories have a menu. I want to list the items of a given menu grouped by the item categories so that it writes the category name then lists the items under it and moves on to the next category. I'm getting beat up by the syntax. Please help.
Items:
public class Item
{
public int ID { get; set; }
public string Name { get; set; }
public double Price { get; set; }
public int Order { get; set; }
public bool Active { get; set; }
public Category Category { get; set; }
}
Categories:
public class Category
{
public int ID { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
public int Order { get; set; }
public Menu Menu { get; set; }
}
Menus:
public class Menu
{
public int ID { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
public int Order { get; set; }
}
In the controller I have the linq statement:
var model = from i in _db.Items
join c in _db.Categories on i.Category.ID equals c.ID
join m in _db.Menus on c.Menu.ID equals m.ID
where m.Name == menuName
select i;
I'm pretty sure that won't work with the select i but I'm out of ideas and that is it's curent state.
In the view I have:
#model IEnumerable<MyStore.Models.Item>
And I have tried everything I can think of to get the grouping to work and nothing has. I even tried just setting a variable to the currentCategoryID and checking if it changed then writing the category name but it errors out when it hits #item.Category.Name saying Object reference not set to an instance of an object.
I'm totally lost. :(
UPDATE
OK, We got that part working, now is there a way to make this loop more elegant? I don't like the temp var to check if it's a new category.
int currentCategory = 0;
foreach (var item in Model)
{
if (!item.Category.ID.Equals(currentCategory))
{
<h3>#item.Category.Name</h3>
currentCategory = item.Category.ID;
}
<div>
#Html.DisplayFor(modelItem => item.Name)
</div>
<div>
</div>
}
In general your query looks good and should produce the expected results - provided you do not have null properties for Menu or Category.
Could it be that some of your categories contain null as a Menu property? In that case your join will throw a NullReferenceException since you directly try to access c.Menu.ID - a workaround would be this:
var model = from i in Items
join c in Categories on i.Category.ID equals c.ID
where c.Menu !=null
join m in Menus on c.Menu.ID equals m.ID
where m.Name == menuName
select i;
Same problem with the Category property of Item - if it can be null you have to exclude those items since you use the category.ID property:
var model = from i in Items
where i.Category != null
join c in Categories on i.Category.ID equals c.ID
where c.Menu !=null
join m in Menus on c.Menu.ID equals m.ID
where m.Name == menuName
select i;
Edit in response to comment:
Since no properties are null we can drop that part from the query again - but you also need to specify which related entities you want to materialize when you retrieve entities from the context. In your case you need the related Category property as well, which you can specify using Include():
var model = from i in Items.Include("Category")
join c in Categories on i.Category.ID equals c.ID
where c.Menu !=null
join m in Menus on c.Menu.ID equals m.ID
where m.Name == menuName
select i;

Categories

Resources