How to write a Linq that can retrieve all parent table records and total of sub-table record, I mean 'separate' into two parts - linq

Let's say I have two tables, parent table 'P' and sub-table 'S', I usually wrote the Linq like this to get what I want:
var rows = from p in db.P
join s in db.S on p.Id equals s.ParentId into subContent
where (some condition here)
select new{
Id = p.Id,
Title = p.Title
SubContentCount = subContent.Count()
}
It's very simple, but if for some reason I have to pass a parameter into this query when there has one (let's say 'key'), I have to do this (I guess :-):
var rows = from p in db.P
join s in db.S on p.Id equals s.ParentId into subContent
where (some condition here)
select p;
if(!string.IsNullOrEmpty(key)){ // I'm using C#
rows = rows.Where(q => q.Title.Contains(key))
}
And then:
var list = rows.Select(q => new ()
{
Id = q.Id,
Title = q.Title,
subCount = ???.Count()
});
Is that passable to do Linq like this? if so, how?
Thanks for any kind help!

You could create a method that receives a Func<Table, bool>as parameter and use it to filter your dataset:
public static void Main(string[] args)
{
var rows = new List<Table>
{
new Table { Id = 1, Title = "A", SubContent = new [] { "A1" } },
new Table { Id = 2, Title = "B", SubContent = new [] { "B1", "B2" } },
new Table { Id = 3, Title = "C", SubContent = new [] { "C1", "C2", "C3" } },
};
var title = "C";
foreach (var item in Filter(rows, table =>
String.IsNullOrEmpty(title) || table.Title == title))
{
Console.WriteLine(
"Title={0}, SubContent.Length={1}",
item.Title, item.SubContent.Length);
}
}
public static List<Table> Filter(List<Table> original, Func<Table, bool> filter)
{
return original.Where(filter).ToList();
}
public class Table
{
public int Id { get; set; }
public string Title { get; set; }
public string[] SubContent { get; set; }
}

Why not include the filter in the where clause?
where string.IsNullOrEmpty(key) || p.Title.Contains(key)
Quick example in the interactive console:
public class Parent { public int Id {get; set;} public string Title {get; set;} }
public class SubTable { public int Id {get; set;} public int ParentId {get; set;} }
public class Result { public int Id {get; set;} public string Title {get; set;} public int SubContentCount {get; set;} }
var p1 = new Parent() { Id = 1, Title = "Parent_1" };
var p2 = new Parent() { Id = 2, Title = "Parent_2" };
var p3 = new Parent() { Id = 3, Title = "Parent_3" };
var s1_1 = new SubTable() { Id = 11, ParentId = 1 };
var s1_2 = new SubTable() { Id = 12, ParentId = 1 };
var s1_3 = new SubTable() { Id = 13, ParentId = 1 };
var s2_1 = new SubTable() { Id = 21, ParentId = 2 };
var s2_2 = new SubTable() { Id = 22, ParentId = 2 };
var s3_1 = new SubTable() { Id = 31, ParentId = 3 };
var db_P = new List<Parent>() { p1, p2, p3 };
var db_S = new List<SubTable>() { s1_1, s1_2, s1_3, s2_1, s2_2, s3_1 };
public IEnumerable<Result> GetResults(string key = null)
{
var rows = from p in db_P
join s in db_S on p.Id equals s.ParentId into subContent
where string.IsNullOrEmpty(key) || p.Title.Contains(key)
select new Result() {
Id = p.Id,
Title = p.Title,
SubContentCount = subContent.Count()
};
return rows;
}
And example output (formatted onto multiple lines for readability)
> GetResults().ToList()
List<Submission#0.Result>(3) {
Submission#0.Result { Id=1, SubContentCount=3, Title="Parent_1" },
Submission#0.Result { Id=2, SubContentCount=2, Title="Parent_2" },
Submission#0.Result { Id=3, SubContentCount=1, Title="Parent_3" }
}
> GetResults("1").ToList()
List<Submission#0.Result>(1) {
Submission#0.Result { Id=1, SubContentCount=3, Title="Parent_1" }
}
>

Related

"Operation is not valid due to the current state of the object." Exception, when I want to retrieve Items

I use this method to retrieve Items for a Tree component in Blazor Serverside, In the DAL I have:
public List<TreeItem> GetTreeItems()
{
var tree = new List<TreeItem>();
TreeItem item = new TreeItem()
{
DepartmentID = 0,
CategoryID = 0,
Text = "Root",
Childs = context.Departments.OrderBy(d => d.Order).Select(d => new TreeItem()
{
DepartmentID = d.Id,
CategoryID = 0,
Text = d.Title,
Childs = d.Categories.OrderBy(c => c.Order).Select(c => new TreeItem()
{
DepartmentID = d.Id,
CategoryID = c.Id,
Text = c.Title
}).ToList()
}).ToList()
};
tree.Add(item);
return tree;
}
The TreeItem class is the following, (The model shared by the blazor Component and Dal Class):
public class TreeItem
{
public int DepartmentID { get; set; }
public int CategoryID { get; set; }
public string Text { get; set; }
public List<TreeItem> Childs { get; set; }
}
But when I was to retrieve Items for the tree in the blazor component I get the exception: Operation is not valid due to the current state of the object., admin is the DAL class I inject to Blazor component as follows:
private void GetTreeModel()
{
try
{
Items = admin.GetTreeItems();
TreeSuccess = true;
TreeMessage = "Success";
return;
}
catch (Exception ex) // Error here
{
TreeSuccess = false;
TreeMessage = "Can not load tree items";
return;
}
}
What is this error and How to solve it?
I solved my problem using First loading entities and then using Linq to Objects, Like this:
var tree = new List<TreeItem>();
var departments = context.Departments.OrderBy(d => d.Order).ToList();
var categories = context.Categories.OrderBy(c => c.Order).ToList();
TreeItem item = new TreeItem()
{
DepartmentID = 0,
CategoryID = 0,
Text = "Root",
Childs = departments.Select(d => new TreeItem()
{
DepartmentID = d.Id,
CategoryID = 0,
Text = d.Title,
Childs = categories.Where(c => c.DepartmentID == d.Id).OrderBy(c => c.Order).Select(c => new TreeItem()
{
DepartmentID = d.Id,
CategoryID = c.Id,
Text = c.Title
}).ToList()
}).ToList()
};
tree.Add(item);
return tree;
}

How would I perform a left join with coalesce in Linq?

Consider tables
Inventions, list of inventions
Components, list of all components available for use in inventions, and
InventionComponents, list of utilized components, with count, in an invention
For a given invention, &inventionID, I would like to do a 'covering' left join to all of the components instead of just the utilized components.
SQL would be something like
select
I.name as inventionName
, C.name as componentName
, coalese (IC.count, 0) as componentCount
from
(select &inventionID as inventionID, ID, name from components) C -- all components applied to some &inventionID
left join
inventionComponents IC
on
C.ID = IC.ComponentID
and C.inventionID = IC.inventionID
join
inventions I
on
I.ID = C.inventionID
Sample data and Linq query in .NET fiddle at https://dotnetfiddle.net/cx4bHp results in an exception
[System.NullReferenceException: Object reference not set to an instance of an object.]
Question: How should the Linq query be modified to perform the desired covering query ?
For completeness, the C# fiddle code is repeated here
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
var components = new List<Component>{
new Component { ID=1, Name = "Florgebit" },
new Component { ID=2, Name = "Phadron" },
new Component { ID=3, Name = "Goobstem" },
new Component { ID=4, Name = "Larchwren" },
new Component { ID=5, Name = "Zangponder" },
new Component { ID=6, Name = "Spoofork" },
new Component { ID=7, Name = "Forkoon" },
new Component { ID=8, Name = "Blidget" },
new Component { ID=9, Name = "Wazzawim" },
new Component { ID=10, Name = "Klackberg" },
};
var inventions = new List<Invention>{
new Invention { ID=21, Name = "Swazzlute" },
new Invention { ID=22, Name = "Corpocran" },
new Invention { ID=23, Name = "Fillyboof" },
};
var inventionComponents = new List<InventionComponent>{
new InventionComponent { ID=100, InventionID=21, ComponentID=1, Count=1 },
new InventionComponent { ID=101, InventionID=21, ComponentID=2, Count=2 },
new InventionComponent { ID=102, InventionID=21, ComponentID=8, Count=3 },
new InventionComponent { ID=103, InventionID=23, ComponentID=5, Count=4 },
new InventionComponent { ID=104, InventionID=23, ComponentID=6, Count=5 },
new InventionComponent { ID=105, InventionID=23, ComponentID=3, Count=4 },
new InventionComponent { ID=106, InventionID=21, ComponentID=4, Count=3 },
new InventionComponent { ID=107, InventionID=22, ComponentID=5, Count=2 },
new InventionComponent { ID=108, InventionID=22, ComponentID=4, Count=1 },
new InventionComponent { ID=109, InventionID=22, ComponentID=1, Count=6 },
new InventionComponent { ID=110, InventionID=22, ComponentID=7, Count=1 },
new InventionComponent { ID=111, InventionID=21, ComponentID=9, Count=1 },
};
var details =
from A in inventions
join B in inventionComponents on A.ID equals B.InventionID
join C in components on B.ComponentID equals C.ID
orderby A.Name, C.Name
select new {
InventionName = A.Name,
ComponentName = C.Name,
ComponentCount = B.Count
};
/*
foreach(var d in details)
{
Console.WriteLine("Invention: {0}, Component: {1}, Count: {2}", d.InventionName, d.ComponentName, d.ComponentCount);
}
*/
var inventionID = 22;
var index = 1;
// want full coverage of inventionID, componentID with applied counts
// 22,1,6
// 22,2,**0**
// 22,3,**0**
// 22,4,1
// 22,5,2
// 22,6,**0**
// 22,7,1
// 22,8,**0**
// 22,9,**0**
// 22,10,**0**
var corpcheck =
from C in components select new { InventionID = inventionID, ComponentID = C.ID, ComponentName = C.Name } into allcomps
join B in inventionComponents on new { allcomps.InventionID, allcomps.ComponentID } equals new { B.InventionID, B.ComponentID } into join1
// from j1 in Join1 // inner join
from j1 in join1.DefaultIfEmpty() // causes exception
orderby allcomps.ComponentName
select new {
RowNum = index++,
InventionID = allcomps.InventionID,
ComponentName = allcomps.ComponentName,
ComponentCount = j1.Count,
};
foreach(var x in corpcheck)
{
Console.WriteLine("InventionID: {0}, RowNum: {1}, ComponentName: {2}, Count: {3}", x.InventionID, x.RowNum, x.ComponentName, x.ComponentCount);
}
}
public class Invention
{
public int ID { get; set; }
public string Name { get; set; }
}
public class InventionComponent
{
public int ID { get; set; }
public int InventionID { get; set; }
public int ComponentID { get; set; }
public int Count { get; set; }
}
public class Component
{
public int ID { get; set; }
public string Name { get; set; }
}
}
By adding DefaultIfEmpty(), j1 can be null for some components. If j1 is null, I assume you want count to be 0:
from C in components select new { InventionID = inventionID, ComponentID = C.ID, ComponentName = C.Name } into allcomps
join B in inventionComponents on new { allcomps.InventionID, allcomps.ComponentID } equals new { B.InventionID, B.ComponentID } into join1
from j1 in join1.DefaultIfEmpty()
orderby allcomps.ComponentName
select new {
RowNum = index++,
InventionID = allcomps.InventionID,
ComponentName = allcomps.ComponentName,
ComponentCount = j1 == null ? 0 : j1.Count, // add null check
};
In LINQ-to-objects you could also use ComponentCount = j1?.Count ?? 0. but I assume you're going to use this in LINQ to a SQL backend.

Linq to objects, joining to collections and simplify select new

I need some help to simplify a linq query. I have 2 classes Invoice and Customer.
The Invoice have a property CustomerId and a property Customer.
I need to get all invoices and include the Customer object.
I don't like my query, as it needs to change if new properties are added to the Invoice object.
I can't join the invoice and customer earlier than this stage so that is not an alternative.
My query.
var customers = GetCustomers();
var invoices = GetInvoices();
var joinedList = (from x in invoices
join y in customers on x.CustomerId equals y.CustomerId
select new Invoice
{
Amount = x.Amount,
CustomerId = x.CustomerId,
Customer = y,
InvoiceId = x.InvoiceId
}).ToList();
The classes
public class Invoice
{
public int InvoiceId { get; set; }
public double Amount { get; set; }
public int CustomerId { get; set; }
public Customer Customer { get; set; }
}
public class Customer
{
public int CustomerId { get; set; }
public string Name { get; set; }
}
private static IEnumerable<Invoice> GetInvoices()
{
yield return new Invoice
{
Amount = 34,
CustomerId = 1,
InvoiceId = 1
};
yield return new Invoice
{
Amount = 44.7,
CustomerId = 1,
InvoiceId = 2
};
yield return new Invoice
{
Amount = 67,
CustomerId = 2,
InvoiceId = 3
};
yield return new Invoice
{
Amount = 89,
CustomerId = 3,
InvoiceId = 4
};
}
private static IEnumerable<Customer> GetCustomers()
{
yield return new Customer
{
CustomerId = 1,
Name = "Bob"
};
yield return new Customer
{
CustomerId = 2,
Name = "Don"
};
yield return new Customer
{
CustomerId = 3,
Name = "Alice"
};
}
Why not just a simple foreach loop:
// Dictionary for efficient look-up
var customers = GetCustomers().ToDictionary(c => c.CustomerId);
var invoices = GetInvoices().ToList();
//TODO: error checking
foreach(var i in invoices)
i.Customer = customers[i.CustomerId];

Linq Unique Values

i have a list of generic class which consists of 2 string property and 1 List as a property
code snipnets is as follows:
public Class abc
{
public int ID { get; set; }
public String Name { get; set; }
List<String> myList;
public List<String> Subjects
{
get
{
if (myList == null)
{
myList = new List<string>();
}
return myList;
}
}
public abc()
{
}
public abc(int id, String name, params string[] subjects)
{
Subjects.AddRange(subjects.AsEnumerable<String>());
ID = id;
Name = name;
}
}
List<abc> myList = new List<abc>();
myList.Add(new abc(1, "p1", "Maths", "Science"));
myList.Add(new abc(2, "p2", "Maths", "Art"));
myList.Add(new abc(3, "p3", "Art", "Science"));
myList.Add(new abc(4, "p4", "Geometry", "Maths"));
I need the output as
Subject Count Person
Maths 3 p1,p2,p4
Science 2 p1,p3
Art 2 p2,p3
Geometry 1 p4
Looks like you want something like:
var query = from item in myList
from subject in item.Subjects
group item.Name by subject into g
select new { Subject = g.Key,
Count = g.Count(),
Person = string.Join(",", g) };
(Change g into g.ToArray() in the string.Join call if you're using .NET 3.5.)
var result =myList.SelectMany(p => p.Subjects
.Select(q => new{Person = p.Name, Subject = q, ID = p.ID}))
.GroupBy(p => p.Subject)
.Select(p => new {Name = p.Key, Count = p.Count(), Persons = p
.Aggregate("", (a, b) => a + b.Person
+ ",").TrimEnd(',')}).OrderBy( p => p.Count);
Iterate over this collection, and print result as needed - properties of a result are Name, Count, Persons

Linq to Objects - Where search within a list

Linq to Objects - Where search within a list
internal class ProdQtyByWarehouse
{
public int id { get; set; }
public List<ProdWarehouseQty> ProdWarehouseQtys { get; set; }
}
internal class ProdWarehouseQty
{
public int id { get; set; }
public string PName { get; set; }
}
protected void Page_Load(object sender, EventArgs e)
{
var list1 = new List<ProdWarehouseQty>
{
new ProdWarehouseQty
{
id = 3,
PName = "list1PN1"
},
new ProdWarehouseQty
{
id = 4,
PName = "list1PN2"
}
};
var list2 = new List<ProdWarehouseQty>
{
new ProdWarehouseQty
{
id = 5,
PName = "list2PN1"
},
new ProdWarehouseQty
{
id = 6,
PName = "list2PN2"
}
};
var prodQtyByWarehouses = new List<ProdQtyByWarehouse>
{
new ProdQtyByWarehouse {id = 1, ProdWarehouseQtys = list1},
new ProdQtyByWarehouse {id = 1, ProdWarehouseQtys = list2}
};
List<int> integers = new List<int>{2,3,4,6};
List<ProdQtyByWarehouse> list =
(from c in prodQtyByWarehouses
where c.ProdWarehouseQtys.Contains(new ProdWarehouseQty {id = 3})
select c).ToList(); // no object is returned
}
How can i achieve:
List<ProdQtyByWarehouse> list =
(from c in prodQtyByWarehouses
where c.ProdWarehouseQtys.Contains(new ProdWarehouseQty {id in integers})
select c).ToList();
List<ProdQtyByWarehouse> list =
(
from c in prodQtyByWarehouses
where c.ProdWarehouseQtys.Exists(x => integers.Contains(x.id))
select c
).ToList();

Resources