Need to Include() related entities but no option to do so - c#

I'm not sure how else to word the title of this question so let me explain.
I have a need to select most of one entity type from my database, using .Include to select it's related entities, but at the same time to only select the entities where the entity identifier is equal to one of the IDs in a string array.
My code as follows:
List<TSRCategory> electives = new List<TSRCategory>();
foreach (var i in client.Electives.Split('&'))
{
int id = Int32.Parse(i);
electives.Add(db.TSRCategories.Find(id));
}
This correctly selects the TSRCategories that are part of the Electives list of IDs, but does not include the related entities. I was using this code:
TSRCategories = db.TSRCategories.Include("Competencies.CompetencySkills").ToList();
but this does not select only the chosen Electives. What I am ideally looking for is something like this:
List<TSRCategory> electives = new List<TSRCategory>();
foreach (var i in client.Electives.Split('&'))
{
int id = Int32.Parse(i);
electives.Add(db.TSRCategories.Find(id));
}
TSRCategories = electives.Include("Competencies.CompetencySkills").ToList();
But of course this can't be done for whatever reason (I don't actually know what to search for online in terms of why this can't be done!). Electives is a string with the & as a delimiter to separate the IDs into an array. TSRCategories contains Competencies which contains CompetencySkills. Is there a way to actually do this efficiently and in few lines?

You will find that fetching the associated ids one by one will result in poor query performance. You can fetch them all in one go by first projecting a list of all the needed ids (I've assumed the key name ElectiveId here):
var electiveIds = client.Electives.Split('&')
.Select(i => Int32.Parse(i))
.ToArray();
var electives = db.TSRCategories
.Include(t => t.Competencies.Select(c => c.CompetencySkills))
.Where(tsr => electiveIds.Contains(tsr.ElectiveId))
.ToList();
But one thing to mention is that the storage of your ids in a single string field joined by a delimiter violates database normalization. Instead, you should create a new junction table, e.g. ClientElectives which link the Electives associated with a Client in normalized fashion (ClientId, ElectiveId). This will also simplify your EF retrieval code.
Edit
According to the examples in the documentation, I should be using .Select for depth specification of the eager loading (not .SelectMany or other extension methods).

Try to use this extensions method:
using System.Data.Entity;
from x in db.Z.Include(x => x.Competencies)
.Include(x => x.Competencies.CompetencySkills)
select a.b.c;
To search by the given list of ids:
int[] ids = new int[0]; // or List<int>
from x in db.Z
where ids.Contains(x.Id)
select a.b.c;

Related

LINQ Join/Update List of Objects from Database

This issue is a new one to me in LINQ. And maybe I'm going about this wrong.
What I have is a list of objects in memory, which could number up to 100k, and I need to find in my database which objects represent an existing customer.
This search needs to be done across multiple object properties and all I have to go on are the name and address of the person - no unique identifier since this data comes from an outside source.
Is it possible to join my generic of objects against my database context and then update the generic objects, with data from the context, based on whether they are found in the join?
I thought I was getting close to the join working with the below code. And I think the join works .. maybe. But I can't even seem to loop through the records.
public void FindCustomerMatches(List<DocumentLine> lines)
{
IQueryable<DocumentLine> results = null;
var linesQuery = lines.AsQueryable();
using (var customerContext = new Entities())
{
customerContext.Configuration.LazyLoadingEnabled = false;
var dbCustomerQuery = customerContext.customers.Where(c => !c.customernumber.StartsWith("D"));
results = from c in dbCustomerQuery
from l in linesQuery
where c.firstname1 == l.CustomerFirstName
&& c.lastname1 == l.CustomerLastName
&& c.street_address1.Contains(l.CustomerAddress)
&& c.city == l.CustomerCity
&& c.state == l.CustomerState
&& c.zip == l.CustomerZip
select l;
foreach (var result in results)
{
// Do something with each record here, like update it.
}
}
}
It seems to me that you have two collections: a local collection of DocumentLines in variable lines, and a collection of Customers in a customerContext.Customers, probably in a database management system.
Every DocumentLine contains several properties that can also be found in a Customer. Alas you didn't say whether all DocumentLine properties can be found in a Customer.
From lines (the local collection of DocumentLines) you only want to keep only those DocumentLines of which there is at least one Customer in your queryable collection of Customers that match all these properties.
So the result is a sequence of DocumentLines, a sub-collection of lines.
The problem is that you don't want to query a sub-collection of the database table Customers, but you want a sub-collection of your local lines.
Using AsQueryable doesn't transport your lines to your DBMS. I doubt whether the query you defined will be performed by the DBMS. I suspect that all Customers will be transported to your local process to perform the query.
If all properties of a DocumentLine are in a Customer then it is possible to extract the DocumentLines properties from every Customer and use Queryable.Contains to keep only those extracted DocumentLines that are in your lines:
IQueryable<DocumentLine> customerDocumentLines = dbContext.Customers
.Select(customer => new DocumentLine()
{
FirstName = customer.FirstName,
LastName = customer.LastName,
...
// etc, fill all DocumentLine properties
});
Note: the query is not executed yet! No communication with the DBMS is performed
Your requested result are all customerDocumentLines that are contained in lines, removing the duplicates.
var result = customerDocumentLines // extract the document lines from all Customers
.Distinct // remove duplicates
.Where(line => lines.Contains(line)); // keep only those lines that are in lines
This won't work if you can't extract a complete DocumentLine from a Customer. If lines contains duplicates, the result won't show these duplicates.
If you can't extract all properties from a DocumentLine you'll have to move the values to check to local memory:
var valuesToCompare = dbContext.Customers
.Select(customer => new
{
FirstName = customer.FirstName,
LastName = customer.LastName,
...
// etc, fill all values you need to check
})
.Distinct() // remove duplicates
.AsEnumerable(); // make it IEnumerable,
// = efficiently move to local memory
Now you can use Enumerable.Contains to get the subset of lines. You'll need to compare by value, not by reference. Luckily anonymous types compare for equality by value
var result = lines
// extract the values to compare
.Select(line => new
{
Line = line,
ValuesToCompare = new
{
FirstName = customer.FirstName,
LastName = customer.LastName,
...
})
})
// keep only those lines that match valuesToCheck
.Where(line => valuesToCheck.Contains(line.ValuesToCompare));

ASP.NET MVC C# Select and Where Statements

I'm having trouble understanding .Select and .Where statements. What I want to do is select a specific column with "where" criteria based on another column.
For example, what I have is this:
var engineers = db.engineers;
var managers = db.ManagersToEngineers;
List<ManagerToEngineer> matchedManager = null;
Engineer matchedEngineer = null;
if (this.User.Identity.IsAuthenticated)
{
var userEmail = this.User.Identity.Name;
matchedEngineer = engineers.Where(x => x.email == userEmail).FirstOrDefault();
matchedManager = managers.Select(x => x.ManagerId).Where(x => x.EngineerId == matchedEngineer.PersonId).ToList();
}
if (matchedEngineer != null)
{
ViewBag.EngineerId = new SelectList(new List<Engineer> { matchedEngineer }, "PersonId", "FullName");
ViewBag.ManagerId = new SelectList(matchedManager, "PersonId", "FullName");
}
What I'm trying to do above is select from a table that matches Managers to Engineers and select a list of managers based on the engineer's id. This isn't working and when I go like:
matchedManager = managers.Where(x => x.EngineerId == matchedEngineer.PersonId).ToList();
I don't get any errors but I'm not selecting the right column. In fact the moment I'm not sure what I'm selecting. Plus I get the error:
Non-static method requires a target.
if you want to to select the manager, then you need to use FirstOrDefault() as you used one line above, but if it is expected to have multiple managers returned, then you will need List<Manager>, try like:
Update:
so matchedManager is already List<T>, in the case it should be like:
matchedManager = managers.Where(x => x.EngineerId == matchedEngineer.PersonId).ToList();
when you put Select(x=>x.ManagerId) after the Where() now it will return Collection of int not Collection of that type, and as Where() is self descriptive, it filters the collection as in sql, and Select() projects the collection on the column you specify:
List<int> managerIds = managers.Where(x => x.EngineerId == matchedEngineer.PersonId)
.Select(x=>x.ManagerId).ToList();
The easiest way to remember what the methods do is to remember that this is being translated to SQL.
A .Where() method will filter the rows returned.
A .Select() method will filter the columns returned.
However, there are a few ways to do that with the way you should have your objects set up.
First, you could get the Engineer, and access its Managers:
var engineer = context.Engineers.Find(engineerId);
return engineer.Managers;
However, that will first pull the Engineer out of the database, and then go back for all of the Managers. The other way would be to go directly through the Managers.
return context.Managers.Where(manager => manager.EngineerId == engineerId).ToList();
Although, by the look of the code in your question, you may have a cross-reference table (many to many relationship) between Managers and Engineers. In that case, my second example probably wouldn't work. In that case, I would use the first example.
You want to filter data by matching person Id and then selecting manager Id, you need to do following:
matchedManager = managers.Where(x => x.EngineerId == matchedEngineer.PersonId).Select(x => x.ManagerId).ToList();
In your case, you are selecting the ManagerId first and so you have list of ints, instead of managers from which you can filter data
Update:
You also need to check matchedEngineer is not null before retrieving the associated manager. This might be cause of your error
You use "Select" lambda expression to get the field you want, you use "where" to filter results

filtering a table by ids of another table in linq

So lets say i have a table mappedIds of an entity from linq, which has a relationship with another table named finishedDownloads on its column categoryID. How do i make a new table of mappedIds that contain no ids found in finishedDownloads?
I understand the commands like where and except, but im just not sure how to say, look at this id, and compare it to that id.
I'm looking for the equivalent of
SELECT * FROM mappedIds mIDs WHERE mIDs.CategoryID NOT IN
(SELECT categoryID FROM finishedDownloads)
Edit: Mapped Ids is stored in a Table
You didn't say how your context is set, but even if it's not exactly as I think it is you can easily see the idea:
var results = _context.MappedIds
.Where(x => !_context.FinishedDownloads
.Select(f => f.categoryID)
.Contains(x.CategoryID));
var idList = finishedDownloads.Select(f => f.categoryID);
var result = mappedIds.Where(m => !idList.Contains(m.CategoryID)).ToList();
Try this
var result = mappedIds.Select(m=>m.CategoryId).Except(finishedDownloads
.Select(f=>f.categoryId));

Is it possible to query all objects that have one or more possible children using NHibernate?

I have a table called Recipes which contain one recipe per row. I also have a table called RecipeIngredients which contain one ingredient as used by a particular recipe. Thus, each Recipe row has one or more children RecipeIngredients rows.
What I'm trying to do is create a query to find all recipes that contain any ingredients in a list of desired ingredients. For example, show me all recipes that use either flour, eggs, or bananas.
The SQL would look something like this:
SELECT * FROM Recipes r
WHERE EXISTS (select 1 from RecipeIngredients where RecipeId = r.RecipeId and IngredientId = ANY (5, 10, 15) limit 1);
However, I'm having a tough time figuring out how to express this as a LINQ query, or using the .QueryOver<T> method. I don't want to hard code in the SQL since this needs to be database agnostic and I want the configured NHibernate dialect to generate the correct code.
Any ideas?
NHibernate has support for this SQL statements, called
15.8. Detached queries and subqueries,
16.8. Subqueries
The syntax would be like this:
var session = ...// get a ISession
Reciepe reciepe = null; // this will be a reference to parent
// the SELECT inside of EXISTS
var subquery = QueryOver.Of<ReciepeIngredient>()
// The PARENT handling here
// the filter, to find only related ingredients
.Where(item => item.ReciepeId == reciepe.ID)
.Where(Restrictions.In("ID", new[] { 5, 10, 15 }))
// Select clause
.Select(ing => ing.ID)
;
Having the above subquery, we can use it like this
// the '() => reciepe' setting is essential here, it represents parent in a subquery
var query = session.QueryOver<Reciepe>(() => reciepe);
query.WithSubquery
// our EXISTS (...
.WhereExists(subquery);
var list = query
.List<Reciepe>();
NOTE: let's check even more deeper subquery(ies) usage here Query on HasMany reference
A Few More Details:
Radim's answer turns out to be the best way to express the sub-query, however there's a few gotchas that took me a while to figure out. Thus, I'll post an answer as well to fill in the details.
First off, the line:
.Where(Restrictions.In("ID", new[] { 5, 10, 15 }))
Doesn't actually work if ID refers to an entity itself. In other words:
.Where(Restrictions.In("Ingredient", arrayOfIds))
Will throw a very confusing null reference exception since the Ingredient field maps to a Ingredients object. Using "IngredientId" doesn't work either. In that case, you have to use this:
.Where(Restrictions.In("Ingredient", arrayOfIds
.Select(id => new Ingredients(id)).ToArray()))
To cast the ID array to an array of Ingredients objects. After that, things start working.
I also found an easy performance improvement that made the query run noticably faster, at least on PostgreSQL. If you change the sub-query from:
WHERE exists (SELECT RecipeIngredientId FROM recipeingredients WHERE
RecipeId = r.RecipeId and IngredientId in (:p0, :p1))
To:
WHERE exists (SELECT RecipeIngredientId FROM recipeingredients WHERE
RecipeId = r.RecipeId and IngredientId in (:p0, :p1) LIMIT 1)
It will only have to check a single row within the nested query. The query ran about twice as fast for me. This is easy to express:
var subquery = QueryOver.Of<RecipeIngredients>()
.Where(item => item.Recipe.RecipeId == recipe.RecipeId)
.Where(Restrictions.In("Ingredient", allowedIngs))
.Select(i => i.RecipeIngredientId).Take(1);
Hope this helps!
Try this Linq query:
recipes.Where(r => r.RecipeIngredients.Any(i => new long[]{5, 10, 15}.Contains(i.Id)));

In LINQ, how can I do an .OrderBy() on data that came from my .Include()?

Here's what I'm doing:
List<Category> categories =
db.Categories.Include("SubCategories").OrderBy(c => c.Order).ToList();
I have a column on my categories table called "Order" which simply holds an integer that gives the table some kind of sorting order.
I have the same column on my "SubCategories" table...
I want to know the simplest solution to add the sort on my subcategories table... something like:
List<Category> categories =
db.Categories.Include("SubCategories").OrderBy(c => c.Order)
.ThenBy(c => c.SubCategories as x => x.Order).ToList();
I'd like to keep it in this type of LINQ format... (method format)...
Keep in mind, i'm working in MVC and need to return it to a view as a model. I've been having trouble with errors because of AnonymousTypes...
I'm not sure if this is supported, but here's how it might be done:
List<Category> categories =
db.Categories.Include(c => c.SubCategories.OrderBy(s => s.Order)).OrderBy(c => c.Order)
The Include method now supports Expressions like this, but I'm not certain if it supports ordering too.
You might be better off sorting the subcategories when you use them, probably in your view.
For example:
#for (var cat in Model.Categories) {
#cat.Name
#for (var sub in cat.SubCategories.OrderBy(c => c.Order) {
#sub.Name
}
}
You can split the single query into 2 queries which just fill up the context:
IQueryable<Category> categoryQuery = db.Categories.Where(c=> /*if needed*/);
List<Category> categories = categoryQuery.OrderBy(c => c.Order).ToList();
categoryQuery.SelectMany(c => c.SubCategories)
.OrderBy(sub => sub.Order)
.AsEnumerable().Count(); // will just iterate (and add to context) all results
You even don't need the error prone string "SubCategories" anymore then.
If Category.SubCategories is a collection in itself, then you won't be able to order using the existing extension methods (and c => c.SubCategories as x => x.Order translates to almost nothing, basically saying that SubCategories is a Func<SubCategory, bool>)
If you're content to have the sorting done in memory (which shouldn't really be a problem since you're already fetching them from the database anyway, provided you don't have thousands of the things) you can implement your own custom IComparer<Category> which interrogates the SubCategories of each Category to determine whether one Category should be placed above or below another Category in a sort operation.
Your statement would then be:
var categories = db.Categories.Include("SubCategories").OrderBy(x => x, new CategorySubCategoryComparer())

Categories