Entity Framework - grouping by one column and selecting multiple columns - c#

I have this method for my API where I'm trying to filter out a user (userId) and group by EventId. Then I want to select three columns from the table: name, eventId and UserId.
This is my code but it's not working:
[HttpGet("[action]/{userId}")]
public IActionResult EventsMatchFiltered(int userId)
{
var events = _dbContext.Events.Where(event1 => event1.UserId != userId)
.GroupBy(g => new {g.EventId })
.SelectMany(g => g.Select(h => new
{
Name = h.Name,
h.EventId,
h.UserId
}));
return Ok(events);
}

Firstly, events would be an IQueryable rather than a materialized collection. Second, it would be an anonymous type rather than a proper DTO/ViewModel serializable class. Thirdly, you are grouping by an EventId, but then not "selecting" anything based on that grouping. EventId sounds like the Primary Key of the Event you are supposedly grouping.. Are you sure you don't actually mean to Group by User??
Normally for something like a Grouped result you would have a view model something like:
[Serializable]
public class UserEventSummaryViewModel
{
public int UserId { get; set; }
public ICollection<EventSummaryViewModel> Events { get; set; } = new List<EventSummaryViewModel>();
}
[Serializable]
public class EventSummaryViewModel
{
public int EventId { get; set; }
public string Name { get; set; }
}
then to query and fill:
var otherUserEvents = _dbContext.Events
.Where(e => e.UserId != userId)
.GroupBy(g => g.UserId )
.Select(g => new UserEventSummaryViewModel
{
UserId == g.Key,
Events = g.Select(e => new EventSummaryViewModel
{
EventId = e.EventId,
Name = e.Name
}).ToList()
}).ToList();
This would provide a structure of events grouped by each of the other Users. If you want UserId and UserName then add the UserName property to the UserEventSummary view model and in the GroupBy clause:
.GroupBy(g => new { g.UserId, g.Name } )
and populate these in the viewmodel with:
UserId == g.Key.UserId,
UserName = g.Key.Name,

Related

Select necesary columns in a table in EF Core

I have two models: Room and User
Room :
public class Room
{
[Key]
public Guid Id { get; set; }
[Required]
public string Name { get; set; }
public List<User> Users { get; set; }
}
User:
public class User
{
[Key]
public Guid Id { get; set; }
public string UserName { get; set; }
public virtual List<Room> Rooms { get; set; }
}
I want to get the list of room names, that a special user, here user1, is belong to.
Here is my code:
List<string> MyRoomsNames = _context.Rooms
.Include(p => p.Users)
.Select(r => new { r.Name,r.Users})
.Where(r => r.Users.Contains(user1)).ToList();
And, here is the error:
Error CS0029 Cannot implicitly convert type
'System.Collections.Generic.List<<anonymous type: string Name,
System.Collections.Generic.List<Core.Models.User> Users>>' to
'System.Collections.Generic.List'
The issue with structure you had was doing the Where after the Select. For instance given the following:
List<string> MyRoomsNames = _context.Rooms
.Select(r => r.Name)
.Where(r => r.Users.Contains(user1))
.ToList();
This won't work because the Where clause will operate on the results of the Select so to query users you'd want to have selected them, but then this will conflict with the desired return type. Switching the order of operations:
List<string> MyRoomsNames = _context.Rooms
.Where(r => r.Users.Contains(user1))
.Select(r => r.Name)
.ToList();
This will give you the desired output as the Where clause will be applied against the Room entities and their related Users.
One further improvement for the query would be to do the check against the user ID rather than comparing entity references. EF may optimize that "Users.Contains", or it may be doing something unsavory like client-side evaluation, but an alternative that would just do an Index check in the DB:
var userId = user1.Id;
List<string> MyRoomsNames = _context.Rooms
.Where(r => r.Users.Any(u => u.UserId == userId))
.Select(r => r.Name)
.ToList();
Or since you have Rooms associated to the User entity, check how "user1" was loaded and whether Rooms was Included:
List<string> MyRoomsNames = user1.Rooms
.Select(r => r.Name)
.ToList();
This could trigger a lazy load or a NullReferenceException if the user's rooms were not eager loaded (Include). Alternatively the rooms could be queried through the user:
var userId = user1.Id;
List<string> MyRoomsNames = _context.Users
.Where(u => u.UserId == userId)
.SelectMany(u => u.Rooms.Select(r => r.Name))
.ToList();

Linq2Db: How to use function Sql.Ext.ListAgg?

I have a model with table Position where are multiple rows with same column value CompanyId and I'd like to group these rows and concat another column Email into comma separated list.
My database is IBM DB2 for i and in SQL I can write query like this:
SELECT
CompanyId,
ListAgg(Email, ',') within group (order by Email) as Emails
FROM Postion
GROUP BY CompanyId
I'm trying to retype the query above to Linq2DB with additional provider LinqToDb4iSeries but without success. The first property GroupedEmails1 builds query without statement "group by", the second one GroupedEmails2 builds nested subquery with group by and parent query with ListAgg - both of them fail to work.
public class Position {
public int CompanyId { get; set; }
public string Email { get; set; }
}
public class MyDataConection : DataConnection {
public ITable<Position> Positions => GetTable<Position>();
public IQueryable<object> GroupedEmails1 => Position
.Select(p => new {
p.CompanyId
Emails = Sql.Ext.ListAgg(p.Email, ",").WithinGroup.OrderBy(p.Email).ToValue()
});
public IQueryable<object> GroupedEmails2 => Position
.GroupBy(p => p.CompanyId)
.SelectMany(g => g.Select(p => new { CompanyId = g.Key, Email = p.Email }))
.Select(p => new {
p.CompanyId
Emails = Sql.Ext.ListAgg(p.Email, ",").WithinGroup.OrderBy(p.Email).ToValue()
});
}
Does anybody have an experience with Linq2Db "Sql.Ext.*" aggregate functions?
I have found the solution for my question above ... maybe it will help someone.
There is an extension method called StringAggregate that is translated into sql function LISTAGG.
public class Position {
public int CompanyId { get; set; }
public string Email { get; set; }
}
public class MyDataConection : DataConnection {
public ITable<Position> Positions => GetTable<Position>();
public IQueryable<object> GroupedEmails1 => Position
.GroupBy(p => p.CompanyId)
.Select(g => new {
CompanyId = g.Key,
Emails = g.StringAggregate(",", x => x.Email).OrderBy(x => x.Email).ToValue()
});
}

Entity Framework select distinct values from one to many table with filter

I'm trying to write a query in EF.
Consider this relationship:
The goal is to get the teachers with the full collection of students, who are active between a certain filtered period (from-to).
I wrote the following query:
var filtered = _context.Teachers
.Join(_context.Students, // Target
fk => fk.Id, // FK
pk => pk.teacherId, // PK
(fk, pk) => new { teach = fk, students = pk }
)
.Where(i => i.students.date_active >= from &&
i.students.date_active <= to)
.OrderBy(i => i.teach.name)
.Select(i => i.teach)
.Include(i => i.Students)
.AsNoTracking();
With this query, I get duplicate teachers. So I'll just add the Distinct() operator and I have the teachers. But then my teacher object still contains all the students. I want only the students for that period. Any help on how to get the good result?
List<Dto.Teacher> list = filtered.Select(i => Dto.Teacher
{
id = i.Id,
name = i.name
Students = i.Students.Select(j => new Dto.Student
{
id = i.id,
date_active = i.date_active,
}).ToList(),
}).ToList();
public class Teacher()
{
public int id { get; set; }
public string name { get; set; }
public List<Dto.Student> Students { get; set; }
}
when using an ORM such as EF the join operator should be avoided as often as possible.
In your case you may try something like the following (variation are possible):
_context.Teachers.
Where(t => t.Sudents.Any(s => s.date_active >= from &&
s.date_active <= to)
).
Select(t => new {
teacher = t,
activeStudents = t.Students.Where(s => s.date_active >= from &&
s.date_active <= to)
});

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();

How should I return two lists of objects from one Controller Action?

I am having two different list coming from same object. I want to get those two at same time as separate list or join those two same list when returning JSON object.
Here is my code.
List<User> userEntity = users.Where(s => s.Id == Id).ToList();
var GetUserNames = userEntity.SelectMany(s => s.Names.Select(u =>
new
{
Name = u.Name,
Id = u.Id
})).ToList();
var GetProfile = userEntity.SelectMany(s => s.Profile.Select(u =>
new
{
Name = u.Name,
Id = u.Id
})).ToList();
return Json(GetUserNames, JsonRequestBehavior.AllowGet);
I would prefer to do this differently: the returned items are different types. Rather than returning a bare JSON list with a type discriminator, return a multiproperty JSON object:
return Json(new
{
Names = GetUserNames,
Profiles = GetProfile
}, JsonRequestBehavior.AllowGet);
Your returned JSON will have the {Name, Id} objects separated into their types, in sketch form:
{
Names: [
{Name:"UserName", Id:"3"},
{Name:"OtherUser", Id: "4"}
],
Profiles: [
{Name:"Normal", Id:"1"},
{Name:"Admin", Id: "99"}
]
}
In most JSON.Net client side parsing scenarios (i.e. consuming a WebAPI from a WPF smart client), you automatic mapping (such as from RestSharp) would allow you to deserialize this into a class of the form
public class NameId
{
public int Id {get; set;}
public string Name {get; set;}
}
public class UserNamesResponse
{
public List<NameId> Names {get; set;}
public List<NameId> Profiles {get; set;}
}
This may be more convenient and clearer to work with than an interleaved list which must be filtered into separate lists for binding anyway.... (or fed through filtering type converters at a performance penalty in the UI binding layer...)
You could use GetUserNames.Concat(GetProfile).
List<User> userEntity = users.Where(s => s.Id == Id).ToList();
var GetUserNames = userEntity.SelectMany(s => s.Names.Select(u =>
new
{
Name = u.Name,
Id = u.Id
})).ToList();
var GetProfile = userEntity.SelectMany(s => s.Profile.Select(u =>
new
{
Name = u.Name,
Id = u.Id
})).ToList();
return Json(GetUserNames.Concat(GetProfile) , JsonRequestBehavior.AllowGet);
Enumerable.Concat Method
Update as per comments
Create a class and enum:
public class NameId
{
public string Name {get;set;}
public string Id {get;set;}
public ThisType Type { get; set; }
}
public enum ThisType
{
Username,
Profile
}
Then return that instead:
List<User> userEntity = users.Where(s => s.Id == Id).ToList();
var GetUserNames = userEntity.SelectMany(s => s.Names.Select(u =>
new NameId
{
Name = u.Name,
Id = u.Id,
Type = ThisType.Username
})).ToList();
var GetProfile = userEntity.SelectMany(s => s.Profile.Select(u =>
new NameId
{
Name = u.Name,
Id = u.Id,
Type = ThisType.Profile
})).ToList();
return Json(GetUserNames.Concat(GetProfile) , JsonRequestBehavior.AllowGet);

Categories