Group by Nested List Identifier - c#

I have an object as follows:
class ObjectParent
{
public List<ObjectChild> ChildNumbers{ get; set; }
public int ParentId { get; set; }
}
class ObjectChild
{
public int ChildId { get; set; }
public string Property { get; set; }
}
I have a List<ObjectParent> obj that I need to group by ChildId which is inside ObjectChild
How can I do so?
this is what i tried
var groupedItemDetails = itemDetails
.GroupBy(u => u.ChildNumbers.ChildId)
.Select(grp => grp.ToList())
.ToList();

You can use SelectMany to flatten the list and then group by ChildId like this:-
var result = parents.SelectMany(x => x.ChildNumbers, (parentObj, childnum) =>
new {
parentObj, childnum
})
.GroupBy(x => x.childnum.ChildId)
.Select(x => new
{
ChildId = x.Key,
Properties = x.Select(z => z.childnum.Property),
ParentIds = x.Select(z => z.parentObj.ParentId)
});
Check this Fiddle I have created with some sample data.

Related

Create DTO by joining EF entity with another object in LINQ

I have an Entity Framework entity
public class Entiy
{
public string EntityProperty1 { get; set; }
public string EntityProperty2 { get; set; }
public string EntityProperty3 { get; set; }
public Guid? SomeId { get; set; }
}
And also an OtherObject and a DTO which contains properties that'd be equal to the other classes.
public class OtherObject
{
public string OtherObjectProperty1 { get; set; }
public string OtherObjectProperty2 { get; set; }
public string OtherObjectProperty3 { get; set; }
public Guid SomeId { get; set; }
}
public class DTO
{
public string EntityProperty1 { get; set; }
public string EntityProperty2 { get; set; }
public string EntityProperty3 { get; set; }
public string OtherObjectProperty1 { get; set; }
public string OtherObjectProperty2 { get; set; }
public string OtherObjectProperty3 { get; set; }
}
In my service class I get an IQueryable of my entities and I send in an argument which contains an enumerable of OtherObject. It works fine when I just want to create DTOs using the Entities properties.
public IEnumerable<DTO> GetDtos(IEnumerable<OtherObject> otherObjects)
{
return _someRepository.GetAll()
.Where(a => otherObjects.Select(b => b.SomeId).Contains(a.SomeId))
.Select(d => new DTO()
{
EntityProperty1 = d.EntityProperty1
EntityProperty2 = d.EntityProperty2
EntityProperty3 = d.EntityProperty3
})
.ToList();
}
However, I would also like to join in some properties from my OtherObject class. If I do the following
public IEnumerable<DTO> GetDtos(IEnumerable<OtherObject> otherObjects)
{
return _someRepository.GetAll()
.Where(a => otherObjects.Select(b => b.SomeId).Contains(a.SomeId))
.Select(d => new DTO()
{
EntityProperty1 = d.EntityProperty1
EntityProperty2 = d.EntityProperty2
EntityProperty3 = d.EntityProperty3
OtherObjectEntity1 = otherObjects.FirstOrDefault(a => a.SomeId == d.SomeId).OtherObjectEntity1
})
.ToList();
}
I get the following error: "Unable to create a constant value of type XXX. Only primitive types or enumeration types are supported in this context"
You can add a property from otherObject by adding another Select to the first query
public IEnumerable<DTO> GetDtos(IQueryable<OtherObject> otherObjects)
{
var ids = otherObjects.Select(x => x.SomeId);
return _someRepository.GetAll()
.Where(x => ids.Contains(x.SomeId)
.ToList()
.Select(x =>
new DTO()
{
EntityProperty1 = x.EntityProperty1,
EntityProperty2 = x.EntityProperty2,
EntityProperty3 = x.EntityProperty3,
OtherObjectEntity1 = otherObjects.FirstOrDefault(p => p.SomeId == x.SomeId).OtherObjectEntity1
};
}
Also I extracted otherObjects' ids into local variable to optimize the query a little bit.
Easiest way, is to do a ToList after the Where clause, then do DTO projection afterwards. Doing an early ToList would overselect all columns of the repository, which is bad for performance.
Another option, materialized the first three columns first..
var result =
someRepository.GetAll()
.Where(a => otherObjects.Select(b => b.SomeId).Contains(a.SomeId))
.Select(d => new DTO()
{
EntityProperty1 = d.EntityProperty1
EntityProperty2 = d.EntityProperty2
EntityProperty3 = d.EntityProperty3
}).
.ToList()
..then re-project the DTO:
result = result(d =>
new DTO() {
EntityProperty1 = d.EntityProperty1,
EntityProperty2 = d.EntityProperty2,
EntityProperty3 = d.EntityProperty3,
OtherObjectEntity1 = otherObjects.FirstOrDefault(a => a.SomeId == e.SomeId).OtherObjectEntity1
}
Yet another approach is to use IQueryable on IQueryable. That would generate subquery in SELECT statement.
public IEnumerable<DTO> GetDtos(IQueryable<OtherObject> otherObjects)
{
return _someRepository.GetAll()
.Where(a => otherObjects.Select(b => b.SomeId).Contains(a.SomeId))
.Select(d => new DTO()
{
EntityProperty1 = d.EntityProperty1
EntityProperty2 = d.EntityProperty2
EntityProperty3 = d.EntityProperty3,
OtherObjectEntity1 = otherObjects.FirstOrDefault(a => a.SomeId == e.SomeId).OtherObjectEntity1
})
.ToList();
}
Or use join (Milosz Wieczorek's answer), it's more efficient than subquery.
Did you try using Join instead od Where ?
It would look something like this:
_someRepository.GetAll()
.Join(otherObjects, a => a.SomeId, b => b.SomeId, (d, e) => new DTO
{
EntityProperty1 = d.EntityProperty1
EntityProperty2 = d.EntityProperty2
EntityProperty3 = d.EntityProperty3
OtherObjectEntity1 = e.OtherObjectEntity1
})
.ToList();
In my opinion it would be allso more efficient than your implementation.
EDIT
Thanks for the comments, I checked the query with database and it didn't work.
I decided to fix the query, if you would like to use Join :)
var ids = otherObjects.Select(i => i.Id);
// When
var results = repository.GetAll()
.Where(a => ids.Contains(a.Id))
.ToList()
.Join(otherObjects, a => a.Id, b => b.Id, (a, b) => new SimpleDto
{
Id = a.Id,
Name = a.Name,
Attribute = b.Attribute
})
.ToList();

Why does calling .Include() after a .Join() not work?

I have a DB structure which is not ideal, but I have coded it into EF like this:
[Table("Item")]
public class Item
{
[Key] public int Id { get; set; }
public int CategoryId { get; set; }
public int ItemTypeId { get; set; } // An ItemTypeId of 1 means this row refers to an Article
public int ItemId { get; set; } // this refers to the Article primary key
}
[Table("Article")]
public class Article
{
[Key] public int Id { get; set; }
...
public virtual ICollection<SubArticle> SubArticles { get; set; }
}
[Table("SubArticle")]
public class SubArticle
{
...
public int ArticleId { get; set; }
}
modelBuilder.Entity<Article>().Collection(_ => _.SubArticles).InverseReference(_ => _.Article).ForeignKey(_ => _.ArticleId);
What I want to do is get all articles (with the corresponding sub-articles) that belong to a specific category. I have this query which is working:
var result = await Context.Set<Item>()
.Where(i => i.CategoryId == 200)
.Where(i => i.ItemTypeId == 1)
.Join(
Context.Set<Article>().Include(a => a.SubArticles),
i => i.ItemId,
a => a.Id,
(i,a) => a)
.ToListAsync();
result.SubArticles.First(); // works
My question is why does this query not work:
var result = await Context.Set<Item>()
.Where(i => i.CategoryId == 200)
.Where(i => i.ItemTypeId == 1)
.Join(
Context.Set<Article>(),
i => i.ItemId,
a => a.Id,
(i,a) => a)
.Include(a => a.SubArticles)
.ToListAsync();
result.SubArticles.First(); // error: result.SubArticles is null

LINQ to Entities, Where Any In

How to write 'Where Any In' in LINQ to Entity?
Here is my model :
class Chair
{
public int Id { get; set; }
public int TableId { get; set; }
public Table Table { get; set; }
}
class Table
{
public int Id { get; set; }
public ICollection<Chair> Chairs { get; set; }
public ICollection<Category> Categories { get; set; }
public Table()
{
Chairs = new List<Chair>();
Categories = new List<Category>();
}
}
class Category
{
public int Id { get; set; }
public ICollection<Table> Tables { get; set; }
}
I also got a simple list of Category :
List<Category> myCategories = new List<Category>(c,d,e);
I want to get only that Chairs that belongs to Table that got one of the Category from myCategories List. Thats what im trying to do :
var result =
ctx.Chairs.Where(x => x.Table.Categories.Any(y => myCategories.Any(z => z.Id == y.Id))).ToList();
I think its ok but what i get is error :
"Unable to create a constant value of type 'ConsoleApplication1.Category'. Only primitive types or enumeration types are supported in this context"
Try to compare with in-memory categories Ids collection, instead of categories collection.
var myCategoriesIds = myCategories.Select(c => c.Id).ToArray();
var result =
context.Chairs
.Where(
x => x.Table.Categories.Any(
y => myCategoriesIds.Contains(y.Id)))
.ToList();
this is because ctx.Chairs is a collection that is in database, you should retrieve that collection first in order to compare it with in-memory data:
var result = ctx
.Chairs
.AsEnumerable() // retrieve data
.Where(x =>
x.Table.Categories.Any(y =>
myCategories.Any(z => z.Id == y.Id)))
.ToList();
EDIT: that wouldn't be the correct thing to do if you have a lot of entities on database, what you can do is to split it into two queries:
var tables = ctx.Tables
.Where(x =>
x.Categories.Any(y =>
myCategories.Any(z => z.Id == y.Id)));
var result = ctx.Chairs
.Where(x =>
tables.Any(t=> t.Id == x.TableId))
.ToList();
You can select Ids from myCategories and use it last statement.
var CategoryIds = myCategories.Select(ct => ct.Id);
var result = ctx.Chairs.Where(x => x.Table.Categories.Any(y => CategoryIds.Any(z => z == y.Id))).ToList();

Invalid anonymous type error selecting mutiple columns in LINQ to Entities

I am trying to fill my ViewModel from a few different LINQ queries but I am running into the problem of trying to fill multiple properties from a single LINQ query but I get the error
invalid anonymous type member declarator. anonymous type members must be declared with a member assignment, simple name or member access
I have done some searches and founds some posts but they are all about completely filling a ViewModel and not just a few properties like I am trying to do. What am I supposed to do to fix this, or am I going about this completely wrong?
using (ForumContext db = new ForumContext())
{
model.ID = db.yaf_Topic
.Where(t => t.ForumID == 5)
.OrderByDescending(t => t.Posted)
.Select(t => t.PollID.Value).First();
model = db.yaf_Poll
.Where(p => p.PollID == model.ID)
.Select(p => new
{
model.Question = p.Question,
model.IsMultipleChocie = p.AllowMultipleChoices,
model.ExperationDate = p.Closes
})
.First();
model.Choices = db.yaf_Choice
.Where(c => c.PollID == model.ID)
.Select(c => new
{
model.Votes.Key = c.Choice,
model.Votes.Value = c.Votes,
})
.ToList();
model.VotedIPs = db.yaf_PollVote
.Where(p => p.PollID == model.ID)
.Select(p => p.RemoteIP)
.ToList();
model.VotedUserIDs = db.yaf_PollVote
.Where(p => p.PollID == model.ID)
.Select(p => p.UserID)
.ToList();
}
My Model:
public class PollVM
{
public int ID { get; set; }
public string Question { get; set; }
public bool IsMultipleChocie { get; set; }
public DateTime? ExperationDate { get; set; }
public KeyValuePair<string, int> Choices { get; set; }
public List<string> VotedIPs { get; set; }
public List<int?> VotedUserIDs { get; set; }
}
You can't assign a variable inside an anonymous type declaration. You need to select the anonymous type variable first and assign its properties to the model properties one by one. Change this part
model = db.yaf_Poll
.Where(p => p.PollID == model.ID)
.Select(p => new
{
model.Question = p.Question,
model.IsMultipleChocie = p.AllowMultipleChoices,
model.ExperationDate = p.Closes
})
.First();
to this
var poll = db.yaf_Poll
.Where(p => p.PollID == model.ID)
.Select(p => new
{
p.Question,
p.AllowMultipleChoices,
p.Closes
})
.First();
model.Question = poll.Question;
model.IsMultipleChocie = poll.AllowMultipleChoices;
model.ExperationDate = poll.Closes;
The third query below has the same problem
model.Choices = db.yaf_Choice
.Where(c => c.PollID == model.ID)
.Select(c => new
{
model.Votes.Key = c.Choice,
model.Votes.Value = c.Votes,
})
.ToList();
I assume there might be more than one choices, so change your model as below
public class PollVM
{
public PollVM()
{
this.Choices = new List<KeyValuePair<string, int>>();
}
public int ID { get; set; }
public string Question { get; set; }
public bool IsMultipleChocie { get; set; }
public DateTime? ExperationDate { get; set; }
public List<KeyValuePair<string, int>> Choices { get; set; }
public List<string> VotedIPs { get; set; }
public List<int?> VotedUserIDs { get; set; }
}
and change the third query to this
var choices = db.yaf_Choice
.Where(c => c.PollID == model.ID)
.Select(c => new
{
c.Choice,
c.Votes,
})
.ToList();
foreach (var ch in choices)
{
model.Choices.Add(new KeyValuePair<string, int>(ch.Choice, ch.Votes));
}

Groupby list within the list using LINQ

I have two classes:
class Customer
{
public string Name { get; set; }
public string ZipCode { get; set; }
public List<Order> OrderList { get; set; }
}
class Order
{
public string OrderNumber { get; set; }
}
Using LINQ, i want to get list of Orders group by ZipCode. If Zipcode "12121" has 10 customers and each has 2 orders then it should return me only one Zipcode with the list of 20 orders.
I am trying to do it like this but not able to figure out whats wrong
var orders = br.CustOrderList
.Select(r => new
{
r.ZipCode,
r.Name,
r.OrderList
})
.GroupBy(x => new { x.ZipCode, x.OrderList});
Any help please?
This should do what you want:
var orders = br.CustOrderList
.GroupBy(x => x.ZipCode)
.Select(g => new
{
ZipCode = g.Key,
Orders = g.SelectMany(x => x.OrderList)
});
var orders = br.CustOrderList
.Select(r => new
{
r.ZipCode,
r.Name,
r.OrderList
})
.GroupBy(x => x.ZipCode);
You just want to group by the ZipCode so just group by that
Ah yea just try
var order = br.CustOrderList
.GroupBy(x = x.ZipCode);
No need to select new items out of the list

Categories