How to include all levels of a object in EF? [duplicate] - c#

This question already has an answer here:
loading a full hierarchy from a self referencing table with EntityFramework.Core
(1 answer)
Closed 2 years ago.
I have an object that defines a network structure, I want to send over all children of that object and the children of the children and so on.
Right now I have this:
var Data = await _context.Scans
.Include(c => c.networkDevices)
.ThenInclude(d => d.ports)
.ThenInclude(p => p.Service)
.Include(c => c.root)
.ThenInclude(d => d.children).ThenInclude(p => p.children).ThenInclude(c => c.children)
.ToListAsync();
return Data;
This code will get most levels but if a network has many different layers it won't get all of them.
How can i make it so that all layers get included.

I think there is no built in way to load "all" layers because in theory it'd be possible that you have cyclic references.
This snippet will create a query for the number of layers
namespace System.Data.Entity
{
using Linq;
using Linq.Expressions;
using Text;
public static class QueryableExtensions
{
public static IQueryable<TEntity> Include<TEntity>(this IQueryable<TEntity> source,
int levelIndex, Expression<Func<TEntity, TEntity>> expression)
{
if (levelIndex < 0)
throw new ArgumentOutOfRangeException(nameof(levelIndex));
var member = (MemberExpression)expression.Body;
var property = member.Member.Name;
var sb = new StringBuilder();
for (int i = 0; i < levelIndex; i++)
{
if (i > 0)
sb.Append(Type.Delimiter);
sb.Append(property);
}
return source.Include(sb.ToString());
}
}
}
Usage:
var children = await DbContext.Roots
.Include(3, a => a.Children)
.SingleOrDefaultAsync(a => a.Id == 5);

I think the easiest way to accomplish this is by lazy loading,
take a look at this post

Related

Linq - Order by deep property from string

I need to order a collection of data dynamically. I have this working at the top level:
//where prop is a string passed in, ex: "ShoeSize"
_clowns
.AsEnumerable()
.OrderBy(x => x.GetType().GetProperty(prop)?.GetValue(x))
.Select(x => x.Id)
.ToList();
And that works so long as I only need to order by some property of Clowns. But what if I need to order Clowns by a property of their Car? I think I'm close, but can't clear the gap:
//for prop = "Car.ClownCapcity"
var queryBuilder = _clowns;
var orderProp = prop;
if (prop.Contains(".")){
string[] props = prop.Split(".");
foreach(string oneProp in props){
if (props.Last() != oneProp){
//this line is wrong for sure
queryBuilder.Include(x => x.GetType().GetProperty(oneProp));
} else {
orderProp = oneProp;
}
}
}
queryBuilder.AsEnumerable()
.OrderBy(x => x.GetType().GetProperty(orderProp)?.GetValue(x))
.Select(x => x.Id)
.ToList();
This doesn't work because I cannot build up queryBuilder, reassigning at the Include doesn't work because the return type is different. I also haven't figured out how to dynamically go deeper inside the final OrderBy.
Is there a decent way to do this in Linq, or should I go build a SQL string?
There are security concerns, those can be handled elsewhere and aren't terribly relevant to this question
Update
Progress! Got it to work two-levels deep, but only when I explicitly know it's two-levels deep. I haven't figured out the abstraction yet.
if (prop.Contains(".")){
string[] props = prop.Split(".");
string includeProp = props.FirstOrDefault();
string orderProp = props.LastOrDefault();
return _Clowns
.Include(includeProp)
.AsEnumerable()
.OrderBy(x => {
var y = x.GetType().GetProperty(formattedProp)?.GetValue(x);
return y?.GetType().GetProperty(orderProp)?.GetValue(y);
})
.Select(x => x.Id)
.ToList();
}
Answering with my own solution. Please give me a better answer if I'm way over-complicating this.
if (prop.Contains(".")){
string[] props = prop.Split(".");
string includePath = prop.remove(prop.LastIndexOf("."));
IQueryable<ClownModel> queryBuilder = _clowns.Include(includePath);
return queryBuilder.
.AsEnumerable()
.OrderBy(x => {
object y = x;
foreach (var prop in props)
{
//avoid unnecessary depth checks if a null is found
if (y == null)
{
break;
}
//if we're dealing with a collection
if (y?.GetType().Namespace == "System.Collections.Generic")
{
//gets a HashMap<ModelType>
var o = (dynamic)y;
//can't access this HashMap with [0] or .First() though?
foreach(object m in o)
{
//cheat and iterate
y = m;
break;
}
}
y = y?.GetType().GetProperty(prop)?.GetValue(y);
}
return y;
})
.Select(x => x.Id)
.ToList();
}
My initial workup was wrong, it was still only working for 2-level deep. This works for any levels, and accounts for collections of properties along the way. Intriguingly, the Include function can take a dot-notation string (super handy), but not the OrderBy.
So pass in the Include, then loop through each property level inside the OrderBy, taking the first record if it's a collection.
Bing-bam-boom, you got a baby pachyderm. Back to you, Trent.

OrderBy in Include child using EF Core [duplicate]

This question already has answers here:
Filtering on Include in EF Core
(9 answers)
Closed 2 years ago.
In my .NET Core / EF Core application I have a model with a nested list of child objects. When I retrieve an instance, I need the nested list to be ordered by one of the child's properties.
What is the correct way to sort this list so that it always returns sorted correctly from my database?
Right now I do it after loading from database:
public async Task<Parent> GetParent(int id)
{
var result = await context.Parents
.Include(p => p.Children)
.SingleOrDefaultAsync(p => p.Id == id);
result.Children = result.Children.OrderBy(c => c.Sequence).ToList();
return result;
}
Starting with Entity Framework Core 5.0, you can sort (OrderBy) and filter (Where) directly in the Include statement (with some restrictions).
See the Microsoft Documentation.
Your statement can therefore be simplified like this:
public async Task<Parent> GetParent(int id)
{
return await context.Parents
.Include(p => p.Children.OrderBy(c => c.Sequence))
.SingleOrDefaultAsync(p => p.Id == id);
}
This is a nice step forward for EF Core in my opinion.
The result you are trying to return is the ordered list of children. That's not what you want. Instead sort the children then return the parent:
public async Task<Parent> GetParent(int id)
{
var parent = context.Parents
.Include(p => p.Children)
.SingleOrDefaultAsync(p => p.Id == id);
parent.Result.Children = parent.Result.Children.OrderBy(c => c.Sequence).ToList();
return await parent;
}
var result = loadedInMemory.Result.Children.OrderBy(c => c.Sequence).ToList();
You need to add ToList() at the end

StackOverflowException using Linq(Kit) for nested data

I'm trying to build a nested query using Linq/LinqKit. In theory this seems to be easy. But I am stuck with the practical part.
In my database I have a table which has a self-reference to its parent. In my linq-query I now want to select all parents of a given element (and the parents of this one and so on).
In my code I have the following expression in partial class of MyTable:
public static Expression<Func<MyTable, IEnumerable<MyTable>>> Parents => (entity) => entity.ParentId != null ? new[]{entity.ParentEntity}.Union(Parents.Invoke(entity.ParentEntity) : new MyEntity[]{};
which should select the parent of a given entity and those parents when the ParentId is set.
The query itself (simplified):
dbContext
.MyTable
.AsExpandable()
.Where(x => x.Id == myId)
.Select(x => new
{
Parents = MyTable.Parents.Invoke(x, dbContext)
});
Running this code ends up in an StackOverflowException as the stop-condition is not hit and therefore the Parents-call is nested endlessly until the stack is full.
Any ideas how this can be done or is this not possible? Or is there an other way for fetching nested data using Linq/LinqKit within one query?
I already tried passing the context to the expression in order to create a sub-query (also not working):
public static Expression<Func<MyTable, MyContext, IEnumerable<MyTable>>> Parents => (entity, dbContext) => entity.ParentId != null ? new[]{entity.ParentEntity}.Union(Parents.Invoke(dbContext.MyTable.FirstOrDefault(x => x.Id == entity.ParentId), dbContext) : new MyEntity[]{};
As mentioned in comments, currently it's not possible to create a recursive expandable (i.e. non invokable) expression.
However, if you can limit the maximum depth, one possible solution would be to build expression like this (utilizing the EF navigation property):
Parents = new MyTable [] { x.Parent, x.Parent.Parent, x.Parent.Parent.Parent, ...}
.Where(e => e != null)
dynamically:
static Expression<Func<MyTable, IEnumerable<MyTable>>> ParentsSelector(int maxLevels)
{
var parameter = Expression.Parameter(typeof(MyTable), "x");
var parents = new Expression[maxLevels];
for (int i = 0; i < parents.Length; i++)
parents[i] = Expression.Property(i > 0 ? parents[i - 1] : parameter, "Parent");
Expression<Func<MyTable, bool>> predicate = x => x != null;
var result = Expression.Call(
typeof(Enumerable), "Where", new[] { parameter.Type },
Expression.NewArrayInit(parameter.Type, parents), predicate);
return Expression.Lambda<Func<MyTable, IEnumerable<MyTable>>>(result, parameter);
}
and use it as follows:
var parents = ParentsSelector(10);
var query = dbContext.MyTable
.AsExpandable()
.Where(x => x.Id == myId)
.Select(x => new
{
Parents = parents.Invoke(x)
});

LINQ: how to get an intersection of two sets of ints?

There must be a way to compare two sets of results while staying in LINQ. Here's my existing code that uses a HashSet to do the comparison after two separate queries:
public static void AssertDealershipsShareTransactionGatewayCredentialIds(long DealershipLocationId1,
long DealershipLocationId2)
{
using (var sqlDatabase = new SqlDatabaseConnection())
{
var DealershipCredentials1 =
sqlDatabase.Tables.DealershipLocationTransactionGateway
.Where(x => x.DealershipLocationId == DealershipLocationId1)
.Select(x => x.TransactionGatewayCredentialId);
var DealershipCredentials2 =
sqlDatabase.Tables.DealershipLocationTransactionGateway
.Where(x => x.DealershipLocationId == DealershipLocationId2)
.Select(x => x.TransactionGatewayCredentialId);
var doSetsOfCredentialsMatch = new HashSet<int>(DealershipCredentials1).SetEquals(DealershipCredentials2);
Assert.IsTrue(doSetsOfCredentialsMatch,
"The sets of TransactionGatewayCredentialIds belonging to each Dealership did not match");
}
}
Ideas? Thanks.
Easy answer (This will make 1, possibly 2 database calls, both of which only return a boolean):
if (list1.Except(list2).Any() || list2.Except(list1).Any())
{
... They did not match ...
}
Better answer (This will make 1 database call returning a boolean):
var DealershipCredentials1 =
sqlDatabase.Tables.DealershipLocationTransactionGateway
.Where(x => x.DealershipLocationId == DealershipLocationId1)
.Select(x => x.TransactionGatewayCredentialId);
var DealershipCredentials2 =
sqlDatabase.Tables.DealershipLocationTransactionGateway
.Where(x => x.DealershipLocationId == DealershipLocationId2)
.Select(x => x.TransactionGatewayCredentialId);
if (DealershipCredentials1.GroupJoin(DealershipCredential2,a=>a,b=>b,(a,b)=>!b.Any())
.Union(
DealershipCredentials2.GroupJoin(DealershipCredential1,a=>a,b=>b,(a,b)=>!b.Any())
).Any(a=>a))
{
... They did not match ...
}
The second method works by unioning a left outer join that returns a boolean indicating if any unmatching records were found with a right outer join that does the same. I haven't tested it, but in theory, it should return a simple boolean from the database.
Another approach, which is essentially the same as the first, but wrapped in a single LINQ, so it will always only make 1 database call:
if (list1.Except(list2).Union(list2.Except(list1)).Any())
{
}
And another approach:
var common=list1.Intersect(list2);
if (list1.Except(common).Union(list2.Except(common)).Any()) {}

Entity Framework, Get element from related table based on includes

I recognize that this question has been asked in various forms before, but none of them that I have read quite give me what I need.
Question: Given the model below, and only knowing the client ID, how can I get a List that consists of the Uid of each activity that the client is permitted to perform?
I start with code something like the code below, but don't know where to go from there. I assume that it has something to do with a select statement - but my ignorance of LINQ is overwhelming me :)
public List<string> GetClientAndPermittedActivities ( int clientId )
{
var permittedActivities = ReadAllRaw()
.Where(c => c.Id == clientId)
.Include("ClientType")
.Include("Role")
.Include("PermittedActivities")
.Include("Activities");
}
internal IQueryable<TE> ReadAllRaw ()
{
return base.ReadAll();
}
// READ (ALL)
internal IQueryable<T> ReadAll ()
{
return DbSet;
}
private void SetContext ( DbContext context )
{
this.Context = context;
this.DbSet = Context.Set<T>();
}
You can just 'chain' your includes together with your navigation properties.
public List<string> GetClientAndPermittedActivities(int clientId)
{
var permittedActivities =
ReadAllRaw()
.Include("ClientType.Role.PermittedActivities")
.Where(c => c.Id == clientId)
.ToList();
}
That should get you all the PermittedActivities.
EDIT
If you add
using System.Data.Entity;
to your class, you can use lambdas to create your statements.
public List<string> GetClientAndPermittedActivities(int clientId)
{
var permittedActivities =
ReadAllRaw().Include(x => c.ClientType.Role.PermittedActivities.SelectMany(pa => pa.Activities.Uid))
.Where(c => c.Id == clientId)
.ToList();
}
And with this you receive your requested Activity.Uid.
Your method can looks like this:
public List<string> GetClientAndPermittedActivities(int clientId)
{
return ReadAllRaw()
.Where(c => c.Id == clientId)
.SelectMany(
ct => ct.ClientType
.Role
.PermittedActivities,
(s, c) => c.Uid
)
.ToList();
}
Should be something like
from c in Client
where c.ClientId = clientId
from p in c.ClientType.Role.PermittedActivities
select p.Activity.Uid
As you see, you just start at Client and then follow the associations: n - 1 is represented by a dot . and 1 - n by a new from statement.
This is called query syntax, or comprehensive syntax. Most of the times this produces more succinct code than fluent syntax (Client.Where(c => ...), although there are things you can only do with the latter.

Categories

Resources