I have a table named "Employees". The employeeId from this table MAY be in another table (a many-to-many join tbale) named "Tasks". The other field in the "Tasks" table is a taskId linked to the "TaskDetails" table. This table includes details such as budgetHours.
Using EF4, how do I write the WHERE statement such that the return is employees assigned to tasks where the budgetHours is > 120 hours?
THe WHERE statement in the following limits rows in the Employees table but now I need to add the conditions on the TaskDetails table.
var assocOrg = Employees.Where(x => x.EmployeeTypeID == 2 && x.DepartmentName == "ABC").Select (x => x.EmployeeID);
Thanks!
If Employees has a navigation property named Tasks, try this:
var assocOrg = Employees.Where(x => x.EmployeeTypeID == 2 &&
x.DepartmentName == "ABC" &&
x.Tasks.Any(t => t.BudgetHours > 120))
.Select (x => x.EmployeeID);
If you table sturcture is as below,
TableName Employee Task TaskDetails
ReferenceKeys EmpID EmpdID/TaskID TaskID/BudgetHours
then use,
Employee.Where(x => x.Task.EmpID == x.EmpID && x.Task.TaskDetails.TaskID == x.Task.TaskID && x.Task.TaskDetails.BudgetHours > 120).select(x => x.EmpID)
Assuming you have a Tasks navigation property on the Employee entity, this should be straightforward:
var assocOrg = Employees.Where(x => x.Tasks.Any(t => t.BudgetHours > 120) && x.DepartmentName == "ABC").Select (x => x.EmployeeID);
Of course, this requires the Tasks property to be resolved at this point, either explicitly, via lazy-loading, or with .Include().
(kudos to #adrift for getting Tasks.Any() right... oops.)
Related
Suppose I have a list of employees and each employee has several projects. I can get a given employee using:
var employee = employees.SingleOrDefault(x => x.Id == "id");
But how can I filter also project for the employee?
For example:
var employee = list
.SingleOrDefault(x => x.Key == employeeKey &&
x.Projects.SingleOrDefault(p => p.Key == projectKey));
If you want to filter down the Projects after getting the Employee you can use a .Select().
var result = employees.Where(e => e.Id == id).Select(e => new Employee
{
Id = e.Id,
Projects = e.Projects.SingleOrDefault(p => p.Key == projectKey)
}).SingleOrDefault();
So you can get the data you need in one step, but you have to assign the properties by yourself.
Another way is to first get your Employee and then filter down the projects, like BoredomOverload suggested:
var employee = employees.SingleOrDefault(x => x.Id== "id");
employee.Projects = employee.Projects.SingleOrDefault(p => p.Key == projectKey);
Either way you get the employee and the Projects of that Employee filtered.
var employee = employees.SingleOrDefault(
x => x.Id.Equals("id") && x.project.Equals("project")
);
Use Any() LINQ method like
var employee = employees.SingleOrDefault(x => x.Id== "id" && x.Projects.Any(p => p.Id == "Id"));
Moreover, You are filtering based on employee ID x.Id== "id" and mostly that employee ID would a primary key (Unique in nature) and in such case filtering just by Id would be much enough I believe
SingleOrDefault returns the object if found or null. So, in your case, it returns all employees because you are not testing anything. You just said if the project is there then return it.
Use Any instead which will return a boolean value if exist or not:
var employee = list.SingleOrDefault(x => x.Key == customerKey && x.Projects.Any(p => p.Key == projectKey));
If you need to filter if he has only one project with the specific key:
var employee = list.SingleOrDefault(x => x.Key == customerKey && x.Projects.Count(p => p.Key == projectKey) == 1);
You can also achieve it with SingleOrDefault but test the value with null:
var employee = list.SingleOrDefault(x => x.Key == customerKey && x.Projects.SingleOrDefault(p => p.Key == projectKey) != null);
If you want the return type to be more specific then use the select.
If it didn't work, try to add "include" to the list:
list.Include("Projects").... the rest of the query
I have the following Entity Framework function that it joining a table to a list. Each item in serviceSuburbList contains two ints, ServiceId and SuburbId.
public List<SearchResults> GetSearchResultsList(List<ServiceSuburbPair> serviceSuburbList)
{
var srtList = new List<SearchResults>();
srtList = DataContext.Set<SearchResults>()
.AsEnumerable()
.Where(x => serviceSuburbList.Any(m => m.ServiceId == x.ServiceId &&
m.SuburbId == x.SuburbId))
.ToList();
return srtList;
}
Obviously that AsEnumerable is killing my performance. I'm unsure of another way to do this. Basically, I have my SearchResults table and I want to find records that match serviceSuburbList.
If serviceSuburbList's length is not big, you can make several Unions:
var table = DataContext.Set<SearchResults>();
IQuerable<SearchResults> query = null;
foreach(var y in serviceSuburbList)
{
var temp = table.Where(x => x.ServiceId == y.ServiceId && x.SuburbId == y.SuburbId);
query = query == null ? temp : query.Union(temp);
}
var srtList = query.ToList();
Another solution - to use Z.EntityFramework.Plus.EF6 library:
var srtList = serviceSuburbList.Select(y =>
ctx.Customer.DeferredFirstOrDefault(
x => x.ServiceId == y.ServiceId && x.SuburbId == y.SuburbId
).FutureValue()
).ToList().Select(x => x.Value).Where(x => x != null).ToList();
//all queries together as a batch will be sent to database
//when first time .Value property will be requested
I am trying to query a collection and child collection using EF 7. Here's the code:
var customerID = 86795;
var query = await _context.Contacts
.Where(g => g.CustomerID == customerID )
.Include(g => g.Address.Where(p => p.AddressTypeID == 1))
.ThenInclude(p=> p.City)
.ToListAsync();
> Error CS1061 'IEnumerable<Address>' does not contain a definition for
> 'City' and no extension method 'City' accepting a first argument of
> type 'IEnumerable<Address>' could be found (are you missing a using
> directive or an assembly reference?) Contacts.DNX 4.5.1, Contacts.DNX
> Core 5.0
It works fine when I just use
var customerID = 86795;
var query = await _context.Contacts
.Where(g => g.CustomerID == customerID )
.Include(g => g.Address)
.ThenInclude(p=> p.City)
.ToListAsync();
But this will load all the addresses for the customer where I only want the recent address for which the AddressTypeID is 1.
Any idea how to do this?
You can try anonymous projection, that will fetch translate your query into SQL.
var customerID = 86795;
var query = await _context.Contacts
.Where(g => g.CustomerID == customerID)
.Select(cntct=> new
{
contact = cntct,
address = cntct.Address.Where(p => p.AddressTypeID == 1),
city = cntct.Address.Where(p => p.AddressTypeID == 1)
.Select(h=>h.City),
}.ToList();
You can't filter in Include. In any version of entity framework.
If you need to load a subset of the collection then you need to Join instead of using navigation property and filter whenever you need using Where clause
Like this (simplified, extra steps for readability):
var filteredAddresses = Addresses.Where(x=>x.AddressTypeId==1);
var customersWithAddress = Customers.Join(filteredAddresses, x=>x.Id,x=>x.CustomerId,(c,a)=> new {
Customer=c,
Address=a
});
Or if you need a single customer, assuming you have Customer navigation property in Address:
var addressWithCustomer = Addresses
.Where(x=>x.AddressTypeId==1 && x.CustomerId == customerId)
.Include(x=>x.Customer)
.Include(x=>x.City)
.Single();
A lot of times, it is better to approach queries which involve conditional nested entities, to start with the nested entity, apply the conditions to this nested fellow and then project out the parent entity, since it is always easier to reach to the parent entities from the nested enumerable ones. (many to one)
in our case, we can apply the filter out on the Address entity and then group it on the Contact entity.
var customerID = 86795;
var query = await _context.Addresses
.Where(a => a.Contact.CustomerID == customerID
&& a.Contact.RegistrationDate.Year == 2016
&& a.AddressTypeID == 1)
.Include(a => a.Contact)
.Include(a => a.City)
.GroupBy(a => a.Contact)
.Take(20) // by the way, you should apply some orderby for a predicatble Take
.ToListAsync();
and if you absolutely want a list of Contacts as the output of the above query, you can do this.
var contacts = query.Select(g =>
{
g.Key.Addresses = g.ToList();
return g.Key;
}).ToList();
// now you can work off the Contacts list, which has only specific addresses
This will basically give you a grouped list of all Contacts with CustomerID, and with those address types and registration years only. The important thing here is to iterate through the group to get the addresses, and not use the grouping.Key.Addresses navigation. (grouping.Key will be the Contact entity)
Also, I don't know if CustomerID is a primary key on the Contact entity, but if it is, it looks like you would just need a list of matching addresses for one Contact. In that case, the query would be:
var query = await _context.Addresses
.Where(a => a.Contact.CustomerID == customerID && a.AddressTypeID == 1)
.Include(a => a.Contact)
.Include(a => a.City)
.ToListAsync();
Include The Collection for Eager Load then use Any instead of Where ... to Select specific items in the child of the wanted entity.
var customerID = 86795;
var query = await _context.Contacts
.Where(g => g.CustomerID == customerID )
.Include(g => g.Address.Any(p => p.AddressTypeID == 1))
.ThenInclude(p=> p.City)
.ToListAsync();
I have a table with a list of customers.
One customer has 0, 1 or more contract(s).
I must retrieve all the enabled customer, set them in a DTO and add the current contract to this DTO (If there is one)
For the moment, it is very slow (more than 10min).
CODE
List<CustomerOverviewDto> result = new List<CustomerOverviewDto>();
customers= context.Customers.Where(c => c.IsEnabled).ToList();
foreach (Customer customer in customers)
{
CustomerOverviewDto customerDto = GetCustomer(customer);
Framework.Contract contract =
customer.Contracts.Where(c => c.ContractEndDate >= DateTime.Today && c.ContractStartDate <= DateTime.Today)
.FirstOrDefault();
if (contract != null)
{
SetContract(customerDto, contract);
}
result.add(customerDto);
}
Use projection to only return the columns you work with by using "Select". If you have 36 columns this will give you better results.
customers= context.Customers.Where(c => c.IsEnabled).Select(cust => new Customer
{
Id = cust .Id
}).ToList();
https://www.talksharp.com/entity-framework-projection-queries
After that check in the queryplan if you have table scans or index scans. Try to avoid them by setting apropriate indexes.
I think the the problem is the query that retrieves the contract inside the loop. It would be better to retrieve all the data with one query like this:
var date = DateTime.Today;
var query =
from customer in context.Customers
where customer => customer.IsEnabled
select new
{
customer,
contract = customer.Contracts.FirstOrDefault(c => c.ContractEndDate >= date && c.ContractStartDate <= date)
};
var result = new List<CustomerOverviewDto>();
foreach (var entry in query)
{
CustomerOverviewDto customerDto = GetCustomer(entry.customer);
if (entry.contract != null)
SetContract(customerDto, entry.contract);
result.add(customerDto);
}
Ok so first of all, when you use .ToList(), you are executing the query right there, and pulling back every row that IsEnabled into the memory for working on. You want to do more on the database side.
result = context.Customers.Where(c => c.IsEnabled); //be lazy
Secondly, the query is only going to perform well and be optimised by the execution engine properly if it has indexes to work with.
Add some indexes on the fields that you are performing comparisons on.
Think about this line of code for example
customer.Contracts.Where(c => c.ContractEndDate >= DateTime.Today &&
c.ContractStartDate <= DateTime.Today).FirstOrDefault();
Is you don't have a foreign key from customers to contracts, and you have no indexes on ContractStartDate and ContractEndDate it is going to perform extremely poorly and is going to be run once for every customer that 'IsEnabled'
It seems you only want to do some thing when returns a value. So you can add this in you initial query, and include the contracts:
customers= context.Customers
.Include(c => c.Contracts)
.Where(c => c.IsEnabled
&& c.Contracts.Any(con => con.ContractEndDate >= DateTime.Today && con .ContractStartDate <= DateTime.Today))
.ToList();
foreach (Customer customer in customers)
{
CustomerOverviewDto customerDto = GetCustomer(customer);
Framework.Contract contract =
customer.Contracts.Where(c => c.ContractEndDate >= DateTime.Today && c.ContractStartDate <= DateTime.Today)
.First();
SetContract(customerDto, contract);
}
As I have no idea of what your domain model structure looks like or why you are not using navigation properties to map the CURRENT contract to the customer, you could do something like this.
You could do just 2 roundtrips to the database by materializing all the customers and contracts and then mapping them in memory to your DTO objects. Assuming you have CustomerId as FK and Customer.Id as PK.
List<CustomerOverviewDto> result = new List<CustomerOverviewDto>();
customers = context.Customers.Where(c => c.IsEnabled).ToList();
contracts = context.Contracts.Where(c => c.ContractEndDate >= DateTime.Today && c.ContractStartDate <= DateTime.Today).ToList();
foreach (Customer customer in customers)
{
var customerDto = GetCustomer(customer);
var contract = contracts.Where(c => c.CustomerId == customer.Id).FirstOrDefault();
if (contract != null)
{
SetContract(customerDto, contract);
}
result.add(customerDto);
}
I finally solved the problem by using 1 query and projection
context.Customers.Where(c => c.IsEnabled).Select(c => new CustomerOverviewDto{...}).ToList();
I directly retrieve the contract when creating the CustomerOverviewDto
I am using nHibernate and I need to create a query that does this:
Course Table
CourseId
CourseName
Task Table // course can have many tasks
TaskName
TaskId
CousreId
Now I need to do a contains:
session
.Query<Course>()
.Where(x =>
x.Tasks.Contains(/* wants a task object. I want to do it on property level. */) &&
x.CourseId == 1)
How can I change my query to do a Contains on TaskName?
Project your Tasks to a TaskName then use contains on that.
var query = session
.Query<Course>()
.Where(x => x.Tasks
.Select(t => t.TaskName)
.Contains(myTaskName)
&& x.CourseId == 1);
If I correctly understood you can use Any method
session.Query<Course>().Where(x => x.Tasks.Any(t => t.Name == "task name")
&& x.CourseId == 1);
Have you tried this?
var results = session.Query<Course>()
.Where(crs => crs.Tasks.Count(tsk => tsk.TaskName == theName) > 0);
This should count the number of tasks with the correct name (specified in theName in my example), and return all courses that have a count value greater than zero, i.e. all courses that contain a task with the specific name.
You either have to implement your own IComparer or IEqualityComparer (as I recall, I may be off) and base it on a specific property of the object. Or use Count() or Find() instead. Here's some pseudo code:
session.Query<Course>().Where(x => x.Tasks.Count(t => t.TaskProperty == "something") > 0 && x.CourseId == 1)
I would try something like this:
var results = session
.Query<Course>()
.Where(crs => crs.Tasks.Any(tsk => tsk.TaskName == theName) && crs.CourseId == 1);