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();
Related
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,
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()
});
}
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)
});
I have a problem with a linq query.
I have 3 entities:
user, target and results.
each user can have multiple (or no) targets and each target can have multiple (or no) results
I want a query that returns all users including possible targets and possible results. And that's working great. But now I want to include filters to filter the targets and results. So that the query only returns users, targets and results matching these criteria.
public class User
{
public ICollection Targets {get;set;}
public string otherProperty {get;set;}
}
public class Target
{
public ICollection Results {get;set;}
public User user {get;set;}
public string Language {get;set;}
}
public class Result
{
public Target Target {get;set;}
public int score {get;set;}
}
Any EF core linq specialists that can help me?
Kind regards,
Robrecht
EDIT 1
var query =
from auditUser in _auditUserRepository.GetAll().Include(u => u.user)
.WhereIf(!input.Group.IsNullOrWhiteSpace(), u => u.Group == input.Group)
.WhereIf(!input.Filter.IsNullOrWhiteSpace(), u => u.user.FullName.ToLower().Contains(input.Filter.ToLower()))
select auditUser;
var results = query
.Include(u => u.Targets)
.ThenInclude(t => t.AuditResults)
.PageBy(input)
.ToListAsync();
await query
.SelectMany(u => u.Targets)
.WhereIf(!input.Language.IsNullOrWhiteSpace(), t => t.Target == input.Language)
.SelectMany(t => t.AuditResults)
.WhereIf(input.From != null, r => r.CompletionDate >= input.From)
.WhereIf(input.To != null, r => r.CompletionDate <= input.From)
.LoadAsync();
This is what I have so for but it has 2 problems:
When there is no result for a, the target is not included. It seems that it include or theninclude creates inner joins instead of left joins.
This doesn't filter on language or date.
I suggest you create a ViewModel class with the properties you want. Example:
public class UserViewModel
{
[Display(Name = "Other")]
public string otherProperty {get; set;}
[Display(Name = "Possible Targets")]
public List<Target> targets {get; set;}
[Display(Name = "Possible Results")]
public List<Result> results{get; set;}
}
Then in your "repository" class you can create a method to filter the results.
public List<UserViewModel> GetUserViewModelBy(int scoreFilter, string filter1= "", string filter2)
{
IQueryable<Result> query = _context.Results.Where(i => i.score==scoreFilter)).Include(x => x.Target)
.Include(x => x.Target.Results.ToList())
.Include(x => x.User)
.Include(x=>x.User.Targets.Where(i=>i.Language.ToLower().Contains(filter1)).ToList());
if (!string.IsNullOrEmpty(filter2))
{
query = query.Where(x => x.Target.Language.ToLower().Contains(filter2));
}
return query.Select(x => new UserViewModel()
{
otherProperty = x.User.otherProperty,
targets = x.User.targets,
results = x.Results
}).ToList();
}
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();