How to order Nested Collections in Linq and EF - c#

i would like to make a treelistview for my Data.
Tree should look like this
Accounts
-> Providers
-> Accounts
public sealed class AccountRoot
{
public AccountRoot()
{
Providers = new Collection<Hoster>();
}
public long AccountRootId { get; set; }
public ICollection<Hoster> Providers { get; set; }
}
public sealed class Hoster
{
public Hoster()
{
Accounts = new Collection<Account>();
}
[Key]
public long HosterId { get; set; }
public long AccountRootId { get; set; }
public string Name { get; set; }
public ICollection<Account> Accounts { get; set; }
}
public sealed class Account
{
[Key]
public long AccountId { get; set; }
public long HosterId { get; set; }
public Hoster Hoster { get; set; }
public string Name { get; set; }
}
I would like to order my query.
should be sth like
Accounts
Providers A-Z
Accounts A-Z
what i got until now is..
var query = _entity.AccountRoot.Local
.Select(x => new AccountRoot()
{
AccountRootId = x.AccountRootId,
Providers = x.Providers.OrderBy(y => y.Name).ToList()
}).ToList();
What is missing is the orderby for the next nested collection.
Thank you for your help ! :-)

It can be a bit different approaches depending on if you already have a result set, and want to just sort it in code, or if you want to construct IQueryable<> for EF which will be successfully compiled to SQL and executed with actual sorting in database.
First, assume you already have the collection in code. In this case, you have object AccountRoot, which contains collection of Providers, each of which has collection of Accounts. Obviously, you cannot return the same objects, as you need to reorder collection properties, so all you need is to just construct new ones. I would just sort the collections, but you could construct completely new entities, if you need:
var query = ...
.Select(x => new AccountRoot
{
// add copy properties here
// ....
Providers = x.Providers
.Select(y =>
{
// Here we can construct completely new entity,
// with copying all properties one by one,
// or just reorder existing collection as I do here
var result = y;
result.Accounts = y.Accounts.OrderBy(z => z.Name).ToArray();
return result;
})
.OrderBy(y => y.Name)
.ToArray()
})
.ToArray();
Second case, if you need to get it directly from SQL, is a bit different, as you cannot use all that var result = ...; ... return result stuff in lambda - it won't compile to SQL. But idea is the same - you need to construct projection from data sets. It should be something like this:
var query = ...
.Select(x => new AccountRoot
{
AccountRootId = x.AccountRootId,
// Other properties to copy
// ...
Providers = x.Providers
.Select(y => new Hoster
{
HosterId = y.HosterId,
// Other properties to copy
// ...
Accounts = y.Accounts.OrderBy(z => z.Name).ToArray(),
})
.OrderBy(y => y.Name)
.ToArray()
})
.ToArray();

Related

C# Automapper how to create a list with nested mapping?

I am trying to map into the list, which supposes to have two other lists inside. Is it doable? I looked at the documentation but couldn't find what I needed unless I misunderstood something.
CompanyActivityReport.cs
public int OrganisationID { get; set; }
public string OrganisationName { get; set; }
public Nullable<int> OrganisationSubTypeID { get; set; }
public CompanyActivityReportTask ReportTask{get; set;}
public CompanyActivityReportNote ReportNotes{get; set;}
My mapping:
var config = new MapperConfiguration(c =>
{
c.CreateMap<OrganisationMain,CompanyActivityReport>();
c.CreateMap<TaskMain, CompanyActivityReportTask>();
c.CreateMap<NoteMain, CompanyActivityReportNote>();
});
var mapper = new Mapper(config);
List<CompanyActivityReport> TestList = mapper.Map<List<CompanyActivityReport>>(OrganisationMainsList).ToList();
Guess you problably need to map each item individually like:
List<CompanyActivityReport> TestList = OrganisationMainsList.Select(x => mapper.Map<CompanyActivityReport>(x))
Edit: Select return a Enumerable so if you wish to mantain the behaviour a .ToList() is required:
List<CompanyActivityReport> TestList = OrganisationMainsList.Select(x => mapper.Map<CompanyActivityReport>(x)).ToList()

Using LINQ to query four nested entities. - Include Where condition

I have four classes Offer, Section, Field, and Option:
Where the offer has sections and every section has some fields and each field has some options as shown:
public class Offer
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Section> Sections { get; set; }
}
public class Section
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Field> Fields { get; set; }
}
public class Field
{
public int Id { get; set; }
public string Type { get; set; } //[question, group]
public ICollection<Option> Options { get; set; }
}
public class Option
{
public int Id { get; set; }
public string Name { get; set; }
}
I tried to get the offer by id including the nested entities and this code works perfectly:
var offer = _context.Offers
.Include(o => o.Sections
.Select(s => s.Fields
.Select(f => f.Options)))
.FirstOrDefault(o => o.Id == offerId);
The problem is when I try to filter the Fields by 'type' like this:
var offer = _context.Offers
.Include(o => o.Sections
.Select(s => s.Fields.Where(f => f.Type == "question")
.Select(f => f.Options)))
.FirstOrDefault(o => o.Id == offerId);
and I get this error:
The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.
Parameter name: path
I've reviewed lots of questions and still cannot achieve that :(
Linq: query with three nested levels
EF LINQ include nested entities [duplicate]
Using LINQ to query three entitites. - Include path expression must refer to a navigation property defined on the type
Include() is used for Eager loading. It is the process whereby a query for one type of entity also loads related entities as part of the query, so that we don't need to execute a separate query for related entities. Where() is currently not supported inside Include.
If you want to just filter the result you can do something like this :
var offer = _context.Offers
.Include(o => o.Sections
.Select(s => s.Fields
.Select(f => f.Options)))
.FirstOrDefault(o => o.Id == offerId);
var filtered_offer =
new Offer
{
Sections = offer.Sections.Select(S => new Section
{
Id = S.Id,
Name = S.Name,
Fields = S.Fields.Where(f => f.Type == "question").ToList()
}).ToList(),
Id = offer.Id,
Name = offer.Name
};

LINQ Filter Nth Level Nested list

I have the following hierarchy in my project :
Activity
Task
Step
Responses
This means an activity has many tasks, which in turn has many steps , a step has many responses.
Here are my POCO classes:
public class Activity
{
public virtual ICollection<Task> Tasks { get; set; }
}
public class Task
{
public virtual ICollection<Step> Steps{ get; set; }
}
public class Step
{
public virtual int DisplayOrder { get; set; }
public virtual ICollection<Response> Responses{ get; set; }
}
public class Response
{
public virtual int UserId { get; set; }
public virtual int ResponseText{ get; set; }
}
Now, I need to return a List<Activity> which is sorted by ActivityId and has Steps ordered by DisplayOrder and also a Responses which only belong to a given UserId.
Here's what I have tried:
Activities.ToList()
.ForEach((activity) => activity.Tasks.ToList()
.ForEach((task) => task.Steps = task.Steps.OrderBy(s => s.DisplayOrder).ToList()
.ForEach((step) => step.Responses = step.Responses.Where(r => r.UserId == RequestorUserId)))
));
Which is giving me an error:
Cannot implicitly convert type 'void' to ICollection<Step>
The ForEach extension method doesn't return anything and you are trying to set the result to task.Steps. You can achieve the desired result by using the Select method to alter the objects in line
Activities.ToList()
.ForEach((activity) => activity.Tasks.ToList()
.ForEach((task) => task.Steps = task.Steps.OrderBy(s => s.DisplayOrder).ToList()
.Select((step) =>
{
step.Responses = step.Responses.Where(r => r.UserId == 1).ToList();
return step;
}).ToList()));
Also It might be worth changing your types if possible to IEnumerable rather than ICollection as you then won't need all of the ToList() calls.
If you need to assign the result then you'll need to replace the first ForEach as well - try something like:
var a = Activities.ToList()
.Select((activity) =>
{
activity.Tasks.ToList()
.ForEach((task) => task.Steps = task.Steps.OrderBy(s => s.DisplayOrder).ToList()
.Select((step) =>
{
step.Responses = step.Responses.Where(r => r.UserId == 1).ToList();
return step;
}).ToList());
return activity;
});

How to query multiple inherited sub-classes in a single query in Entity Framework

I have a big problem of querying diverse types of inherited subentities in a single query in Entity Framework. My essential aim is providing all of my data model structure in a single JSON string by eager loading. And the tricky point is "the inherited subclasses may contain another inherited subclass". The example seen below will clearly explain the situation.
Assume that I have a simple class structure like this:
public class Teacher
{
public int id { get; set; }
public string fullname{ get; set; }
//navigation properties
public virtual HashSet<Course> courses{ get; set; }
}
public class Course
{
public int id { get; set; }
public string coursename{ get; set; }
//foreign keys
public int TeacherId{ get; set; }
//navigation properties
public virtual Teacher teacher{ get; set; }
public virtual HashSet<Course> prerequisites{ get; set; }
}
Course has some subclasses GradedCourse and UngradedCourse
B1 or B2 may have a list of subentities consists of entities of types B1 or B2.
public class GradedCourse : Course
{
public string gradeType{ get; set; }
}
public class UngradedCourse: Course
{
public string successMetric { get; set; }
}
Now by this structure I want to provide a JSON structure from my WEBApi yielding list of Teacher objects including both GradedCourse and UngradedCourse with their subentities and specific fields. I have a query like this but it does not compile
db.Teachers.Select(t => new
{
t.id,
t.fullName
courses = t.courses.OfType<GradedCourses>()
.Select(g => new
{
id = g.id,
coursename = g.coursename,
prerequisites = g.prerequisites, // this is the list of other subentities
gradeType = g.gradeType
}
).Concat(t.courses.OfType<UngradedCourses>()
.Select(u => new
{
id = u.id,
coursename = u.coursename,
prerequisites = g.prerequisites, // this is the list of other subentities
successMetric= u.successMetric // subclass specific field
}
)
)
}
)
The problem is concating two different types of objects (they have different fields which is not possible for SQL UNION)
How can I handle this? Any help will open my mind. Thanks in advance for the professionals :)
It does not compile because the element type of 2 sets is not the same. So you just need to make them the same before being able to do anything:
db.Teachers.Select(t => new
{
t.id,
t.fullName
courses = t.courses.OfType<GradedCourses>()
.Select(g => new
{
id = g.id,
coursename = g.coursename,
prerequisites = g.prerequisites, // this is the list of other subentities
isGradedCourse = true,
gradeTypeOrMetric = g.gradeType
}).Concat(t.courses.OfType<UngradedCourses>()
.Select(u => new
{
id = u.id,
coursename = u.coursename,
prerequisites = g.prerequisites, // this is the list of other subentities
isGradedCourse = false,
gradeTypeOrMetric= u.successMetric // subclass specific field
}))
//finally select what of your choice
.Select(e => new {
id = e.id,
coursename = e.coursename,
prerequisites = e.prerequisites,
gradeType = e.isGradedCourse ? e.gradeTypeOrMetric : "",
successMetric = e.isGradedCourse ? "" : e.gradeTypeOrMetric
})
});
You still benefit the query being executed on server side without having to pull all teachers to local (and then being able to cast the entities - which is not supported in LinqToEntity query).

Lambda expression - select single object with a IEnumerable<> property

Is it possible to select a single object and populate a containing IEnumerable property with a single Lambda expression?
Something like this:
var someViewModel = _repository.Table.Where(x => x.Id == someId)
.Select(new ListViewModel(){
GroupId = x.Group.Id,
GroupTitle = x.Group.Title
List = ?? // Select new SubViewModel and add it to IEnumerable<SubViewModel>
})
The result I'm after is a new object (ListViewModel in this case) that contains 3 properties. "List" being a collection of newly selected objects.
Is this possible? Am I coming at this from the wrong angle?
Thanks!
Update:
Let me try again :) Keep in mind that my naming is fictional here. Given the following two classes I would like to construct a DB query using a Lambda expression which creates a single "ListViewModel" that contains a collection of "SubViewModel". Does this help clarify?
public class SubViewModel
{
public int Id { get; set; }
public string Title { get; set; }
}
public class ListViewModel
{
public int GroupId { get; set; }
public string GroupTitle { get; set; }
public IEnumerable<SubViewModel> List { get; set; }
}
I'm not sure if I understand your question correctly but here is what I am thinking, you need to create a new IEnumberable and add the item to that collection.
var someViewModel = _repository.Table.Where(x => x.Id == someId)
.Select(new ListViewModel()
{
GroupId = x.Group.Id,
GroupTitle = x.Group.Title
List = new List<SubViewModel> { new SubViewModel(x) }
});

Categories