Is there a way, using LINQ/EF, to get the top most item in a parent/child hierarchy? - linq

I have a class called Structure:
public class Structure
{
public int StructureId { get; set; }
public Structure Parent { get; set; }
}
As you can see, Structure has a parent Structure. There can be an indefinite number of structures within this hierarchy.
Is there any way, using LINQ (with Entity Framework), to get the top-most structure in this hierarchy?
Currently, I'm having to hit the database quite a few times in order to find the top most parent. The top most parent is a Structure with a null Parent property:
Structure structure = structureRepository.Get(id);
while (structure.Parent != null)
{
structure = structureRepository.Get(structure.Parent.StructureId);
}
// When we're here; `structure` is now the top most parent.
So, is there any elegant way to do this using LINQ/Lambdas? Ideally, starting with the following code:
var structureQuery = from item in context.Structures
where item.StructureId == structureId
select item;
I just want to be able to write something like the following so that I only fire off one database hit:
structureQuery = Magic(structureQuery);
Structure topMostParent = structureQuery.Single();

This is not a direct answer, but the problem you are having is related to the way you are storing your tree. There are a couple ways of simplifying this query by structuring data differently.
One is to use a Nested Set Hierarchy, which can simplify many kinds of queries across trees.
Another is to store a denomralized table of Ancestor/Descendant/Depth tuples. This query then becomes finding the tuple with the current structure as the descendant with the maximum depth.

I think the best I'm going to get is to load the entire hierarchy in one hit from the structure I want the top parent of:
var structureQuery = from item in context.Structures
.Include(x => x.Parent)
where item.StructureId == structureId
select item;
Then just use the code:
while (structure.Parent != null)
{
structure = structure.Parent;
}

I have a similar situation. I didn't manage to solve it directly with LINQ/EF. Instead I solved by creating a database view using recursive common table expressions, as outlined here. I made a user-defined function that cross applies all parents to a child (or vice versa), then a view that makes use of this user-defined function which I imported into my EF object context.
(disclaimer: simplified code, I didn't actually test this)
I have two tables, say MyTable (containing all items) and MyParentChildTable containing the ChildId,ParentId relation
I have then defined the following udf:
CREATE FUNCTION dbo.fn_getsupertree(#childid AS INT)
RETURNS #TREE TABLE
(
ChildId INT NOT NULL
,ParentId INT NULL
,Level INT NOT NULL
)
AS
BEGIN
WITH Parent_Tree(ChildId, ParentId)
AS
(
-- Anchor Member (AM)
SELECT ChildId, ParentId, 0
FROM MyParentChildTable
WHERE ChildId = #childid
UNION all
-- Recursive Member (RM)
SELECT info.ChildId, info.ParentId, tree.[Level]+1
FROM MyParentChildTable AS info
JOIN Parent_Tree AS tree
ON info.ChildId = tree.ParentId
)
INSERT INTO #TREE
SELECT * FROM Parent_Tree;
RETURN
END
and the following view:
CREATE VIEW VwSuperTree AS (
SELECT tree.*
FROM MyTable
CROSS APPLY fn_getsupertree(MyTable.Id) as tree
)
GO
This gives me for each child, all parents with their 'tree level' (direct parent has level 1, parent of parent has level 2, etc.). From that view, it's easy to query the item with the highest level. I just imported the view in my EF context to be able to query it with LINQ.

I like the question and can't think of a linq-y way of doing this. But could you perhaps implement this on your repository class? After all, there should be only one at the top and if the need for it is there, then maybe it deserves a structureRepository.GetRoot() or something.

you can use the linq take construct, for instance
var first3Customers = (
from c in customers
select new {c.CustomerID, c.CustomerName} )
.Take(2);

Related

Entity Framework, Table Per Type and Linq - Getting the "Type"

I have an Abstract type called Product, and five "Types" that inherit from Product in a table per type hierarchy fashion as below:
I want to get all of the information for all of the Products, including a smattering of properties from the different objects that inherit from products to project them into a new class for use in an MVC web page. My linq query is below:
//Return the required products
var model = from p in Product.Products
where p.archive == false && ((Prod_ID == 0) || (p.ID == Prod_ID))
select new SearchViewModel
{
ID = p.ID,
lend_name = p.Lender.lend_name,
pDes_rate = p.pDes_rate,
pDes_details = p.pDes_details,
pDes_totTerm = p.pDes_totTerm,
pDes_APR = p.pDes_APR,
pDes_revDesc = p.pDes_revDesc,
pMax_desc = p.pMax_desc,
dDipNeeded = p.dDipNeeded,
dAppNeeded = p.dAppNeeded,
CalcFields = new DAL.SearchCalcFields
{
pDes_type = p.pDes_type,
pDes_rate = p.pDes_rate,
pTFi_fixedRate = p.pTFi_fixedRate
}
}
The problem I have is accessing the p.pTFi_fixedRate, this is not returned with the Products collection of entities as it is in the super type of Fixed. How do I return the "super" type of Products (Fixed) properties using Linq and the Entity Framework. I actually need to return some fields from all the different supertypes (Disc, Track, etc) for use in calculations. Should I return these as separate Linq queries checking the type of "Product" that is returned?
This is a really good question. I've had a look in the Julie Lerman book and scouted around the internet and I can't see an elegant answer.
If it were me I would create a data transfer object will all the properties of the types and then have a separate query for each type and then union them all up. I would insert blanks into the DTO properies where the properties aren't relevant to that type. Then I would hope that the EF engine makes a reasonable stab at creating decent SQL.
Example
var results = (from p in context.Products.OfType<Disc>
select new ProductDTO {basefield1 = p.val1, discField=p.val2, fixedField=""})
.Union(
from p in context.Products.OfType<Fixed>
select new ProductDTO {basefield1 = p.val1, discField="", fixedField=p.val2});
But that can't be the best answer can it. Is there any others?
So Fixed is inherited from Product? If so, you should probably be querying for Fixed instead, and the Product properties will be pulled into it.
If you are just doing calculations and getting some totals or something, you might want to look at using a stored procedure. It will amount to fewer database calls and allow for much faster execution.
Well it depends on your model, but usually you need to do something like:
var model = from p in Product.Products.Include("SomeNavProperty")
.... (rest of query)
Where SomeNavProperty is the entity type that loads pTFi_fixedRate.

Can LINQ ToArray return a strongly-typed array in this example?

I've contrived this example because it's an easily digested version of the actual problem I'm trying to solve. Here are the classes and their relationships.
First we have a Country class that contains a Dictionary of State objects indexed by a string (their name or abbreviation for example). The contents of the State class are irrelevant:
class Country
{
Dictionary<string, State> states;
}
class State { ... }
We also have a Company class which contains a Dictionary of zero or more BranchOffice objects also indexed by state names or abbreviations.
class Company
{
Dictionary<string, BranchOffice> branches;
}
class BranchOffice { ... }
The instances we're working with are one Country object and an array of Company objects:
Country usa;
Company companies[];
What I want is an array of the State objects which contain a branch. The LINQ I wrote is below. First it grabs all the companies which actually contain a branch, then joins to the list of states by comparing the keys of both lists.
The problem is that ToArray returns an anonymous type. I understand why anonymous types can't be cast to strong types. I'm trying to figure out whether I could change something to get back a strongly typed array. (And I'm open to suggestions about better ways to write the LINQ overall.)
I've tried casting to BranchOffice all over the place (up front, at list2, at the final select, and other less-likely candidates).
BranchOffice[] offices =
(from cm in companies
where cm.branches.Count > 0
select new {
list2 =
(from br in cm.branches
join st in usa.states on br.Key equals st.Key
select st.Value
)
}
).ToArray();
You can do:
select new MyClassOfSomeType {
..
)
For selection, you can give it a custom class type. You can also then use ToList. With ArrayList, if you need to keep it loosely typed, you can then make it strongly typed later using Cast<>, though only for any select result that doesn't generate an anonymous class.
HTH.
If i understand the problem correctly, the you want just the states that have office brances in them, not the branches too. If so, one posible linq is the following:
State[] offices =
(from cm in companies
where cm.branches.Count > 0
from br in cm.branches
join st in usa.states on br.Key equals st.Key
select st.Value
).Distinct().ToArray();
If you want both the states and the branches, then you will have to do a group by, and the result will be an IEnumerable>, which you can process after.
var statesAndBranches =
from cm in companies
where cm.branches.Count > 0
from br in cm.branches
join st in usa.states on br.Key equals st.Key
group br.Value by st.Value into g
select g;
Just one more thing, even though you have countries and branches declared as dictionaries, they are used as IEnumerable (from keyValuePair in dictionary) so you will not get any perf benefit form them.

What Linq to Entities statement tells me if a particular child entity exists?

Here is a foreach statement I'd like to express in Linq to Entities. It loops through child entities (attachments) of a parent entity (currentFactSheet) to see if an attachment exists with a particular FileName. How can I condense this procedural code into a Linq to Entites statement?
FactSheet currentFactSheet = mainWindow.GetCurrentFactSheet();
bool attachmentExists = false;
foreach (var thisAttachment in currentFactSheet.AttachmentsNav)
{
if (thisAttachment.FileName == nameOfAttachedFile)
{
attachmentExists = true;
}
}
This is a partial image showing FactSheet (left) and the Attachment entity associated via a navigation property named AttachmentsNav:
I want to query in memory entities to avoid a round trip to the database. I've found examples like this that search only the parent level. I've made many attempts, but they never bring up intellisense with the field names on my child entity (specifically Attachment.FileName).
Thanks in advance.
Try this:
bool attachmentExists = currentFactSheet.AttachmentsNav.Any(a => a.FileName == nameOfAttachedFile);

LINQ to SQL many to many int ID array criteria query

Ok this should be really simple, but I am doing my head in here and have read all the articles on this and tried a variety of things, but no luck.
I have 3 tables in a classic many-to-many setup.
ITEMS
ItemID
Description
ITEMFEATURES
ItemID
FeatureID
FEATURES
FeatureID
Description
Now I have a search interface where you can select any number of Features (checkboxes).
I get them all nicely as an int[] called SearchFeatures.
I simply want to find the Items which have the Features that are contained in the SearchFeatures.
E.g. something like:
return db.Items.Where(x => SearchFeatures.Contains(x.ItemFeatures.AllFeatures().FeatureID))
Inside my Items partial class I have added a custom method Features() which simply returns all Features for that Item, but I still can't seem to integrate that in any usable way into the main LINQ query.
Grr, it's gotta be simple, such a 1 second task in SQL. Many thanks.
The following query will return the list of items based on the list of searchFeatures:
from itemFeature in db.ItemFeatures
where searchFeatures.Contains(itemFeature.FeatureID)
select itemFeature.Item;
The trick here is to start with the ItemFeatures table.
It is possible to search items that have ALL features, as you asked in the comments. The trick here is to dynamically build up the query. See here:
var itemFeatures = db.ItemFeatures;
foreach (var temp in searchFeatures)
{
// You will need this extra variable. This is C# magic ;-).
var searchFeature = temp;
// Wrap the collection with a filter
itemFeatures =
from itemFeature in itemFeatures
where itemFeature.FeatureID == searchFeature
select itemFeature;
}
var items =
from itemFeature in itemFeatures
select itemFeature.Item;

LINQ-to-SQL select filter

Is there a way to ensure a particular conditional clause is added to the expression tree on each select from a particular table?
For example, a table with a field with the date a record was deleted should never come back or be included in any kind of statement.
Rather than include a where clause each time, is there a way, without creating a view, to add a conditional to each select?
--- Edit for clarity below ---
I'm looking for a function I can partial, much like the Insert/Update/Delete functions, but for Selecting. I want to apply a blanket filter to all queries against a table.
Furthermore, if I get a collection of items from a parent, I want that set to be filtered as well.
Something like:
Private Function BaseItems() As IQueryable(Of Item)
Return (From mi In dataContext.Items Where mi.DeletedAt Is Nothing Select mi)
End Function
Public Function GetItems() as list(of Item)
Return (From mi in BaseItems() select mi).ToList()
End Function
works for functions I write and call. ITEMS, can be a child of MASTER, for example.
'assume TheMaster is a LinqToSQL data class which has a one to many child of Items
TheMaster.Items.Count '<-- will bring back all Items.
How do I always filter what populates by data classes?
You should be able to do this my putting the items into a list and then use lambda expressions to filter the list?
MyListObject.Where(x => x == x.Date);
I'm not sure I understand the context of your question. However you can add more where conditionals on a linq query. If you return an IQueryable, you'll only return a runnable query and you can chain on it other LINQ queries. Afaik it won't execute until you start making it to an IEnumerable or List to iterate through.
Example on LINQ-to-SQL:
MyDataContext context = new MyDataContext();
public IQueryable<MyTable> GetTable() {
return from record in context.Records
where record.Date > DateTime.Now
select record;
}
public IEnumerable<MyTable> GetTableWithinWeek() {
return from record in GetTable()
where record.Date < DateTime.Now.AddDays(7);
select record;
}
I hope my answer makes sense.
AssociateWith in the DataLoadOptions of the DataContext seems to do what I'm needing.

Resources