Linq expression for filtered collection of collections? - linq

I'm hoping this will be a rather simple question for anyone who's good at Linq. I'm struggling to come up with the right Linq expression for the following. I'm able to hack something to get the results, but I'm sure there's a proper and simple Linq way to do it, I'm just not good enough at Linq yet...
I have a database accessed through Entity Framework. It has a number of Tasks. Each Task has a collection of TimeSegments. The TimeSegments have Date and Employee properties.
What I want is to be able to get the tasks for a certain employee and a certain month and the timesegments for each task for that same month and employee.
Again, the tasks do not in themselves have month nor date information, but they do by the TimeSegments associated with each task.
Very simplified it looks sort of like this:
public class Model //Simplified representation of the Entity Framework model
{
public List<Task> Tasks { get; set; }
}
public class Task
{
public int Id { get; set; }
public List<TimeSegment> TimeSegments { get; set; }
public Customer Customer { get; set; }
}
public class TimeSegment
{
public int Id { get; set; }
public string Date { get; set; }
public Employee Employee { get; set; }
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
}
So how do I do this as simply as possible with Linq? I.e. tasks and associated timesegments for a certain month and employee. I would also like to be able to get it by Customer BTW...

This is the simplest thing I could come up with:
var tasksWithSegments =
from segment in model.TimeSegments
where segment.Date.Month == month
where segment.Employee.Id == employeeId
group segment by segment.Task into result
select new
{
Task = result.Key,
TimeSegments = result.ToArray()
};
Please note that you might have to add some properties to your model, such as Model.TimeSegment and TimeSegment.Task.
The trick with LINQ queries often is to start at the right collection. In this case the ideal starting point is TimeSegments.
ps. I'm not sure whether Date.Month == month will actually work with EF, but I think it will (with EF 4.0 that is).
Update:
Could you show how to extend this
query and get the tasks for a
particular Customer as well?
I'm not sure what you mean, but you can for instance filter the previous queryable like this:
var tasksWithSegmentsForCustomers =
from taskWithSegments in tasksWithSegments
where taskWithSegments.Task.Customer.Id == customerId
select taskWithSegments;
Can I get the return type to be a list
of Tasks with a list of TimeSegments
if I have this in a method?
Again, not sure what you exactly want, but if you want two separate lists that have no relation, you can do this:
List<Task> tasks = (
from taskWithSegments in tasksWithSegments
select taskWithSegments.Task).ToList();
List<TimeSegments> segments = (
from taskWithSegments in tasksWithSegments
from segment in taskWithSegments.Segments
select segment).ToList();
Of course, if this is what you need, than it might be easier to rewrite the original query to something like this:
List<TimeSegment> segments = (
from segment in model.TimeSegments
where segment.Date.Month == month
where segment.Employee.Id == employeeId
select segment).ToList();
List<Task> allTasks =
segments.Select(s => s.Task).Distinct().ToList();
Once you got the hang of writing LINQ queries, there is no way you want to go back to writing SQL statements or old-fashion foreach statements.
Think LINQ!!!

What I want is to be able to get the
tasks for a certain employee and a
certain month and the timesegments for
each task for that same month and
employee.
This will select tasks from an instance of Model where the task has at least one time segment that in the requested month for the requested employee (untested):
Model model = new Model();
tasks = model.Tasks.Where(t => t.TimeSegments.Any(ts => ts.Employee.Id = requestedId && Convert.ToDate(ts.Date).Month == requestedMonth));

Related

Linq Group By - Unable to group on custom classes?

I have noticed that when I do a LINQ - group by, it only seems to be working if I dont group on any of my custom classes.
I have a Product class (shown below) and I would like to group on Product.Id ,Product.Variant (object) and on Product.Options (ICollection) (because my source list contains multiple times the same Product but with different Variants and / or Options)
Product:
public class Product
{
public int Id { get; set; }
public Variant Variant { get; set; }
public ICollection<Option> Options{ get; set; }
public string Name { get; set; }
public int Amount { get; set; }
}
The code below will do a grouping, but only on my Product.ID, when I try to also group on Variant / Options, I get no grouping (well, not the grouping I am intending to make) as it will return just as much items as my source list.
IEnumerable<productAndSum> productsAndSums = unmappedProducts
.GroupBy(prod => new { Id = prod.Id})
.Select(group => new productAndSum()
{
Key = group.Key,
Sum = group.Sum(x => x.Amount)
});
If I am on the right track and the issue is related to the Objects, then it might be usefull to add that also Option and Variant have multiple objects and collections themselves, or is this too deep?
Extra information: I first started to just group by my Product class (not Id, Variant & Options separately), but this was unsuccessful. so I started eliminating properties and this is how I found out this issue. I think that solving this issue will result in killing two birds with one stone.
Warm regards

How can I manually join cached Entity Framework objects?

I'm having a performance issue with lookups using the navigation properties of an EF model.
My model is something like this (conceptually):
public class Company
{
public int ID { get; set; }
public string CompanyName { get; set; }
public EntityCollection<Employee> Employees { get; set; }
}
public class Employee
{
public int CompanyID { get; set; }
public string EmployeeName { get; set; }
public EntityReference<Company> CompanyReference { get; set; }
}
Now let's say I want to get a list of all Companies that have (known) Employees.
Additionally, assume that I've already cached lists of the both the Companies and the Employees through previous calls:
var dbContext = new EmploymentContext();
var allCompanies = dbContext.Companies.ToList();
var allEmployees = dbContext.Employees.ToList();
bool activeCompanies =
allCompanies.Where(company => company.Employees.Any()).ToList();
This (in my environment) generates a new SQL statement for each .Any() call, following the Employees navigation property.
I already have all the records I need in my cached lists, but they're not 'connected' to each other on the client side.
I realize I can add .Include() calls to my initial cache-fill statement. I want to avoid doing this because in my actual environment I have a large number of relations and a large number of lists I'm populating up front. I'm caching largely to keep Linq from generating overly-complicated nested SQL statements that tend to bog down my database server.
I also realize I can modify my query so as to do an in-memory join:
bool activeCompanies = allCompanies.Where
(
company => allEmployees.Any(employee => employee.CompanyID == company.ID)
);
I'm trying to avoid doing such a rewrite, because the actual business logic gets rather involved. Using Linq statements has significantly improved the readability of this logic, and I'd prefer not to lose that if at all possible.
So my question is this: can I connect them together manually somehow, in the way that the Entity Framework would connect them?
I'd like to continue to use the .Any() operator, but I want it to examine only the objects I have in memory in my dbContext - without going back to the database repeatedly.

Linq selecting from muliple tables

I have the following model
public class SummaryModel
{
public int CompanyCount { get; set; }
public int GroupCount { get; set; }
public int ProjectCount { get; set; }
public int ResourcesCount { get; set; }
public int PeopleCount { get; set; }
}
I would like to use linq to query my database and return record counts from multiple tables and populate this model object.
This is how I am doing it:
using (var ctx = new WeWorkModel.weWorkEntities())
{
var summary = new SummaryModel()
{
CompanyCount = ctx.Companies.Count(),
PeopleCount = ctx.People.Count(),
GroupCount = ctx.Groups.Count(),
ProjectCount = ctx.Projects.Count(),
ResourcesCount = ctx.Resources.Count()
};
}
Is this the most efficient way to do this?
Yes, this is the most efficient way - equivalent to writing sql query as this does not fetch the objects but only does a count on the server. So something like this ( using profiler I tracked the query)
SELECT
[GroupBy1].[A1] AS [C1]
FROM ( SELECT
COUNT(1) AS [A1]
FROM [dbo].[Company] AS [Extent1]
) AS [GroupBy1]
Do you need to store this model in a database or change it's values after instantiating? If no, why not put this code block inside of a parameterless constructor and mark the fields readonly as to avoid using this model differently than intended. If you find later you need greater control over initialization of fields, simply add another constructor to deal with that specific case. To the main question, I see nothing particularly inefficient with your way of handling it. Although, with code there is nearly always terser ways or more efficient ways of handling just about any scenario.

Coalesce fields in a .net MVC 4 model without getting "Only initializers, entity members, and entity navigation properties are supported" from LINQ

The answer to this question gave rise to this other question: How to use LINQ expressions as static members of classes in queries when the class is related multiple times to a second class
I have an existing ASP.net MVC 4 site which I need to modify.
The core entity within this site are Items that are for sale, which are created by several different companies and divided into several categories. My task is to allow each company its own optional alias for the global categories. Getting the two categories set up in the database and model was no problem, making the application use the new optional alias when it exists and default to the global otherwise is where I'm struggling to find the optimal approach.
Adding a coalesce statement to every LINQ query will clearly work, but there are several dozen locations where this logic would need to exist and it would be preferable to keep this logic in one place for when the inevitable changes come.
The following code is my attempt to store the coalesce in the model, but this causes the "Only initializers, entity members, and entity navigation properties are supported." error to be thrown when the LINQ query is executed. I'm unsure how I could achieve something similar with a different method that is more LINQ friendly.
Model:
public class Item
{
[StringLength(10)]
[Key]
public String ItemId { get; set; }
public String CompanyId { get; set; }
public Int32 CategoryId { get; set; }
[ForeignKey("CategoryId")]
public virtual GlobalCategory GlobalCategory { get; set; }
[ForeignKey("CompanyId, CategoryId")]
public virtual CompanyCategory CompanyCategory { get; set; }
public String PreferredCategoryName
{
get{
return (CompanyCategory.CategoryAlias == null || CompanyCategory.CategoryAlias == "") ? GlobalCategory.CategoryName : CompanyCategory.CategoryAlias;
}
}
}
Controller LINQ examples:
var categories = (from i in db.Items
where i.CompanyId == siteCompanyId
orderby i.PreferredCategoryName
select i.PreferredCategoryName).Distinct();
var itemsInCategory = (from i in db.Items
where i.CompanyId == siteCompanyId
&& i.PreferredCategoryName == categoryName
select i);
For one you are using a compiled function (getPreferredCategoryName) in the query, unless EF knows how to translate that you are in trouble.
Try the following in item definition:
public static Expression<Func<Item,String>> PreferredCategoryName
{
get
{
return i => (i.CompanyCategory.CategoryAlias == null || i.CompanyCategory.CategoryAlias == "") ?
i.GlobalCategory.CategoryName :
i.CompanyCategory.CategoryAlias;
}
}
Which is used as follows:
var categories = db.Items.Where(i => i.CompanyID == siteCompanyId)
.OrderBy(Item.PreferredCategoryName)
.Select(Item.PreferredCategoryName)
.Distinct();
This should work as you have a generically available uncompiled expression tree that EF can then parse.

Intellisense in linq projection after group by

I have a Collection<IStatements> statements which has
public interface IStatements
{
IDocuments Documents { get; set; }
string StatementDate { get; set; }
}
public class Documents : IDocuments
{
public string Date { get; set; }
public string Url { get; set; }
}
I would like to perform a group by on StatementDate and then do a projection.
When projecting if the group has more then one statement then I would like to club their statement date into 1. The problem is that I don't get Intellisense after groupby
var monthlyStatements = from mStatement in statements
orderby mStatement.StatementDate descending
group mStatement by mStatement.StatementDate;
I have tried following code
var monthlyStatements = from mStatement in statements
orderby mStatement.StatementDate descending
group mStatement by mStatement.StatementDate
into msStatement
select new
{
StatementDate =
mStatement.Documents.Date.,//no Intellisense
};
msStatement is going to be a grouping of IStatements, not a single IStatements. So you can do two things with it:
Get the Key property (which will be the statement date)
Get the contents of the group (which will each be an IStatements)
It's not really clear what you're trying to do with the multiple IStatements though.
The reason you haven't got dStatementDate any more is because you're using a query continuation; the only thing left is the grouping. Fortunately it's irrelevant here as you can get the statement date from the key; you can remove your "let" clause completely.

Resources