LINQ join based on checkbox - c#

I'm using Entity Framework 6.0 in a C# WPF app. I have this query to search the Employee entity:
var query = Context.Employees.AsQueryable();
switch (selectedColumnValue)
{
case "lastName":
query = query.Where(q => q.lastName == SearchValue);
break;
case "employeeID":
query = query.Where(q => q.personnelID == SearchValue);
break;
case "securityID":
query = query.Where(q => q.securityID == SearchValue);
break;
default:
return;
}
The parent entity Employee may have some child rows in the EmployeeStatus entity to indicate if the employee is inactive, on leave of absence, or archived. Employee entity's primary key is employeeID and is a foreign key on EmployeeStatus.
Here is the Employee entity's columns:
public int employeeID { get; set; }
public string personnelID { get; set; }
public string securityID { get; set; }
public string firstName { get; set; }
public string middleName { get; set; }
public string lastName { get; set; }
public string suffix { get; set; }
public string job { get; set; }
public string organizationalUnit { get; set; }
public string costCenter { get; set; }
public string notes { get; set; }
public System.DateTime createdDate { get; set; }
And here's the EmployeeStatus entity's columns
public int employeeStatusID { get; set; }
public int employeeID { get; set; }
public int validEmployeeStatusID { get; set; }
public Nullable<System.DateTime> exitDate { get; set; }
public System.DateTime createdDate { get; set; }
If the user clicks the checkbox "Include archived employees?" during the search, we need to do the join to the EmployeeStatus and add the condition that the EmployeeStatus column validEmployeeStatusID equals 5 (Archived). How do you do this in LINQ?
Update: I need to add the Linq join after the above switch statement in an IF statement, such as this:
if (IncludeArchived)
{
// Add to Linq query here using method syntax
}
And so the Linq statement needs to use the method syntax.

What about this query?
query = (from empl in query
join status in Context.Status on empl.employeeID equals status.employeeID
where status.employeeStatusID == 5
select empl).Distinct();
Or you can perform chain:
query = query.Join(Context.Status, x => x.employeeID, x => x.employeeID, (a, b) => new { a, b })
.Where(x => x.b.employeeStatusID == 5).Select(x => x.a).Distinct();

Actually you do not need to add it after the switch statement.
In your current implementation you are including ALL employees (even the archived ones). So you actually need to make it so that you exclude the archived ones if the checkbox is not checked.
Note: I am assuming you have the navigation properties for your entities.
var query = Context.Employees.AsQueryable();
if (!IncludeArchived)
{
query = query.Where(e => e.Status == null || e.Status.employeeStatusId != 5);
}
switch (selectedColumnValue)
{
// ...
}

Related

C# Model Query returns no result because Joins may not have records

I have a query and I cannot remember. I guess my real question is, Do I need the extra Joins in this query and if yes them how do I handle the fact that Users may not have Posts, Tasks, or Notifications. I know that you can use coalesce but in this instance it doesn't appear to like it. So How do I get a successful Query if no records are found in the tables of the joins.
In the controller I do a redirect
if (modelInstance == null)
{
return RedirectToAction("Index", "Dashboard");
}
If I remove the joins it will populate the View Page. My guess is because there are no records in these joins - Yet..
Here is my Query:
public class ProfileViewModel
{
public static ProfileViewModel GetUserProfile(string id, GeneralEntities db)
{
var qUser = from usr in db.Users
join post in db.Posts on usr.AspNetUsersId equals post.CreatedBy
join task in db.Tasks on usr.AspNetUsersId equals task.UsersId
join notif in db.Notifications on usr.AspNetUsersId equals notif.UsersId
where (usr.AspNetUsersId == id)
select new ProfileViewModel
{
AspNetUsersId = usr.AspNetUsersId,
FirstName = usr.FirstName,
LastName = usr.LastName,
ProfileImage = usr.ProfileImage,
Title = usr.Title,
Education = usr.Education,
Skills = usr.Skills,
About = usr.About,
IsFemale = usr.IsFemale,
IsMale = usr.IsMale,
IsStaff = usr.IsStaff
};
var result = qUser.FirstOrDefault();
if (result != null)
{
result.PostResults = db.Posts.Where(x => x.CreatedBy == result.AspNetUsersId);
result.TaskResults = db.Tasks.Where(x => x.UsersId == result.AspNetUsersId);
result.NotificationResults = db.Notifications.Where(x => x.UsersId == result.AspNetUsersId);
};
return result;
}
public string UsersId { get; set; }
public string AspNetUsersId { get; set; }
[Display(Name = "F Name")]
public string FirstName { get; set; }
[Display(Name = "L Name")]
public string LastName { get; set; }
[Display(Name = "Profile Image")]
public string ProfileImage { get; set; }
[Display(Name = "Job Title")]
public string Title { get; set; }
[Display(Name = "Education")]
public string Education { get; set; }
[Display(Name = "Skills")]
public string Skills { get; set; }
[Display(Name = "About")]
public string About { get; set; }
public bool? IsStaff { get; set; }
public bool? IsMale { get; set; }
public bool? IsFemale { get; set; }
public virtual IEnumerable<Post> PostResults { get; set; }
public virtual IEnumerable<Tasks> TaskResults { get; set; }
public virtual IEnumerable<Notification> NotificationResults { get; set; }
}
I also tried the .Where(x => x.UserId != null) but that did not work either.
thanks for your help!
While you can use LEFT JOIN to handle the case where the joined table can have zero-to-many results, the result isn't going to be what you appear to need based on the structure of your ProfileViewModel class.
If you always want to return the matching user then a simple query on db.Users to get the record is sufficient. You'll end up with 4 queries - one for the user and one each for the related records - but they'll all be quite straight-forward:
var usr = db.Users.FirstOrDefault(u => u.AspNetUsersId == id);
if (usr == null)
return null;
return new ProfileViewModel
{
AspNetUsersId = usr.AspNetUsersId,
// ...the rest of the usr fields...
PostResults = db.Posts.Where(x => x.CreatedBy == id),
// ...and so on.
};
Note that since IQueryable<> implements IEnumerable<> then each time you enumerate over PostResults, TaskResults or NotificationResults you'll be running a query against the database.
Alternatively you could include some extras in your usr query to get metrics on the linked tables - number of items for instance, or just a flag saying whether or not they have any linked rows:
var usr =
(
from usr in db.Users
where usr.AspNetUsersId == id
select new
{
usr,
postCount = db.Posts.Count(p => p.CreatedBy == usr.AspNetUsersId),
hasTasks = db.Tasks.Any(t => t.UsersId == usr.AspNetUsersId),
hasNotes = db.Notifications.Any(n => n.UsersId == usr.AspNetUsersId),
}
).FirstOrDefault();
That could be useful for reducing the query load later. If there are no posts for instance then assigning an empty array to the PostResults field is a good idea. Maybe you want to load all of the posts if there are only a handful rather than have them queried multiple times later.
A note since this is ASP.NET: You want to be very careful about those Results fields.
Deferred queries are useful, but when they outlive their data context they can cause problems. In a simple MVC view model context you're probably fine, just as long as you understand the risks. For short-term 'fetch and display' this should be perfectly fine, but if the model is stored and reused anywhere then don't save an IQueryable<>. Materialize your queries with ToArray() or similar in those instances, or explicitly recreate the query against the current data context when you need it.
Here is the working code with the Left joins that #YongShun led me to. I am still wondering if I really need these joins in my current query, Nevertheless if someone can find this useful here it is..
public class ProfileViewModel
{
public static ProfileViewModel GetUserProfile(string id, GeneralEntities db)
{
var qUser = from usr in db.Users
join post in db.Posts on usr.AspNetUsersId equals post.CreatedBy into a from post in a.DefaultIfEmpty()
join task in db.Tasks on usr.AspNetUsersId equals task.UsersId into b from task in b.DefaultIfEmpty()
join notif in db.Notifications on usr.AspNetUsersId equals notif.UsersId into c from notif in c.DefaultIfEmpty()
where (usr.AspNetUsersId == id)
select new ProfileViewModel
{
AspNetUsersId = usr.AspNetUsersId,
FirstName = usr.FirstName,
LastName = usr.LastName,
ProfileImage = usr.ProfileImage,
Title = usr.Title,
Education = usr.Education,
Skills = usr.Skills,
About = usr.About,
IsFemale = usr.IsFemale,
IsMale = usr.IsMale,
IsStaff = usr.IsStaff
};
var result = qUser.FirstOrDefault();
if (result != null)
{
result.PostResults = db.Posts.Where(x => x.CreatedBy == result.AspNetUsersId);
result.TaskResults = db.Tasks.Where(x => x.UsersId == result.AspNetUsersId);
result.NotificationResults = db.Notifications.Where(x => x.UsersId == result.AspNetUsersId);
};
return result;
}
public string UsersId { get; set; }
public string AspNetUsersId { get; set; }
[Display(Name = "F Name")]
public string FirstName { get; set; }
[Display(Name = "L Name")]
public string LastName { get; set; }
[Display(Name = "Profile Image")]
public string ProfileImage { get; set; }
[Display(Name = "Job Title")]
public string Title { get; set; }
[Display(Name = "Education")]
public string Education { get; set; }
[Display(Name = "Skills")]
public string Skills { get; set; }
[Display(Name = "About")]
public string About { get; set; }
public bool? IsStaff { get; set; }
public bool? IsMale { get; set; }
public bool? IsFemale { get; set; }
public virtual IEnumerable<Post> PostResults { get; set; }
public virtual IEnumerable<Tasks> TaskResults { get; set; }
public virtual IEnumerable<Notification> NotificationResults { get; set; }
}

Convert the result of LINQ query to list of Custom Model

Good evening everyone.
I have a LINQ to SQL query result, and I want to convert this result to a list of Custom Model class
down here the Class Model:
public class Beneficiary
{
public int BeneficiaryId { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public DateTime? CreatedAt { get; set; }
[Column(TypeName = "date")]
public DateTime? BithDate { get; set; }
public int? CommunityId { get; set; }
public virtual Community Community { get; set; }
[Required]
public string Gender { get; set; }
public string IdNumber { get; set; }
[Required]
[StringLength(50)]
public string AppName { get; set; }
[StringLength(50)]
public string BeneficiaryNumber { get; set; }
public int CompanyId { get; set; }
public virtual Company Company { get; set; }
public virtual ICollection<BeneficiaryProject> BeneficiaryProjects { get; set; }
public virtual ICollection<Card> Cards { get; set; }
public virtual ICollection<Invoice> Invoices { get; set; }
}
and here is the query that I used :
var list = (from B in db.Beneficiaries
join ben in db.BeneficiaryProjects on B.BeneficiaryId equals ben.BeneficiaryId
where (ben.CardNeeded == true && ben.ProjectId == temp.ProjectId)
select new Beneficiary()
{
BeneficiaryId = B.BeneficiaryId,
FirstName = B.FirstName,
LastName = B.LastName,
IdNumber = B.IdNumber,
Gender = B.Gender
});
the above query is successfully executed, but when I convert the var list to a list of Beneficiary as following:
List<Beneficiary> list1 = list.ToList<Beneficiary>();
but I got the below exception :
An exception of type 'System.NotSupportedException' occurred in EntityFramework.SqlServer.dll but was not handled in user code
Additional information: The entity or complex type 'E_Voucher.Models.Beneficiary' cannot be constructed in a LINQ to Entities query.
Make conversion to custom class after retrieving the results from EF:
var list = (
from B in db.Beneficiaries
join ben in db.BeneficiaryProjects on B.BeneficiaryId equals ben.BeneficiaryId
where (ben.CardNeeded == true && ben.ProjectId == temp.ProjectId)
select new {
B.BeneficiaryId,
B.FirstName,
B.LastName,
B.IdNumber,
B.Gender
}
).AsEnumerable()
.Select(B => new Beneficiary() {
BeneficiaryId = B.BeneficiaryId,
FirstName = B.FirstName,
LastName = B.LastName,
IdNumber = B.IdNumber,
Gender = B.Gender
}).ToList();
Note: The reason you get the error on running ToList is that EF queries defer execution. Your query may be incorrect, but the only way to find out is to try getting its results.
The reason why you are getting this error
(in this line:)
List<Beneficiary> list1 = list.ToList<Beneficiary>();
is that you are trying to convert a Linq Query variable to a List.
All you really need to do is the following:
List<Beneficiary> list1 = list.ToList();
the query you have for temp.ProjectID:
var projects = db.Projects.Where(p => p.ContractId == id); foreach (var temp in projects)
should be something like:
int ProjectId = (from P in db.Projects where P.ContractId == id select P.ProjectId).FirstOrDefault();
and then take that value and put into your next query:
... where (ben.CardNeeded == true && ben.ProjectId == ProjectId)
I believe you can't project your object of type Beneficiary from within LINQ to Entities to a mapped entity object.
Try this:
db.Beneficiaries.Join(db.BeneficiaryProjects, B => B.BeneficiaryId, ben => ben.BeneficiaryId, (B, ben) => new { B, ben })
.Where(x => x.ben.CardNeeded == true && x.ben.ProjectId == temp.ProjectId)
.Select(x => x.B)
.ToList();
It's a lamda expression that should do the same thing as your LINQ query but will select your Beneficiary model object and map it. The ToList() can be replaced with an AsQueryable() or AsEnumerable() based on your need.

EF: get data with many-to-one relation

I have Entity model generated from DB:
public partial class Product
{
public Product()
{
this.ProductMeta = new HashSet<ProductMeta>();
}
public int ProductID { get; set; }
public int DomainId{ get; set; }
public bool IsDeleted { get; set; }
public virtual ICollection<ProductMeta> ProductMeta { get; set; }
}
and
public partial class ProductMeta
{
public int ProductID { get; set; }
public int LanguageID { get; set; }
public string ProductName { get; set; }
public bool IsDeleted { get; set; }
public virtual Product Product { get; set; }
}
and want I want to do is get the data from the both tables. What is the best way to join Product with ProductMeta, because ProductMeta have many variants, depends on languageId. Is my where and select are efficent? When I looked into generated SQL, there are many nested SELECT FROM. This is normal?
var result =
from p in _dbContext.Set<Product >()
where (p.DomainID == domainId
&& p.IsDeleted == false
&& p.ProductMeta.Select(pm => pm.LanguageID == languageId && pm.IsDeleted == false).FirstOrDefault()
select(new
{
ProductID = p.ProductID,
ProductName = p.ProductMeta.FirstOrDefault().ProductName
});
Thanks!
You can try selecting from ProductMeta
var result =
(from pm in _dbContext.Set<ProductMeta>()
where pm.LanguageID == languageId
&& pm.IsDeleted == false
&& pm.Product.DomainID == domainId
&& pm.Product.IsDeleted == false
select pm
);
This will give you an object of type List<ProductMeta>.. these objects will have a property call Product that will have all of the Product properties. You may need to use Include if lazy loading is turned off. That would look like
from pm in _dbContext.Set<ProductMeta>().Include("Product")
Append FirstOrDefault() after the linq query to get just a single ProductMeta object
select pm).FirstOrDefault();

Use multiple conditions in join LINQ. i,e AND

How to use multiple condition in LINQ joins, i.e. in my scenario I need to get all the users from table User where group ID = 4 from table UserInGroup, where UserInGroup is intermediate table between User and Group table as in SQL-T we use join as
select *
from user
where user.userID = userIngroup.userID AND userIngroup.groupID == 4
....
In another approach I am using lambda expression along with LINQ, how I can apply where groupID = 4 in following one??
public IEnumerable<User> GetUsersByGroupID(int _groupID)
{
List<User> _listedUsersByGroupID = new List<User>();
using(var _uow = new UserManagement_UnitOfWork())
{
_listedUsersByGroupID = (from _users in _uow.User_Repository.GetAll()
.Include(s=>s.UserInGroup.Select(r=>r.Group))
select _users).ToList();
return _listedUsersByGroupID;
}
}
User Model
[Table("User")]
public class User
{
public User() { }
[Key]
public int UserID { get; set; }
[StringLength(250)]
[Required]
public string FirstName { get; set; }
[StringLength(250)]
[Required]
public string LastName { get; set; }
[Required]
public int Age { get; set; }
[StringLength(250)]
[Required]
public string EmailAddress { get; set; }
public ICollection<UserInGroup> UserInGroup { get; set; }
}
UserInGroup Model
[Table("UserInGroup")]
public class UserInGroup
{
public UserInGroup() { }
[Key]
public int UserGroupID { get; set; }
[Required]
public int UserID { get; set; }
[Required]
public int GroupID { get; set; }
public User User { get; set; }
public Group Group { get; set; }
}
Group Model
public class Group
{
public Group() { }
[Key]
public int GroupID { get; set; }
[StringLength(250)]
[Required]
public string GroupName { get; set; }
public ICollection<UserInGroup> UserInGroup { get; set; }
}
You only need to add a condition to filter the users that belong to the group 4. Try this:
_listedUsersByGroupID = (from _user in _uow.User_Repository.GetAll()
.Include(s=>s.UserInGroup.Select(r=>r.Group))
where user.UserInGroup.Any(ug=>ug.groupID==4)
select _user).ToList();
Lambda query would look something like:
ctx.User.Where(user=>
ctx.UserInGroup.Any(userIngroup=>
user.userID == userIngroup.userID && userIngroup.groupID == 4
)
)
That however is just query, if you want to get results add .AsList() or .AsEnumerable() to end.
However you can write silly and inefficient code if you do not fully understand what you are doing. I would reccomend you try this instead:
var publications = ctx.Database.SqlQuery<UserResults>(String.Format(#"
select UserID, FirstName,LastName,Age,EmailAddress,UserInGroup
from user
where user.userID = userIngroup.userID AND userIngroup.groupID == {0}
order by UserID
", Config.Group));
Where Config.Group is 4; UserResults can be User table as well, if you do not want other fields. You need to execute or enumerate over the sql query to use the data and like before you can use .AsList() or .AsEnumerable() for that.
Variable ctx is database context. For example:
using (var ctx = new toxicEntities())
{
}

Linq Find Item by Primary Key and display Name(that lives in a different table)

I am displaying a record from my database. The record pulls data from other tables and uses a Int in the main table to represent the value so Item table has a Division equal to 1 and the Division table 1 = ALL . Now that i am displaying the records i am trying to turn the 1 into all. All the ID fields show the int. Which is what my code is telling it to do. But i am trying to display the name and when i do that i get a lot of red. It cannot find the name. CatagoryID should be CategoryName.
Hope that makes sense.
if (!IsPostBack)
{
string v = Request.QueryString["ContactID"];
int itemid;
int.TryParse(v, out itemid);
var customerInfo = GetCustomerInfo(itemid);
CONTACTID.Text = customerInfo[0].ContactID.ToString();
ContactTitle.Text = customerInfo[0].ContactTitlesID.ToString();
ContactNameB.Text = customerInfo[0].ContactName;
DropDownAddCategory.Text = customerInfo[0].CategoryID.ToString();
DDLAddDivision.Text = customerInfo[0].DivisionID.ToString();
ContactPhoneBox.Text = customerInfo[0].ContactOPhone;
ContactCellBox.Text = customerInfo[0].ContactCell;
ContactEmailBox.Text = customerInfo[0].ContactEmail;
CextB.Text = customerInfo[0].Ext;
}
private List<Solutions.Models.Contact> GetCustomerInfo(int itemid)
{
using (ItemContext context = new ItemContext())
{
return (from c in context.Contacts
where c.ContactID == itemid
select c).ToList();
}
}
This is the model
public class Contact
{
[ScaffoldColumn(false)]
public int ContactID { get; set; }
public System.DateTime ContactCreated { get; set; }
public string ContactName { get; set; }
public int? ContactTitlesID { get; set; }
public string ContactOPhone { get; set; }
public bool cApproved { get; set; }
public string User { get; set; }
public string ContactCell { get; set; }
public string ContactEmail { get; set; }
public int? DivisionID { get; set; }
public int? CategoryID { get; set; }
[StringLength(5)]
public string CExt { get; set; }
public virtual Division Division { get; set; }
public virtual Category Category { get; set; }
public virtual ContactTitle ContactTitle { get; set; }
public string Ext { get; set; }
}
With Entity Framework you can include related entities in query results:
return (from c in context.Contacts.Include("Catagory")
where c.ContactID == itemid
select c).ToList();
This will return contacts with Catagory objects: customerInfo.Catagory.CategoryName
BTW instead of returning list of contacts and selecting first one by index (thus possibly having index out of range exception), modify your method to return first contact (or default, if not found):
private Solutions.Models.Contact GetCustomerInfo(int itemid)
{
return (from c in context.Contacts.Include("Catagory")
where c.ContactID == itemid
select c).FirstOrDefault();
}
And use it this way:
var customerInfo = GetCustomerInfo(itemid);
if (customerInfo != null)
{
CONTACTID.Text = customerInfo.ContactID.ToString();
// etc
}
Are you using LINQ to SQL or Entity Framework? Check your model again and make sure the relationship between the two tables are setup correctly. The relationship may be missing from the model, and causing this problem.

Categories