Linq grouping get multiple properties - c#

I'm trying to group by CategoryId and display the CategoryId + the Name property of the group, I need help modifying this code so the Name property can be displayed, check out view below th see what I mean.
Database
ItemCategory
int ItemId
int CategoryId
Category
int CategoryId
int? ParentId
string Name
var itemcategories = db.Fetch<ItemCategory, Category>(#"SELECT * FROM ItemCategory LEFT JOIN Category on Category.CategoryId = ItemCategory.CategoryId WHERE ItemId = #0", item.ItemId);
var grouped = from b in itemcategories
where b.Category.ParentId != null
group b by b.Category.ParentId ?? 0 into g
select new Group<int, ItemCategory> { Key = g.Key, Values = g };
public class Group<K,T>
{
public K Key;
public IEnumerable<T> Values;
}
In view
#foreach (var group in #Model.ItemCategories)
{
#group.Key **Category.Name should be displayed here**
}
foreach (var value in group.Values)
{
#value.Category.Name
}

I think you're looking for the answer provided here:
Group by in LINQ.
However, you should also change your DB query so that it returns the actual result of the join and not just an IEnumerable<ItemCategory>.
The Group class could look like:
public class Group<K,T>
{
public K Key;
public IEnumerable<T> Values;
public IEnumerable<string> CategoryNames;
}
Note that if you want to group by ParentId, your key will always be ParentId, it's the idea of the GroupBy function.
With that new Group class and DB query, you should be able to use g.Name as the parameter for CategoryNames.

Related

LINQ create generic List of nestet objects

How can I get a List<Type1> which includes another List<Type2> from another List<Type3>?
Here is the situation:
I have a List<DbStruncture>. Each entry includes a DatabaseStructure
public partial class DatabaseStructure
{
public string TableSchema { get; set; }
public string TableName { get; set; }
public string ColumnName { get; set; }
public bool? IsPrimaryKey { get; set; }
}
I also have
public class Table
{
public string Name { get; set; }
public string Schema { get; set; }
public List<Column> Columns { get; set; }
}
public class Column
{
public string Name { get; set; }
public bool? IsPrimaryKey { get; set; }
}
Now I want to fill the Data from the List<DatabaseStructure> into a List<Table> which includes a List<Column> with all the Columns of that Table.
I tried it with LINQ and this is how far I got:
var query =
from t in result
group t.TableName by t.TableName
into tn
select new
{
Table = tn.Key,
Schema = from s in result where s.TableName == tn.Key select s.TableSchema.First(),
Columns = from c in result where c.TableName == tn.Key select new Column
{
Name = c.ColumnName,
IsPrimaryKey = c.IsPrimaryKey
}
};
The problem with my solution is, that my query is not a generic List...
Can anybody point me into the right direction? Is LINW the right way here? If yes, how do I get the wanted result?
Thanks in advance
Preface: I prefer (and recommend) using Linq with the Extension Method syntax instead of using the from,group,into keywords because it's more expressive and if you need to do more advanced Linq operations you'll need to use Extension Methods anyway.
To begin with, your input is denormalized (I presume the output of running SELECT ... FROM INFORMATION_SCHEMA.COLUMNS) where each row contains repeated table information, so use GroupBy to group the rows together by their table identifier (don't forget to use both the Table Schema and Table Name to uniquely identify a table!)
Then convert each group (IGrouping<TKey: (TableSchema,TableName), TElement: DatabaseStructure>) into a Table object.
Then populate the Table.Columns list by performing an inner Select from the IGrouping group and then .ToList() to get a concrete List<Column> object.
My expression:
List<DatabaseStructure> input = ...
List<Table> tables = input
.GroupBy( dbs => new { dbs.TableSchema, dbs.TableName } )
.Select( grp => new Table()
{
Name = grp.Key.TableName,
Schema = grp.Key.TableSchema,
Columns = grp
.Select( col => new Column()
{
Name = col.Name,
IsPrimaryKey = col.IsPrimaryKey
} )
.ToList()
} )
.ToList()
OK, just found the answer myself.
Here it is:
var query =
(from t in result
group t.TableName by t.TableName
into tn
select new Table
{
Name = tn.Key,
Schema = (from s in result where s.TableName == tn.Key select s.TableSchema).First(),
Columns = (from c in result
where c.TableName == tn.Key
select new Column
{
Name = c.ColumnName,
IsPrimaryKey = c.IsPrimaryKey
}).ToList()
});

Can you add a Where() to an IQueryable when the field is not in the selected output

Suppose I have a 2 table join in a function that returns an IQueryable, but the output is a named type that is neither of the two tables:
var qry = from p in Persons
join h in Hobbies on p.PersonId equals h.PersonId
select new OutputType
{
Name = p.FirstName,
Hobby = h.HobbyName
}
return qry
Let's say now I wanted to take this returned query and do something like:
var newQuery = qry.Where( p=>p.Age > 18 )
As you can see this is a problem because the IQueryable is of type OutputType, so I can't add a where to a person's age unless I were to add the Age to OutputType.
Is there anyway of 'breaking into' the IQueryable expression tree and adding a lambda somehow that will query on the source collection specified in it and add a Where clause to it? Or do I have do I have to add a Where field to the OutputType even though I'm uninterested in ultimately projecting it?
It is easier to narrow your view later than to try to backtrack. Here is a stripped down example of how I like to layer methods for reuse so that they spit out nice sql.
private IQueryable<Part> GetParts_Base()
{
//Proprietary. Replace with your own.
var context = ContextManager.GetDbContext();
var query = from c in context.Component
where c.Active
//kind of pointless to select into a new object without a join, but w/e
select new Part()
{
PartNumber = c.ComponentNumber,
Description = c.ComponentDescription,
Cost = c.ComponentCost,
Price = c.ComponentPrice
};
return query;
}
//Exclude cost from this view
public IEnumerable<Part_PublicView> GetParts_PublicView(decimal maxPrice)
{
var query = GetParts_Base();
var results = from p in query
where p.Cost < maxPrice
select new Part_PublicView()
{
PartNumber = p.PartNumber,
Description = p.Description,
Price = p.Price
};
return results;
}
public class Part_PublicView
{
public string PartNumber { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
}
private class Part : Part_PublicView
{
public decimal Cost { get; set; }
}
Linq-to-entity does not penalize you for selecting the extra column early on. As you can see, the sql includes the Cost column in the constraint but not in the select.
SELECT
1 AS [C1],
[Extent1].[ComponentNumber] AS [ComponentNumber],
[Extent1].[ComponentDescription] AS [ComponentDescription],
[Extent1].[ComponentPrice] AS [ComponentPrice]
FROM [dbo].[Component] AS [Extent1]
WHERE [Extent1].[ComponentCost] < #p__linq__0

How to make a 'group by' on a Child's property and avoiding a 'NotSupportedException'

I try to get the count of loan by Book Type.
i have these 3 class (Simplified). part of a Code-first Model :
public class Loan
{
public int LoanId {get;set;}
.....
public int BookId {get;set;}
Public virtual Book {get;set;}
}
//Book parent class
public class Book {
public int BookId {get;set;}
...
}
//a Book child class with a specific 'Type' property
public SmallBook : Book
{
public string Type {get;set;}
...
}
So long, i tried this kind of Query ....
var StatsMono = (from p in context.Loans
//the 'where' clause allow to obtain all the loans where Loans.Book is a SmallBook.
where context.Books.OfType<SmallBook>().Any(exm => exm.BookId == p.BookId)
//here is my problem : i can't access 'SmallBook.Type' w/o cast
group p by ((SmallBook)p.Book).Type into g
select { GroupingElement=g.Key,intValue=g.Count()}
).ToList();
...but i can't get rid of the following exception:
Unable to cast the type 'Ips.Models.Book' to type
'Ips.Models.SmallBook'. LINQ to Entities only supports casting EDM
primitive or enumeration types.
I understand why i get this error but now i'm wondering if there is a way to achieve what i want with only one query ?
You can use explicit join:
var StatsMono = (from p in db.Loans
join b in db.Books.OfType<SmallBook>() on p.BookId equals b.BookId
group p by b.Type into g
select new { GroupingElement = g.Key, intValue = g.Count() }
).ToList();
But better add the inverse navigation property to your model
public abstract class Book
{
public int BookId { get; set; }
// ...
public ICollection<Loan> Loans { get; set; }
}
and use it
var StatsMono = (from b in db.Books.OfType<SmallBook>()
from p in b.Loans
group p by b.Type into g
select new { GroupingElement = g.Key, intValue = g.Count() }
).ToList();
Something like..
var result = context.Loans.GroupBy(g=> g.book.Type).select(s=> new { BookType= s.book.type, count = s.count }).ToList();

Update a row adding a relationed row in EF (many-to-many)

I have two entities: Order and Item and a many-to-many relationship between them. I have a method which receive the ItemId as parameter like this:
public void AddItems(int OrderId, int ItemId)
{
Item item = db.ItemSet.SingleOrDefault(i => i.Id == ItemId);
Order order = db.OrderSet.SingleOrDefault(o => o.Id == OrderId);
order.Items.Add(item);
db.SaveChanges();
}
There are a lot of rows in the ItemSet table, so the first query is a heavy one. Is there a way that I can add the item to the order without doing a query first on the "ItemSet" table? I mean, can I add the ItemId directly to the Order.Items or something like that?
Item item = new Item { ID = ItemId };
Order order = new Order { ID = OrderId };
db.ItemSet.Attach(item);
db.OrderSet.Attach(order);
order.Items.Add(item);
db.SaveChanges();
Just be sure that you have this in your Order class:
public Order()
{
Items = new List<Item>();
}
So you don't get null pointer exception in order.Items.Add(item);
So you can model the entities, so:
public DbSet<Item> ItemSet {get;set;}
public DbSet<Order> OrderSet {get;set;}
public DbSet<ItemOrder> ItemOrders {get;set;}
public class Item
{
public int Id {get;set;}
}
public class Order
{
public int Id {get;set;}
}
public class ItemOrder
{
[Key, Column(Order = 0)]
public int ItemId {get;set;}
[Key, Column(Order = 1)]
public int OrderId {get;set;}
}
So in order to update:
public void AddItems(int OrderId, int ItemId)
{
var itemExists = db.ItemOrders.FirstOrDefault(x => x.OrderId == OrderId && x.ItemId == ItemId);
if (itemExists != null) return;
db.ItemOrders.Add(new ItemOrder { OrderId = OrderId, ItemId = ItemId });
db.SaveChanges();
}
Then you can query using the LINQ .Join() and is pretty straightforward.
Just for completeness sake the OP mentioned that the navigation properties have now disappeared. So if you know the OrderId then you can simple do:
var items = db.ItemOrders.GroupJoin(
db.ItemSet,
io => io.ItemId,
i => i.Id,
(itemOrder, items) => items)
.ToList();
So to walk you through, GroupJoin asks for the IQueryable/IEnumerable of data you want to join against.
The second param is the field of the initial data set i.e. ItemOrders the set you want to join to.
The third parameter is the field of the set that is being joined in i.e. ItemSet.
The fouth parameter is basically a Select where you are presented with a Func signature of roughly (ItemOrder itemInFirstSet, IEnumerable<Item> itemsThatAreJoined).
You can apply this same logic to the LINQ Join extension. But rather than itemsThatAreJoined it is itemThatIsJoined. So the distinction is GroupJoin will find multiple entities that match, Join will only find one.
This is no way a detailed explanation, more of an overview

Why is this linq expression not working?

I'm using LINQ to Entities.
I have a table called Student; it has ID and Name as it's columns. ID is a primary key.
I'd like to be able select the name of the Student and get the amount of Students with the same Name.
So for example I'd have this as my table data.
ID Name
1 Bob
2 Will
3 Bob
After performing the query I'd return a List of Student objects looking like this.
Name Quantity
Bob 2
Will 1
I guess it is kind of similar to how the Tags page of stackoverflow works; It has the name and the quantity.
Anyways, I created a partial class called Student.cs in which I added a Quantity property like this.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace MySite.Models
{
public partial class Student
{
private int _quantity;
public int Quantity
{
get { return _quantity; }
set { _quantity = value; }
}
}
}
I came up with this but I'm getting an error..
public IQueryable<Student> FindStudentsDistinctWithQuantity()
{
/*SELECT Name, COUNT(Name) AS Quantity
FROM Student
GROUP BY Name*/
var students= (from s in db.Students
group s by s.Name into g
select new {Name = g.Key, Quantity = g.Count()});
return students;
}
The error i'm getting says something like Can't convert from type Anonymous to the Student list. Does it have something to do with it not recognizing the quantity field I added in the partial class?
Thanks!
Change your Student type to look like this:
public partial class Student
{
public Int32 Quantity { get; set; }
public String Name { get; set; }
}
And your query to look like this:
var students = from s in db.Students
group s by s.Name into g
select new Student {
Name = g.Key,
Quantity = g.Count() };
Your method returns an IQueryable<Student> but you are currently returning an IQueryable<T> of a projected anonymous type.
You need to refactor your Student type to have a Name property of type String and then project new instances of your Student type from the expression so that the return type of your expression will match the return type of your method.
Your function returns Student
public IQueryable<Student> FindStudentsDistinctWithQuantity(){ ... }
But your Linq query returns a new type that contains a Name and an Int (count)
>>> select new {Name = g.Key, Quantity = g.Count()});
y-try select new Student{Name = g.Key, Quantity = g.Count()}
The method's return value ties the "students" collection to IQueryable<Student> but... the Linq expression is creating an IQueryable<some anonymous type>, and there is no conversion between the two.
You may get a baby step further by modifying your select part to be:
select new Student() {....}
Hopefully this helps,
Tyler
The select new keywords are causing the form of the data to change, which means the LINQ query will not return an IQueryable<Student>, but rather an anonymous type containing the "Name" and "Quantity" properties. If you change it to return a concrete type rather than an anonymous one you will be able to retrieve the data in the form you want.
public class StudentGrouping {
public string Name { get; set; }
public int Quantity { get; set; }
}
public IQueryable<StudentGrouping> FindStudentsDistinctWithQuantity()
{
/*SELECT Name, COUNT(Name) AS Quantity
FROM Student
GROUP BY Name*/
var students= (from s in db.Students
group s by s.Name into g
select new StudentGrouping {
Name = g.Key,
Quantity = g.Count()
}).AsQueryable();
return students;
}
The problem is that you are not returning students - you are trying to return an anonymous type from your function. This is not allowed.
Create a class to represent your result and use new MyClass { ... } instead of new { ... } in your query, and change the method to return IQueryable<MyClass> instead of IQueryable<Student>.
You could for example make a class called StudentNameAndResults.
class StudentNameAndResults
{
public string Name { get; set; }
public int Quantity { get; set; }
}
Alternatively, you could just return the result as a Dictionary or an IEnumarable of IGrouping. For example:
public IDictionary<string, int> FindStudentsDistinctWithQuantity()
{
Database db = new Database();
var students= (from s in db.Students
group s by s.Name into g
select new {Name = g.Key, Quantity = g.Count()});
return students.ToDictionary(s => s.Name, s => s.Quantity);
}
Also, the property you created is using the verbose syntax from pre-C# 3.0 days. Now you can use auto-implemented properties if you don't need any special logic:
public int Quantity { get; set; }
var students= (from s in db.Students
group s by s.Name into g
select new {Name = g.Key, Quantity = g.Count()});
This is an anonymous type, not IQueryable<Student>. Either you need to return System.Object, or you need to return IQueryable<Student> as follows...
return from s in db.Students
group s by s.Name into g
select new Student{Name = g.Key, Quantity = g.Count()};
Where Student defines the properties used in the initializtion.
You are doing a projection in your linq query. If you'd hover the cursor over var students inside vs you'll see its a collection of an anonymous type.
If you want to return an IQueryabley<Student> you need to do:
var students= from s in db.Students
group s by s.Name into g
select s.Key;
There is no way outside methods can know about the anonymous type you have created in your previous example, so you won't be able to return a typed collection.
With the method I suggested you will still be able to do a projection on the return value of your method later on, since IQueryable is composable until the first enumeration:
var students = FindStudentsDistinctWithQuantity();
var namesAndQunatity = from s in students select new {s.Name, s.Quantity};

Categories

Resources