Entity Framework Core: load full hierarchy - c#

Having a self referencing table, with a ParentId attribute which holds the id of the parent record, what can I do so that using ef I will load into each parent its children.
What I want is to transform this cte which will return the full hierarchy as a collection.
var queryString = #"
;WITH cte AS (
SELECT * FROM [dbo].[Folders] _f WHERE _f.[Id] = #id
UNION ALL
SELECT _c.* FROM [dbo].[Folders] _c
INNER JOIN cte _cte
ON _cte.[Id] = _c.[ParentFolderId]
)
SELECT * FROM cte";
return await this.Entities.FromSql(new RawSqlString(queryString), new SqlParameter("id", id)).ToListAsync();
into something that will somehow load the hierarchy of children into their parents, keeping at the same time the performance of one trip to db.
class Folder
{
public int Id { get; set; }
public int? FolderId { get; set; }
public Folder Folder { get; set; }
public IEnumerable<Folder> Children { get; set; }
}
Hierarchy example
- Main (Id: 1 / ParentId: null)
- C1 (2/1)
- C11 (4/2)
- C111 (7/4)
- C12 (5/2)
- C2 (3/1)
- C21 (6/3)
- C211 (8/6)
Configured relation
builder.Ignore(prop => prop.Folder);
builder.HasOne(prop => prop.Folder).WithMany(prop => prop.Children).HasForeignKey(fk => fk.FolderId);

If you want the entire hierarchy in one query, that's easy. Just retrieve all the Folders and if Change Tracking is enabled EF will fix-up all the relationships. IE if you just run
var folders = db.Set<Folder>().ToList();
You'll have the whole hierarcy with all the Navigation Properties populated.

You can get the whole hierarchy with this query:
var hierarchy = db.Set<Folder>().Include(f => f.Children).ToList();

Related

EF Core SQL Filter Translation

I'm using EF Core 5.0.1 with ASP NET 5.0.1 Web API and I want to build a query with PredicateBuilder using LinqKit.Microsoft.EntityFrameworkCore 5.0.2.1
For the purposes of the question I simplified my model to:
public class User
{
public long IdUser { get; set; }
public string Name { get; set; }
public virtual ICollection<UserDepartment> UserDepartments { get; set; }
}
public class Department
{
public long IdDepartament { get; set; }
public string Name { get; set; }
public virtual ICollection<UserDepartment> UsersDepartment { get; set; }
}
public UserDepartment
{
public long IdUser { get; set; }
public long IdDepartment { get; set; }
public virtual Department { get; set; }
public virtual User { get; set; }
}
One User can have many Departaments and one Departament can have many Users
Three models have its correspondent table in SQL Server and a IEntityTypeConfiguration class with the appropriate relationships set up.
All I want to achieve is to search any User that belongs to any Departament which Department.Name is in a List<String>.
The List<String> contains a list of keywords, not the exact Department Name
Department table has this kind of rows:
IdDepartment
Name
1
Administration
2
HHRR
3
Sales
4
Marketing
And the List<String> can be any keyword like "Admin", "Sal", "Mark" and so on.
First attempt
... was to build a predicate like that:
List<string> kwDepartments = new List<String> {"mark","admin"};
var predicate = PredicateBuilder.New<User>(true);
predicate = predicate.And(x => x.UserDepartments.Where(y => kwDepartments.Any(c => y.Department.Name.Equals(c))).Any());
This produces a SQL with IN operator, like that:
...[t].[Name] IN (N'mark', N'admin'))
Obviously this is not what I want, but if I use .Contains instead of .Equals, an exception is thrown
The LINQ expression could not be translated
I think this is because I'm trying to evaluate a non-primitive value.
Second attempt
... was to iterate over kwDepartments and add an .Or for each string, like that:
foreach (string dep in kwDepartments )
{
predicateDep = predicateDep.Or(x => x.UserDepartments.Where(y=> y.Department.Name.Contains(dep)).Any());
}
predicate = predicate.And(predicateDep);
This returns the match that I expect, but two problems too.
The SQL translation is poor performance as EF Core anidates a INNER JOIN for each keyword. No matter if kwDepartment has two or three elements, but with 100 or 1000 elements will be inadmissible.
Each INNER JOIN on Departments table have a SELECT with all the table fields and i did not need none of them. I've tried to put a .Select(x=> x.Name) statement in the predicate to take only two fields but it make no effect.
Third attempt
... was using Full Text Search using EF.Functions.FreeText but it seems to make no difference.
My goal is to build a predicate that translate in something similar to:
SELECT [c.IdUser]. [c.Name]
FROM [User] AS [c]
WHERE EXISTS (
SELECT 1
FROM [UserDepartment] AS [u]
INNER JOIN (
SELECT [c0].[IdDepartment], [c0].[Name] <--ONLY NEED TWO FIELDS INSTEAD OF ALL FIELDS
FROM [Department] AS [c0]
) AS [t] ON [u].[IdDepartment] = [t].[IdDepartment]
WHERE ([c].[IdUser] = [u].[IdUser]) AND ([t].[Name] like (N'admin%') or [t].[Name] like (N'mark%')))
It is not mandatory to use the LIKE operator, but i put there for better understanding.
Thanks again!
You are on the right track with Or predicate, but instead of multiple Or predicates on user.UserDepatments.Any(single_match) you should create single Or based predicate to be used inside the single user.UserDepatments.Any(multi_or_match).
Something like this:
var departtmentPredicate = kwDepartments
.Select(kw => Linq.Expr((Department d) => EF.Functions.Like(d.Name, "%" + kw + "%")))
.Aggregate(PredicateBuilder.Or);
and then
predicate = predicate.And(u => u.UserDepartments
.Select(ud => ud.Department) // navigate to department
.AsQueryable() // to be able to use departtmentPredicate expression directly
.Any(departtmentPredicate));
With that code and the sample list, DbSet<User>().Where(predicate) is translated to something like this:
DECLARE #__p_1 nvarchar(4000) = N'%mark%';
DECLARE #__p_2 nvarchar(4000) = N'%admin%';
SELECT [u].[IdUser], [u].[Name]
FROM [User] AS [u]
WHERE EXISTS (
SELECT 1
FROM [UserDepartment] AS [u0]
INNER JOIN [Department] AS [d] ON [u0].[IdDepartment] = [d].[IdDepartament]
WHERE ([u].[IdUser] = [u0].[IdUser]) AND (([d].[Name] LIKE #__p_1) OR ([d].[Name] LIKE #__p_2)))

use Linq to form a relationship where none exists in the database

I've been using the .Net Core Linq expressions .Include and .ThenInclude to great success with my Entity Framework Core project.
However, I need to combine 2 models that are not in any type of relationship in the database.
My SQL query looks like this:
SELECT * FROM selectedEnzymes se
LEFT JOIN enzymeDefinitions ed ON se.selectedEnzymeID = ed.selectedEnzymeID
WHERE se.ecosystemID = 7
selectedEnzymes and enzymeDefinitions are both models.
But there is no relationship between them even though they both contained a selectedEnzymeID. In the database, there is no key.
So I was wondering, is there a way to use Linq to combine two models in such a way if no relationship exists?
Thanks!
You can use the LINQ Join and Select as you do in SQL.
Starting with something like this as models and list of both Classes:
public class SelectedEnzymes
{
public int SelectedEnzymeId { get; set; }
public int EcosystemId { get; set; }
public string OtherPropertySelectedEnzymes { get; set; }
}
public class EnzymeDefinitions
{
public int SelectedEnzymeId { get; set; }
public string OtherPropertyEnzymeDefinitions { get; set; }
}
List<SelectedEnzymes> selectedEnzymesList = new List<SelectedEnzymes>();
List<EnzymeDefinitions> enzymeDefinitionList = new List<EnzymeDefinitions>();
You are able to do something like this:
var query = selectedEnzymesList // table in the "FROM"
.Join(enzymeDefinitionList, // the inner join table
selected => selected.SelectedEnzymeId, // set the First Table Join parameter key
definition => definition.SelectedEnzymeId, // set the Secont Table Join parameter key
(selected, definition) => new { SelectedEnzyme = selected, EnzymeDefinition = definition }) // selection -> here you can create any kind of dynamic object or map it to a different model
.Where(selectAndDef => selectAndDef.SelectedEnzyme.EcosystemId == 7); // where statement
So I was wondering, is there a way to use Linq to combine two models
in such a way if no relationship exists?
In fact, this is similar to the method of obtaining two related tables.
You can directly use the following linq to achieve:
var data = (from se in _context.SelectedEnzymes
join ed in _context.EnzymeDefinitions
on se.SelectedEnzymeId equals ed.SelectedEnzymeId
where se.EcosystemId == 7
select new { se.Name,se.LocationId, ed.Name,ed.CountryId }).ToList();
Here is the result:

How to use subquery as a join in NHibernate?

I have the following SQL code:
SELECT err.*,tmp.counted FROM sm_database.error err
LEFT JOIN (
SELECT sme_Hash, COUNT(*) as counted FROM sm_database.error GROUP BY sme_Hash
) tmp
ON tmp.sme_Hash = err.sme_Hash
WHERE sme_Id = 197
Above will get me a extra column counted that should set into my Count property below.
the id 197 will be parameter I will use in a method.
My class name is:
public class Error {
public virtual int Count { get; set; }
public virtual DateTime Date { get; set; }
public virtual int Id { get; set; }
public virtual string Hash { get; set; }
-----------------
//more property's
}
I want to "convert" this into NHibernate with Query(or QueryOver)
Anyone know how to do this properly or could point me in the right direction?
EDIT
I got it with the following:
string query = #"SELECT err.* ,tmp.Count
FROM sm_database.error err
LEFT JOIN (
SELECT sme_Hash as Hash, COUNT(*) as Count FROM sm_database.error GROUP BY sme_Hash
) tmp
ON tmp.Hash = err.sme_Hash
WHERE sme_Id = :id";
var result = session.CreateSQLQuery(query)
.AddEntity(typeof(Error))
.SetParameter("id", id).List<Error>().SingleOrDefault();
It works fine, everthing is pulled except the Count property.
The Count property in my Error class isn't mapped cause we don't have a column in the database because we calculated the count in the query.
Is there a way to get the tmp.Count value in my Error.Count property?
Thanks in advance.
There is no easy answer, no built in solution for this kind of SQL construct.
The options how to go around are:
1) Create special entity and map it to this view: "SELECT sme_Hash, COUNT(*) as counted FROM sm_database.error GROUP BY sme_Hash" It could be either DB view or mapping with subselect (see Mapping native sql to Entity with NHibernate)
2) use native SQL with CreateSQLQuery
Because NHibernate provides querying on top of Entity model. I.e. the FROM is always coming from mapping. No way how to create that as above

Linq EF Split Parent into multiple Parents

Using Entity Framework to query a database with a Parent table and Child table with a 1-n relationship:
public class Parent {
public int id { get; set; }
public IList<Child> Children { get; set; }
}
public class Child {
public int id { get; set; }
}
Using EF, here's a quick sample query:
var parents = context.Parents;
Which returns:
parent id = 1, children = { (id = 1), (id = 2), (id = 3) }
What we need is for this to flatten into a 1-1 relationship, but as a list of parents with a single child each:
parent id = 1, children = { (id = 1) }
parent id = 1, children = { (id = 2) }
parent id = 1, children = { (id = 3) }
We're using an OData service layer which hits EF. So performance is an issue -- don't want it to perform a ToList() or iterate the entire result for example.
We've tried several different things, and the closest we can get is creating an anonymous type like such:
var results = from p in context.Parents
from c in p.Children
select new { Parent = p, Child = c }
But this isn't really what we're looking for. It creates an anonymous type of parent and child, not parent with child. So we can't return an IEnumerable<Parent> any longer, but rather an IEnumerable<anonymous>. The anonymous type isn't working with our OData service layer.
Also tried with SelectMany and got 3 results, but all of Children which again isn't quite what we need:
context.Parents.SelectMany(p => p.Children)
Is what we're trying to do possible? With the sample data provided, we'd want 3 rows returned -- representing a List each with a single Child. When normally it returns 1 Parent with 3 Children, we want the Parent returned 3 times with a single child each.
Your requirements don't make any sense, the idea behind how EF and LINQ work is not those repetitive info like SQL does. But you know them better and we don't know the whole picture, so I will try to answer your question hoping I understood it correctly.
If like you said, your problem is that IEnumerable<anonymous> doesn't work with your OData service layer, then create a class for the relationship:
public class ParentChild {
public Parent Parent { get; set; }
public Child Child { get; set; }
}
And then you can use in in your LINQ query:
var results = from p in context.Parents
from c in p.Children
select new ParentChild { Parent = p, Child = c }

How to map NHibernate variable table reference?

Having a table called ChildTable with 2 columns SourceTable and SourceId and some other tables ParentTable1, ParentTable2, etc.
The Id found in SourceId can be used to join to a parent table when the SourceTable has a value associated with that table (1 -> ParentTable1, 2 -> ParentTable2). For example, to get all the ChildTable rows which are associated to rows in ParentTable1 it would be achieved with this query:
select *
from ChildTable ct
join ParentTable1 pt1
on ct.SourceTable = 1 and ct.SourceId = pt1.Id
I would like to map those 2 ChildTable columns as 1 property per parent table: Parent1, Parent2,... so 1 of them would be not null and the rest of the parent properties would be null:
public class ChildClass
{
public Parent1Class Parent1 { get; set; }
public Parent2Class Parent2 { get; set; }
public Parent3Class Parent3 { get; set; }
.
.
.
}
Question is: how to write the mapping for this case (mapping by code if possible)?
Note: This is for mapping existing tables, refactoring the table schema is not a solution yet (but suggestions are welcome).
Update
For the purpose of querying it seems to be enough to map a ChildClass property Parent1 with:
ManyToOne(property => property.Parent1, map => map.Formula("(select pt1.Id from dbo.ParentTable1 pt1 where SourceTable = 1 and pt1.Id = SourceId)"));
and the Children collection of Parent1Class with:
mapper.Where("SourceTable = 1");
For update/insert it is probably achievable using accessors, will post an update later.
Why don't you use Any?
Class:
public class ChildClass
{
public virtual ParentBase Parent { get; set; }
// beware of proxies when casting... this may not work like this
public Parent1Class Parent1 { get { return Parent as Parent1Class; } }
public Parent2Class Parent2 { get { return Parent as Parent2Class; } }
.
.
.
}
Mapping:
Any(x => x.Parent, typeof(int), m =>
{
m.IdType<int>();
m.MetaType<int>();
m.MetaValue(1, typeof(Parent1));
m.MetaValue(2, typeof(Parent2));
m.Columns(
id => id.Name("SourceId"),
classRef => classRef.Name("SourceTable"));
});
There is also many-to-any, which maps collections of any types into a relation table.
When using it in a query, you can check the .class, or use a subquery:
HQL:
select *
from ChildTable ct join Parent
where pt1.class = Parent1
or
select *
from ChildTable ct
Where ct.Parent in (from Parant2 p where p.Property = 'Hugo')

Categories