Query that Groups Parent, then Child, when fetching from IQueryable<GrandChild> - linq

Lets imagine that below is our data model. I want to query the Toys but have the results returned such that I can do the following psuedo code.
foreach (var parent in parents)
{
Console.WriteLine(parent.Name);
foreach (var child in parent.Children)
{
Console.WriteLine(child.Name);
foreach (var toy in child.Toys)
{
Console.WriteLine(toy.Name);
}
}
}
The data model:
public class Parent
{
public Guid ParentId { get; set; }
public String Name { get; set; }
public ICollection<Child> Children { get; set; }
}
public class Child
{
public Guid ChildId { get; set; }
public Guid ParentId { get; set; }
public Parent Parent { get; set; }
public String Name { get; set; }
public ICollection<Toy> Toys { get; set; }
}
public class Toy
{
public Guid ToyId { get; set; }
public Guid ChildId { get; set; }
public Child Child { get; set; }
public String Name { get; set; }
public bool IsCool { get; set; }
}
I've tried doing this with grouping but when I try to iterate a grouping all that I see is Key which doesn't have any properties on it so I can't get the Parent.Name or the Child.Name.
Thanks in advance.

You can do:
var query = from p in parents
from c in p.Children
from t in c.Toys
select new { Parent = p.Name, Child = c.Name, Toy = t.Name }
foreach (var a in query)
{
// write values.
}
In effect, this uses SelectMany.
Edit
After your comment, I think what you're looking for is not a grouping. If you've got a Parent object in fact its Children are already grouped inside of it, because each parent has its own collection. But maybe you're looking for a way to populate the collections? That would be done by
var query = from p in parents.Include(x => x.Children.Select(c => c.Toys));

With LazyLoadingEnabled set to be false, you will be able to do this by using one linq query to load all related objects.
Please note, this may cause some performance issue if you have great amount of related data in Children or Toys table.
context.ContextOptions.LazyLoadingEnabled = false;
var parents = context.Parents.Select(x=>x).ToList();
foreach(var parent in parents) {
Console.WriteLine(parent.Name);
foreach(var child in parent.Children) {
Console.WriteLine(child.Name);
foreach(var toy in child.Toys) {
Console.WriteLine(toy.Name);
}
}
}

Related

c# AutoMapper ProjectTo selects all nested object properties

Let's take such classes:
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
public string ExtraProp { get; set; }
}
public class Parent
{
public int Id { get; set; }
public string Text { get; set; }
public Child Child { get; set; }
public string ParentExtraProp { get; set; }
}
public class ChildVo
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ParentVo
{
public int Id { get; set; }
public string Text { get; set; }
public ChildVo Child { get; set; }
}
Automapper mapping:
Mapper.CreateMap<Child, ChildVo>();
//.ForSourceMember(x => x.ExtraProp, o => o.Ignore()); //does not help
//.IgnoreAllNonExisting(); //does not help
Mapper.CreateMap<Parent, ParentVo>();
and query in Linq to Nhibernate:
var test = Session.Query<Parent>()
.Where(x => x.Id == myId)
.ProjectTo<ParentVo>()
.ToList();
ProjectTo selects only columns (properties) which are defined in ParentVo (not all properties from Parent class) - that's great. But is selects all columns (properties) from my Child class, despite the fact that they are not defined in ChildVo.
Why does Automapper ignore my nested property mapping? Is it possible to use all defined mappings during projection?
While I'm not familiar with nhibernate, the same test you've performed here works fine in Entity Framework. In EF you can see the query which has been generated before executing it - try doing this and seeing if it shows the additional column.
public class TestContext : DbContext {
public DbSet<Parent> Parents { get; set; }
}
var query = testContext.Parents.ProjectTo<ParentVo>();
Console.WriteLine(query.ToString());
This produces the following output (note no ExtraProp):
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Text] AS [Text],
CASE WHEN ([Extent2].[Id] IS NOT NULL) THEN 1 END AS [C1],
[Extent1].[Child_Id] AS [Child_Id],
[Extent2].[Name] AS [Name]
FROM [dbo].[Parents] AS [Extent1]
LEFT OUTER JOIN [dbo].[Children] AS [Extent2] ON [Extent1].[Child_Id] = [Extent2].[Id]
This makes me think that the problem is not AutoMapper specifically, but without seeing the generated query it's hard to tell.

Retrieving information from derived child object collections using LINQ

I have been trying to get a list of all Workflows that have Offices contained in a certain List by Office Id. I can easily get all of the Workflows that have SingleWorkflowSteps because they have only one Office, but have been unable to understand how I would successfully get those contained in a MultiWorkflowStep. All workflow steps have either a SingleWorkflowStep or a MultiWorkflowStep that contains two or more SingleWorkflowSteps. At the time I designed this, it seemed like a logical way to do this but atlas my LINQ-fu is not as good as I thought it was. Can someone please point me in the right directions. Code listed below:
var OfficesToFind = new List<int> (new int[] { 1,3,5,7,9,10,11,12} );
public class Workflow
{
public Workflow()
{
WorkflowSteps = new List<WorkflowStepBase>();
}
public int Id { get; set; }
public virtual ICollection<WorkflowStepBase> WorkflowSteps { get; set; }
}
public abstract class WorkflowStepBase
{
public int Id { get; set; }
public int StatusId { get; set; }
public virtual Workflow Workflow { get; set; }
public virtual Status Status { get; set; }
}
public class MultiWorkflowStep : WorkflowStepBase
{
public MultiWorkflowStep()
{
ChildSteps = new List<SingleWorkflowStep>();
}
public virtual ICollection<SingleWorkflowStep> ChildSteps { get; set; }
}
public class SingleWorkflowStep : WorkflowStepBase
{
public int? ParentStepId { get; set; }
public int OfficeId { get; set; }
public virtual MultiWorkflowStep ParentStep { get; set; }
public virtual Office Office { get; set; }
}
public class Office
{
public int Id { get; set; }
public string Name { get; set; }
}
public class WorkflowService : IWorkflowService<Workflow>
{
private readonly IRepository<Workflow> _workflowService;
private readonly IRepository<SingleWorkflowStep> _singleStepService;
private readonly IRepository<MultiWorkflowStep> _multiStepService;
public WorkflowService(IUnitOfWork uow)
{
_workflowService = uow.GetRepository<Workflow>();
_singleStepService = uow.GetRepository<SingleWorkflowStep>();
_multiStepSercice = uow.GetRepository<MultiWorkflowStep>();
}
// ~ ------- Other CRUD methods here -------- ~
public IEnumerable<Workflow> GetWorkflowFilter(List<int> statuses, List<int> offices...)
{
var query = _workflowService.GetIQueryable(); // returns an IQueryable of dbset
if(statuses.Any())
{
query = query.Where(q => statuses.Contains(q.StatusId));
}
if(offices.Any())
{
// Get all active single steps and the ones that contain the offices
singleSteps = _singleStepService
.Where(s => s.StatusId == (int)Enumerations.StepStatus.ACTIVE)
.Where(s => offices.Contains(s.OfficeId));
// Get all of the parent Workflows for the singleSteps
var workflows = singleSteps.Select(w => w.Workflow);
// Update the query with the limited scope
query = query.Where(q => q.Workflow.Contains(q));
}
return query.ToList();
}
}
OK, after a good night sleep, being all bright-eyed and bushy-tailed, I figured out my own problem. First the updated code was all wrong. Because each derived WorkflowStep has access to the Workflow and each MultiWorkflowStep contains a list of SingleWorkflowSteps - when I get the list of all SingleWorkflowSteps (which would include all from MultiWorkflowStep(s)), I simply needed to get a list of all of the parent Workflows of the SingleWorkflowSteps. Next I updated my query to limit the Workflows that were contained in the new Workflow list and here is the correct code for the GetWorkflowFilter method:
...
if(offices.Any())
{
// Get all active single steps and the ones that contain the offices
singleSteps = _singleStepService.Where(s => s.StatusId == (int)Enumerations.StepStatus.ACTIVE).Where(s => offices.Contains(s.OfficeId));
// Get all of the parent Workflows for the singleSteps
var workflows = singleSteps.Select(w => w.Workflow);
// Update the query with the limited scope
query = query.Where(q => q.Workflow.Contains(q));
}
return query.ToList();
}

Entity Framework: Custom proxies for POCO

I am working with the EF6 and I am a big fan of the dynamic proxies, which enables lazy loading and change tracking. Anyway I am not happy, that the lazy loading is triggered once the property is accessed instead of loading the data, when the enumerator or the count property is called first. Therefore I tried to diesable the proxys and replace them by custom proxies. It was an easy thing to use a custom object context and overload the CreateObject method. Unfortantly the ObjectMaterialized event cannot replace the entity and I am not able to replace an entity from a query. The creation of the object lies deep in internal classes of the framework.
Has anybody an idea how to use custom proxies? Or how I am able to replace the entities materialized in an object query?
You should .Include the properties you want to fetch so that you avoid an N+1 query problem.
public class User
{
public int Id { get; set; }
public string Name { get; set ;}
public virtual ICollection<Post> Posts { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
public class Post
{
public int Id { get; set; }
public string Title { get; set ; }
public int AuthorId { get; set; }
public virtual User Author { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
public class Comment
{
public int Id { get; set; }
public string Note { get; set ;}
public int PostId { get; set; }
public virtual Post Post { get; set; }
public int AuthorId { get; set; }
public virtual User Author { get; set; }
}
public class BlogContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Post> Posts { get; set; }
public DbSet<Comment> Comments { get; set; }
}
Then this is BAD in that it'll do tons of queries:
using (var db = new BlogContext())
{
var user = db.Users.Single(u => u.Id=5)); // +1 query
foreach (var post in user.Posts) // N queries
{
var message = String.Format("{0} wrote {1}", user.Name, post.Title);
Console.WriteLine(message);
foreach (var comment in post.Comments) // N * M queries!
{
// and that .Author make N * M MORE!
var message = String.Format("\t{0} commented {1}", comment.Author.Name, comment.Note);
Console.WriteLine(message);
}
}
}
And this is GOOD in that it'll do one query:
using (var db = new BlogContext())
{
var user = db.Users
.Single(u => u.Id=5))
.Include(u => u.Posts) // eliminates the N post queries
.Include(u => u.Posts.Comments) // eliminates the M comment queries
.Include(u => u.Posts.Comments.Author); // eliminates the M comment author queries
foreach (var post in user.Posts) // N queries
{
var message = String.Format("{0} wrote {1}", user.Name, post.Title);
Console.WriteLine(message);
foreach (var comment in post.Comments) // N * M queries!
{
// and that .Author make N * M MORE!
var message = String.Format("\t{0} commented {1}", comment.Author.Name, comment.Note);
Console.WriteLine(message);
}
}
}

RavenDB SelectMany not supported

I am trying to find one or more documents in RavenDB based on the values of a child collection.
I have the following classes
public class GoldenDocument
{
public GoldenDocument()
{
LinkedDocuments = new List<LinkedDocument>();
MergeMatchFields = new List<MergeMatchField>();
}
public string Id { get; set; }
public Guid SourceRowId { get; set; }
public List<MergeMatchField> MergeMatchFields { get; set; }
public List<LinkedDocument> LinkedDocuments { get; set; }
}
And the class that is in the collection MergeMatchFields
public class MergeMatchField
{
public string Id { get; set; }
public Guid OriginId { get; set; }
public string Name { get; set; }
public MatchType MatchType { get; set; }
public double MatchPerc { get; set; }
public string Value { get; set; }
}
In a List<MergeFields> mergeFields collection I have values that is not stored in RavenDB yet. Values are compared to values in a RavenDB document for find if it is a possible match by executing the following query:
using (var session = documentStore.OpenSession())
{
var docs = from gd in session.Query<GoldenDocument>()
from mf in gd.MergeMatchFields
from tf in mergeFields
where mf.Name == tf.Name
&& JaroWinklerCalculator.jaroWinkler(mf.Value, tf.Value) > .90d
&& !string.IsNullOrEmpty(mf.Value)
select gd;
}
I understand that ravenDB does not support SelectMany() so how would I go about getting the results from the Document store?
Create an index for this that would output the values you want to query on.
Note that you can't just execute arbitrary code the way you do here: JaroWinklerCalculator.jaroWinkler(mf.Value, tf.Value) > .90d
But you can use fuzzy queries, and they will do the same.

Present multiple IEnumberables and single value properties in single webgrid

I have an Inventory Class that contains not only its own fields but several reference IDs to other classes.
public class Inventory {
public int Id { get; set; }
public string RtNum { get; set; }
public string AcntNum { get; set; }
public string CardNum { get; set; }
public string Num { get; set; }
[Range(1,3)]
public int Type { get; set; }
public int CompanyId { get; set; }
public int BranchId { get; set; }
public int PersonId { get; set; } }
In my action I generate several IEnumerable lists of the relevant fields from the other classes. I also have several non-list values I want to pass to the View. I know how to create a ViewModel to pass everything to the webgrid but have no way of iterating through the lists. I also know how to AutoMap an index to one list, see How to display row number in MVC WebGrid.
How would you combine the two so that you could use the index to iterate through multiple lists?
Update #1 (more detail)
public class Company {
public int Id { get; set; }
public string Name { get; set; } }
public class Branch {
public int Id { get; set; }
public string Name { get; set; } }
public class Person {
public int Id { get; set; }
public string Name { get; set; } }
public class MyViewModel {
public int PageNumber { get; set; }
public int TotalRows { get; set; }
public int PageSize { get; set; }
public IEnumerable<Inventory> Inventories { get; set; }
public int Index { get; set; }
public IEnumerable<string> CmpNm { get; set; }
public IEnumerable<string> BrnNm { get; set; }
public IEnumerable<string> PrnNm { get; set; } }
Controller
public class InventoryController : Controller
{ // I have a paged gird who’s code is not relevant to this discussion but a pagenumber,
// pagesize and totalrows will be generated
private ProjectContext _db = new ProjectContext();
public ActionResult Index() {
IEnumerable<Inventory> inventories = _db.Inventories;
List<string> cmpNm = new List<string>; List<string> brnNm = new List<string>; List<string> prnNm = new List<string>;
foreach (var item in inventories) { string x1 = "";
Company cmps = _db. Company.SingleOrDefault(i => i.Id == item.CompanyId); if (cmps!= null)
{ x1 = cmps.Name; } cmpNm.Add(x1); x1 = "";
Branch brns = _db. Branch.SingleOrDefault(i => i.Id == item. Branch Id); if (brns!= null) { x1 = brns.Name; } brnNm.Add(x1); x1 = "";
Person pers = _db.Persons.SingleOrDefault(i => i.Id == item. PersonId);
if (pers!= null) { x1 = pers.Name; } prnNm.Add(x1);
// the MyViewModel now needs to populated with all its properties and generate an index
// something along the line of
new MyViewModel { PageNumber= pagenumber, PageSize= pagesize, TotalRows=Totalrows, Inventories = inventories; CmpNm=cmpNm, BrnNm=brnNm, PrnNm=prnNm}
View (How to create the Index is the problem)
#model.Project.ViewModels.MyViewModel
#{ var grid = new WebGrid(Model.Inventories, Model.TotalRows, rowsPerPage: Model.PageSize); }
#grid.GetHtml( columns: grid.Columns(
Grid.Column(“PrnNm”, header: "Person", format: #Model.PrnNm.ElementAt(Index))
Grid.Column(“BrnNm”, header: "Branch", format: #Model.BrnNm.ElementAt(Index))
Grid.Column(“CmpNm”, header: "Company", format: #Model.CmpNm.ElementAt(Index))
grid.Column("RtNum", header: "Route"),
grid.Column("AcntNum", header: "Account"),
grid.Column("CardNum", header: "Card")
… ) )
What the grid should look like is self-evident.
It's pretty unclear what is your goal. But no matter what it is I would recommend you to define a real view model reflecting the requirements of your view and containing only the information you are interested in seeing in this grid:
public class InventoryViewModel
{
public int Id { get; set; }
public string PersonName { get; set; }
public string BranchName { get; set; }
public string CompanyName { get; set; }
public string RouteNumber { get; set; }
public string AccountNumber { get; set; }
public string CardNumber { get; set; }
}
Now you could have the main view model:
public class MyViewModel
{
public int PageNumber { get; set; }
public int TotalRows { get; set; }
public IEnumerable<InventoryViewModel> Inventories { get; set; }
}
Alright, the view is now obvious:
#model MyViewModel
#{
var grid = new WebGrid(
Model.Inventories,
rowsPerPage: Model.PageSize
);
}
#grid.GetHtml(
columns: grid.Columns(
grid.Column("Id", header: "Inventory id"),
grid.Column("PersonName", header: "Person"),
grid.Column("BranchName", header: "Branch"),
grid.Column("CompanyName", header: "Company"),
grid.Column("RouteNumber", header: "Route"),
grid.Column("AccountNumber", header: "Account"),
grid.Column("CardNumber", header: "Card")
)
)
Now all that's left is build this view model in your controller. Since I don't know what you are trying to achieve here, whether you need an inner join or a left outer join on those columns, I will take as an example here a left outer join:
public ActionResult Index()
{
var inventories =
from inventory in _db.Inventories
join person in _db.Persons on inventory.PersonId equals person.Id into outerPerson
join company in _db.Companies on inventory.CompanyId equals company.Id into outerCompany
join branch in _db.Branch on inventory.BranchId equals branch.Id into outerBranch
from p in outerPerson.DefaultIfEmpty()
from c in outerCompany.DefaultIfEmpty()
from b in outerBranch.DefaultIfEmpty()
select new InventoryViewModel
{
PersonName = (p == null) ? string.Empty : p.Name,
CompanyName = (c == null) ? string.Empty : c.Name,
BranchName = (b == null) ? string.Empty : b.Name,
Id = inventory.Id,
AccountNumber = inventory.AcntNum,
CardNumber = inventory.CardNum,
RouteNumber = inventory.RtNum
};
var model = new MyViewModel
{
PageSize = 5,
// TODO: paging
Inventories = inventories.ToList()
};
return View(model);
}
And that's pretty much it. Of course in this example I am leaving the pagination of the Inventories collection for you. It should be pretty trivial now to .Skip() and .Take() the number of records you need.
As you can see ASP.NET MVC is extremely simple. You define a view model to reflect the exact requirements of what you need to show in the view and then populate this view model in the controller. Most people avoid view models because they fail to populate them, probably due to lack of knowledge of the underlying data access technology they are using. As you can see in this example the difficulty doesn't lie in ASP.NET MVC at all. It lies in the LINQ query. But LINQ has strictly nothing to do with MVC. It is something that should be learned apart from MVC. When you are doing MVC always think in terms of view models and what information you need to present to the user. Don't think in terms of what you have in your database or wherever this information should come from.

Resources