I have a table structure, each table with a primary key field named as "ID" and foreign key names matching their parent table's primary key. Therefore, the tables below have relationships where their primary key appears in another table and the first field in any table is it's primary key:
Category
--------
CategoryID
Title
CategoryList
------------
CategoryListID
CategoryID
ListID
List
----
ListID
Title
DataPoint
---------
DataPointID
RecordedDateTime
DataPointValue
--------------
DataPointValueID
DataPointID
TheValue
The above is a many-to-many join between Category and List, via CategoryList. It is also a one-to-many join from List to DataPoint, DataPoint to DataPointValue.
Using C#/LINQ and given a List of the CategoryID values, I would like to retrieve:
All the List entries attached to the Category I have ID's for. With those List entries, I would like to take the most recent 1 DataPoint, as ordered by RecordedDateTime Descending. From there I would like to retrieve every DataPointValue attached to the DataPoint.
The LINQ I have is:
DBDataContext context = new DBDataContext(ConnectionString);
context.LoadOptions = new DataLoadOptions();
context.LoadOptions.LoadWith<DataPoint>(p => p.DataPoints.OrderByDescending(p.RecordedDataTime).FirstOrDefault());
// this next line is how I get the list of category IDs, but don't worry about that...
List<int> categoryIDs = (from TreeNode n in nodes
select Int32.Parse(n.Value)).Distinct().ToList();
var lists = from i in context.List
join ci in context.CategoryLists on i.ListID equals ci.ListID
join p in context.DataPoints on i.ListID equals p.ListID
join v in context.DataPointValues on p.DataPointID equals v.DataPointID
where categoryIDs.Contains(ci.CategoryID)
orderby i.Title ascending
select new
{
List = i,
DataPoint = p,
DataPointValues = p.DataPointValues
};
But this is obviously not working - the LoadWith is causing me issues. Could someone explain how to construct the LoadWith so that it will cause as few SQL queries as possible to retrieve this (admittedly large) amount of data, please?
Many thanks,
Matt.
You asked this a month ago, but here's an answer anyway...
There are a few issues here:
Once you set the LoadOptions property on the context, you can't change it. You should create and configure your DataLoadOptions object, and when you're done, assign it to the context.
LoadWith specifies what children get automatically loaded with the parent. So loadOptions.LoadWith<DataPoint>(p => p.DataPointValues) would automatically load the DataPoint's children, and not wait until the DataPoint.DataPointValues (or whatever you name it) property is accessed. LoadWith makes the loading non-lazy (eager).
AssociateWith allows you to filter and order the in children that automatically-loading relationship. For example loadOptions.AssociateWith<DataPoint>(p => p.DataPointValues.OrderByDescending(v => v.TheValue)) would sort the DataPointValues by value.
And finally, I'd probably break up your query into two, just to make it easier.
// first setup a DataPoint -> DataPointValue relationship in your DBML
// then set up the DataPointValues to automatically load with DataPoint:
dataLoadOptions.LoadWith<DataPoint>(dp => dp.DataPointValues);
// then assign the load options to the context here
// First query
List<int> listIDs = context.CategoryLists
.Where(cl => categoryIDs.Contains(cl.CategoryListID))
.Select(cl => cl.ListID)
.ToList();
// Second query(ies) - this isn't the most elegant, but simple is usually better :)
List<DataPoint> dataPoints = new List<DataPoint>();
foreach (int listID in listIDs)
{
DataPoint foundDP = context.DataPoints
.Where(dp => listIDs.Contains(dp.ListID))
.OrderByDescending(dp => dp.RecordedDateTime)
.Take(1)
.SingleOrDefault();
// Remember, at this point DataPointValues will already be loaded into the DataPoint
if (foundDP != null)
dataPoints.Add(foundDP);
}
Anyway, that's a longwinded answer that you may or may not even need! Ah well, it's practice for me, I guess. Hope it helps.
EDIT:
Sorry, started thinking about this...
You could possibly do this instead (cleaner, faster):
loadOptions.LoadWith<List>(l => l.DataPoints);
loadOptions.AssociateWith<List>(l => l.DataPoints.OrderByDescending(dp => dp.RecordedDateTime).Take(1));
loadOptions.LoadWith<DataPoint>(dp => dp.DataPointValues);
// assign the LoadOptions here,
// then:
List<DataPoint> dataPoints = context.CategoryLists
.Where(cl => categoryIDs.Contains(cl.CategoryID))
.Select(cl => cl.List.DataPoints)
.ToList();
Related
I have collection of Customers, potentially group-able by their preferences (oranges, apples)
CustomerID | Preference | Age
1 oranges 35
2 apples 32
... ... ...
100 oranges 48
I need kind of of summary table so can group Customers into new collection like this:
var GroupedCustomers = Customers
.GroupBy (a => new {a.Preference, ...}) //could be grouped by more complex compound key
.Select (a => new { CustomerPreference = a.Key.Prefence, TotalOrders = a.Count () })
How can I access the inner collections of each group with all original properties of their members (e.g. "Age" of each customer)? For example, I need to bind the list of "orange lovers" to a GridView and compute their average age.
The issue in the actual case is a complex compound key and hundreds of groups so I don't want to enumerate them every time from the original collection.
You need to bind the ungrouped collection to the GridView, then you can apply the filters, keeping the View and the ViewModel synchronized
Follow the documentation example under How to: Group, Sort, and Filter Data in the DataGrid Control
Comments
Binding is just subcase. I want to find general approach. Let say I
want to proceed with some math manipulation on the many properties of
the group members (like age). Does you answer mean whatever I apply
GroupBy, all properties that weren't been included to the group key
are lost?
No, they aren't. Add the list of the grouped items to the group, for example
var GroupedCustomers = Customers
.GroupBy (c=> new {a.Preference, ...}, //could be grouped by more complex compound key
c => c,
(key, g) => new {
CustomerPreference = key.Preference,
SubItems = g.ToList(), // <= this is your inner collection
...})
This my own attempt.
We should limit initial statement by GroupBy only to keep collection unchanged:
var GroupedCustomers = Customers
.GroupBy (a => new {a.Preference, ...});
Then, the inner collections would be as simple as:
var GroupByIndex = GroupedCustomers[0]; //retrieve entire group by its index
var GroupdByContent = GroupedCustomers.First(i => i.Key.Preference == "oranges") //retrieve group by key
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;
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)));
I'm trying to get a list that displays 2 values in a label from a parent and child (1-*) entity collection model.
I have 3 entities:
[Customer]: CustomerId, Name, Address, ...
[Order]: OrderId, OrderDate, EmployeeId, Total, ...
[OrderStatus]: OrderStatusId, StatusLevel, StatusDate, ...
A Customer can have MANY Order, which in turn an Order can have MANY OrderStatus, i.e.
[Customer] 1--* [Order] 1--* [OrderStatus]
Given a CustomerId, I want to get all of the Orders (just OrderId) and the LATEST (MAX?) OrderStatus.StatusDate for that Order.
I've tried a couple of attempts, but can seem to get the results I want.
private IQueryable<Customer> GetOrderData(string customerId)
{
var ordersWithLatestStatusDate = Context.Customers
// Note: I am not sure if I should add the .Expand() extension methods here for the other two entity collections since I want these queries to be as performant as possible and since I am projecting below (only need to display 2 fields for each record in the IQueryable<T>, but thinking I should now after some contemplation.
.Where(x => x.CustomerId == SelectedCustomer.CustomerId)
.Select(x => new Custom
{
CustomerId = x.CustomerId,
...
// I would like to project my Child and GrandChild Collections, i.e. Orders and OrderStatuses here but don't know how to do that. I learned that by projecting, one does not need to "Include/Expand" these extension methods.
});
return ordersWithLatestStatusDate ;
}
---- UPDATE 1 ----
After the great solution from User: lazyberezovsky, I tried the following:
var query = Context.Customers
.Where(c => c.CustomerId == SelectedCustomer.CustomerId)
.Select(o => new Customer
{
Name = c.Name,
LatestOrderDate = o.OrderStatus.Max(s => s.StatusDate)
});
In my hastiness from my initial posting, I didn't paste everything in correctly since it was mostly from memory and didn't have the exact code for reference at the time. My method is a strongly-typed IQueryabled where I need it to return a collection of items of type T due to a constraint within a rigid API that I have to go through that has an IQueryable query as one of its parameters. I am aware I can add other entities/attributes by either using the extension methods .Expand() and/or .Select(). One will notice that my latest UPDATED query above has an added "new Customer" within the .Select() where it was once anonymous. I'm positive that is why the query failed b/c it couldn't be turn into a valid Uri due to LatestOrderDate not being a property of Customer at the Server level. FYI, upon seeing the first answer below, I had added that property to my client-side Customer class with simple { get; set; }. So given this, can I somehow still have a Customer collection with the only bringing back those 2 fields from 2 different entities? The solution below looked so promising and ingenious!
---- END UPDATE 1 ----
FYI, the technologies I'm using are OData (WCF), Silverlight, C#.
Any tips/links will be appreciated.
This will give you list of { OrderId, LatestDate } objects
var query = Context.Customers
.Where(c => c.CustomerId == SelectedCustomer.CustomerId)
.SelectMany(c => c.Orders)
.Select(o => new {
OrderId = o.OrderId,
LatestDate = o.Statuses.Max(s => s.StatusDate) });
.
UPDATE construct objects in-memory
var query = Context.Customers
.Where(c => c.CustomerId == SelectedCustomer.CustomerId)
.SelectMany(c => c.Orders)
.AsEnumerable() // goes in-memory
.Select(o => new {
OrderId = o.OrderId,
LatestDate = o.Statuses.Max(s => s.StatusDate) });
Also grouping could help here.
If I read this correctly you want a Customer entity and then a single value computed from its Orders property. Currently this is not supported in OData. OData doesn't support computed values in the queries. So no expressions in the projections, no aggregates and so on.
Unfortunately even with two queries this is currently not possible since OData doesn't support any way of expressing the MAX functionality.
If you have control over the service, you could write a server side function/service operation to execute this kind of query.
I have 5 tables in a L2S Classes dbml : Global >> Categories >> ItemType >> Item >> ItemData. For the below example I have only gone as far as itemtype.
//cdc is my datacontext
DataLoadOptions options = new DataLoadOptions();
options.LoadWith<Global>(p => p.Category);
options.AssociateWith<Global>(p => p.Category.OrderBy(o => o.SortOrder));
options.LoadWith<Category>(p => p.ItemTypes);
options.AssociateWith<Category>(p => p.ItemTypes.OrderBy(o => o.SortOrder));
cdc.LoadOptions = options;
TraceTextWriter traceWriter = new TraceTextWriter();
cdc.Log = traceWriter;
var query =
from g in cdc.Global
where g.active == true && g.globalid == 41
select g;
var globalList = query.ToList();
// In this case I have hardcoded an id while I figure this out
// but intend on trying to figure out a way to include something like globalid in (#,#,#)
foreach (var g in globalList)
{
// I only have one result set, but if I had multiple globals this would run however many times and execute multiple queries like it does farther down in the hierarchy
List<Category> categoryList = g.category.ToList<Category>();
// Doing some processing that sticks parent record into a hierarchical collection
var categories = (from comp in categoryList
where comp.Type == i
select comp).ToList<Category>();
foreach (var c in categories)
{
// Doing some processing that stick child records into a hierarchical collection
// Here is where multiple queries are run for each type collection in the category
// I want to somehow run this above the loop once where I can get all the Items for the categories
// And just do a filter
List<ItemType> typeList = c.ItemTypes.ToList<ItemType>();
var itemTypes = (from cat in TypeList
where cat.itemLevel == 2
select cat).ToList<ItemType>();
foreach (var t in itemTypes)
{
// Doing some processing that stick child records into a hierarchical collection
}
}
}
"List typeList = c.ItemTypes.ToList();"
This line gets executed numerous times in the foreach, and a query is executed to fetch the results, and I understand why to an extent, but I thought it would eager load on Loadwith as an option, as in fetch everything with one query.
So basically I would have expected L2S behind the scenes to fetch the "global" records in one query, take any primary key values, get the "category" children using one one query. Take those results and stick them into collections linked to the global. Then take all the category keys and excute one query to fetch the itemtype children and link those into their associated collections. Something on the order of (Select * from ItemTypes Where CategoryID in ( select categoryID from Categories where GlobalID in ( #,#,# ))
I would like to know how to properly eager load associated children with minimal queries and possibly how to accomplish my routine generically not knowing how far down I need to build the hierarchy, but given a parent entity, grab all the associated child collections and then do what I need to do.
Linq to SQL has some limitations with respect to eager loading.
So Eager Load in Linq To SQL is only
eager loading for one level at a time.
As it is for lazy loading, with Load
Options we will still issue one query
per row (or object) at the root level
and this is something we really want
to avoid to spare the database. Which
is kind of the point with eager
loading, to spare the database. The
way LINQ to SQL issues queries for the
hierarchy will decrease the
performance by log(n) where n is the
number of root objects. Calling ToList
won't change the behavior but it will
control when in time all the queries
will be issued to the database.
For details see:
http://www.cnblogs.com/cw_volcano/archive/2012/07/31/2616729.html
I am sure this could be done better, but I got my code working with minimal queries. One per level. This is obviously not really eager loading using L2S, but if someone knows the right way I would like to know for future reference.
var query =
from g in cdc.Global
where g.active == true && g.globalId == 41
select g;
var globalList = query.ToList();
List<Category> categoryList = g.category.ToList<Category>();
var categoryIds = from c in cdc.Category
where c.globalId == g.globalId
select c.categoryId;
var types = from t in cdc.ItemTypes
where categoryIds.Any(i => i == t.categoryId)
select t;
List<ItemType> TypeList = types.ToList<ItemType>();
var items = from i in cdc.Items
from d in cdc.ItemData
where i.ItemId == d.ItemId && d.labelId == 1
where types.Any(i => i == r.ItemTypes)
select new
{
i.Id,
// A Bunch of more fields shortened for berevity
d.Data
};
var ItemList = items.ToList();
// Keep on going down the hierarchy if you need more child results
// Do your processing psuedocode
for each item in list
filter child list
for each item in child list
.....
//
Wouldn't mind knowing how to do this all using generics and a recursive method given the top level table