How to aggregate string using Linq 2 Entities - asp.net-mvc-3

I have an IQueryable<Model> of objects in my db. I need to cast them to IQueryable<ViewModel>
class Model
{
public Guid ModelId { get; set; }
// some properties
public virtual User Users{ get; set; }
}
class User
{
public Guid UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
class ViewModel
{
public Guid ModelId { get; set }
public string UsersName { get; set; }
}
ViewModel.UsersName should be merged string of all users first and last names.
This need to be a IQueryable since I'll use DevExpress Mvc Grid and I'll need to pass it in this way. Grid handles the paging, so it needs to IQueryable.
I've tried:
IQueryable<ViewModel> viewModel = model.Select(s => new ViewModel()
{
UsersName = s.Users
.OrderBy(d => d.LastName)
.ThenBy(d => d.FirstName)
.Aggregate(string.Empty, (s1, users) => users.FirstName + ' ' + users.Surname + ", ")
});
but it's not translatable to linq to entities. I've also tried string.join() but it won't work neither.
The result string should be for expample : "Cecelia Leblanc, Sarah Hodge" ... etc.
Thanks for any input.

Usually view models/DTOs should not be too smart, but this is a case where a little bit of smartness comes in too handy for comfort. Create a view model like:
class ViewModel
{
public Guid ModelId { get; set; }
public IEnumerable<string> UserNames { get; set; }
public string UserNamesString
{
get { return string.Join(", ", this.UserNames); }
}
}
populate it by
var models = s.Models.Select(m => new ViewModel
{ ModelId = m.ModelId,
UserNames = m.Users
.OrderBy(d => d.LastName)
.ThenBy(d => d.FirstName)
.Select(u => u.FirstName + ' '
+ u.Surname)
}).ToList();
and access the UserNamesString property from models.AsQueryable().

Related

EF Core - Relationship between 2 entities for non-PK columns

Here are my simplified entities for this questions:
public class Page
{
public Guid Id { get; set; }
public string PageType { get; set; }
public string PageName { get; set; }
public virtual ICollection<Section> Sections { get; set; }
}
public class Section
{
public Guid Id { get; set; }
public string SectionName { get; set; }
public string CmsContentId { get; set; }
[NotMapped]
public ICollection<CmsContentLanguage> Languages { get; set; }
}
public class CmsContentLanguage
{
public Guid Id { get; set; }
public string LangugeCode { get; set; }
public string CmsContentId { get; set; }
}
What I want to do is query my Page table and include the Sections navigation property, and for each Section the Languages property (which is not a db column) will be a collection of CmsContentLanguages. The response from my query would look like this:
{
"id":"6b3c680a-a5aa-4782-80ce-591f1d16abe2",
"pageName":"Description",
"pageType":"content",
"sections":[
{
"id":"e688b09e-9b1c-4094-aa04-cd044c820630",
"sectionName":"Introduction",
"cmsContentId ":"e1ad5dca-c74b-497e-856b-bf26a699e635",
"languages":[
{
"id":"11e19169-797c-4b6a-b5e8-2bdb9c1f28cc",
"languageCode":"en",
"cmsContentId ":"e1ad5dca-c74b-497e-856b-bf26a699e635"
},
{
"id":"19a0f31c-4b96-4afe-920f-40cea544eeab",
"languageId":"es",
"cmsContentId ":"e1ad5dca-c74b-497e-856b-bf26a699e635"
}
]
},
{
"id":"a53b9ace-b9a7-407d-b641-7a3c46077428",
"sectionName":"FAQs",
"cmsContentId ":"e1ad5dca-c74b-497e-856b-bf26a699e635",
"languages":[
{
"id":"11e19169-797c-4b6a-b5e8-2bdb9c1f28cc",
"languageCode":"en",
"cmsContentId ":"e1ad5dca-c74b-497e-856b-bf26a699e635"
},
{
"id":"19a0f31c-4b96-4afe-920f-40cea544eeab",
"languageId":"es",
"cmsContentId ":"e1ad5dca-c74b-497e-856b-bf26a699e635"
}
]
}
]
}
Multiple Section records can have the same CmsContentId value. Section.Languages is not a navigation property (nor do I think it should be).
Then I want to do a Linq query like this:
var guidId = Guid.Parse("6b3c680a-a5aa-4782-80ce-591f1d16abe2");
var query = dbContext.Pages
.Include(x => x.Sections)
.FirstOrDefault(p => p.Id == guidId);
At this point I have the response data I want, but the Languages property of each Section entity is null. This is where I'm lost as to how to setup relationships or do a query to get this data.
You are looking for the ThenInclude method. Here is how you use it.
var query = dbContext.Pages
.Include(x => x.Sections)
.ThenInclude(x => x.Languages)
.FirstOrDefault(p => p.Id == guidId);

Checking grandchildren records to return grand-parent. Linq

I have different roles and each user can have multiple roles. Each role is connected to customer record in different way, e.g. a business analyst has many-to-many relation to project and each customer has many projects; whereas a customer record can have only one project manager associated to it.
public class Customer
{
public CustomerProjectManager ProjectManager { get; set; }
public ICollection<Project> Projects{ get; set; }
...
}
public class Project
{
public ICollection<ProjectBusinessAnalyst> BusinessAnalysts { get; set; }
public ICollection<ProjectDeveloper> ProjectDevelopers { get; set; }
...
}
public class ProjectDeveloper
{
public int Id { get; set; }
public Project Project{ get; set; }
public int ProjectId { get; set; }
public string DeveloperId { get; set; }
public string DeveloperEmail { get; set; }
public string DeveloperName { get; set; }
}
public class CustomerProjectManager
{
public int Id { get; set; }
public ICollection<Customer> Customers { get; set; }
public string ProjectManagerId { get; set; }
public string ProjectManagerEmail { get; set; }
public string ProjectManagerName { get; set; }
public CustomerProjectManager()
{
Customers = new List<Customer>();
}
}
I need to fetch customer records on basis of roles. To explain further, I need to combine multiple customer lists fetched on the basis of different roles assigned to a single user. I am unable to form right linq query.
I have a sample query, mentioned below, which sometimes returns the right records but if I have a new user and no customers are assigned to this user, the query returns all existing customers. Its important for me that all the combination and filtration is done in Iqueryable
Please help!
public async Task<List<Customer>> FetchCustomers(string userId, List<string> userRoles, string userEmail)
{
if (userRoles.Contains("Admin"))
{
customer = _context.Customers;
}
else if (userRoles.Contains("Project Manager") ||
userRoles.Contains("Business Analyst") ||
userRoles.Contains("Developer"))
{
if (userRoles.Contains("Project Manager"))
{
customers = customers.Where(c => c.ProjectManager.ProjectManagerId == userId
|| c.Projects.Any(op =>
op.ProjectsCompleted.Any(assignee =>
assignee.UserId == userId)));
}
if (userRoles.Contains("Business Analyst"))
{
var allPossibleCustomers = _context.Customers.Where(c =>
c.Projects.Any(op => op.BusinessAnalysts.Any(ba => ba.BusinessAnalystId == userId)));
customers = customers?.Union(allPossibleCustomers) ?? allPossibleCustomers;
}
if (userRoles.Contains(Roles.Developer.GetDescription()))
{
var allPossibleCustomers = _context.Customers.Where(c =>
c.Projects.Any(op => op.PREDevDevelopersAssigned.Any(ba => ba.DeveloperId == userId)));
customers = customers?.Union(allPossibleCustomers) ?? allPossibleCustomers;
}
}
var listData = await PagingList<Customer>.CreatePageAsync(customers, page, limit);
return listData;
}
Apparently I was trying to return the wrong list. The linq query is correct.

Fluent LINQ EF Core - Select filtered child property

I have the classes below:
public class User
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class ParentEntity
{
public Guid Id { get; set; }
public string SomeProperty { get; set; }
public ICollection<ChildEntity> ChildEntities { get; set; }
}
public class ChildEntity
{
public Guid Id { get; set; }
public int Vote { get; set; }
public Guid UserId { get; set; }
}
public class ReturnedParentDto
{
public Guid Id { get; set; }
public string SomeProperty { get; set; }
public int Vote { get; set; }
}
I want to be able to return a full list of ParenEntities, but take an Id of the User class (UserClassId), then filter the ParentEntity's ICollection where UserUid = UserClassId, so only 1 ChildEntity is always returned. Then I would want to extract a specific field from that returned ChildEntity and merge it with the ParentEntity fields. The end result should be like the ReturnedParentDto.
I want to do it in the style like
ParentEntities.Include(v => v.ChildEntities).ToList()
That seems to be possible in EF Core 5, but my project is in 3.1.
You can do this as below
Approach 1:
var result = result = parentEntities.Include(x => x.ChildEntities.Where(y => y.UserId == userId))
.Select(x => new ReturnedParentDto {
Id = x.Id,
SomeProperty = x.SomeProperty,
Vote = x.ChildEntities.FirstOrDefault()?.Vote // userId is the variable here
});
Approach 2:
var result = parentEntities.Select(x =>
new ReturnedParentDto {
Id = x.Id,
SomeProperty = x.SomeProperty,
Vote = x.ChildEntities.FirstOrDefault(y => y.UserId == userId)?.Vote // userId is the variable here
});

Asp.Net Core / Entity Framework Core - System.Linq.Enumerable+SelectEnumerableIterator`2[MyClass,System.String]

I am trying to return a json array of my 3rd level depth related data, the issue here is that I get the result with the right property name but with a non clear value content, I failed to find a similar case to solve it. From the returned value message it looks like I am returning a queryable instead of the final result and I need to iterate over it, I've tried several ways to achive that but failed to find the right one.
The json result:
[
{
"registeredYear": "System.Linq.Enumerable+SelectEnumerableIterator`2[MyPath.Groups.GroupYear,System.String]"
}
]
The api endpoint
public async Task<ActionResult<IEnumerable<UserGroup>>> GetUserGroupYears(string email, string groupName)
{
var groupYears = await _repo.GetUserGroupYears(email, groupName);
var mappedEntities = _mapper.Map<GroupYearsListDto[]>(groupYears);
return Ok(mappedEntities);
}
The Repository method
public async Task<IEnumerable<UserGroup>> GetUserGroupYears(string email, string groupName)
{
var userGroupYears = _context.UserGroups
.Include(uo => uo.Group.GroupYears)
.ThenInclude( oy => oy.Year)
.Where(uo => uo.Group.Name == groupName && uo.Email == email );
return await userGoupYears.ToArrayAsync();
}
The used classes:
public class UserGroup
{
public string Email { get; set; }
public string UserId { get; set; }
public virtual User User { get; set; }
public string GroupId { get; set; }
public virtual Group Group { get; set; }
}
public class Group
{
public string Name { get; set; }
public virtual ICollection<UserGroup> Users { get; set; }
public virtual ICollection<GroupYear> GroupYears { get; }
}
public class GroupYear {
public string GroupId { get; set; }
public virtual Group Group { get; set; }
public string YearId { get; set; }
public virtual Year Year { get; set; }
public string RegisteredYear { get; set; }
}
The data transfer object and the mapping:
public class GroupYearsListDto
{
public string RegisteredYear { get; set; }
}
public CoreMappingProfiles()
{
CreateMap<UserGroup, GroupYearsListDto>()
.ForMember(
dest => dest.RegisteredYear,
opt => opt.MapFrom(src => src.Group.GroupYears.Select(x => x.RegisteredYear))
);
}
Update: Attaching a debugger shows that the repository method is returning an IQueryable including the correct values and the controller method makes something wrong when mapping. So I think the following line is responsible of that wrong result:
var mappedEntities = _mapper.Map<GroupYearsListDto[]>(GroupYears);
You are getting this JSON result:
[
{
"registeredYear": "System.Linq.Enumerable+SelectEnumerableIterator`2[MyPath.Groups.GroupYear,System.String]"
}
]
Because you are mapping an IEnumerable<string> to a string, as I mentioned in my comment. So essentially you are getting the same as:
CreateMap<UserGroup, GroupYearsListDto>()
.ForMember(
dest => dest.RegisteredYear,
opt => opt.MapFrom(src =>
{
IEnumerable<string> registeredYears = src.Group.GroupYears.Select(x => x.RegisteredYear);
return registeredYears.ToString();
})
);
And registeredYears.ToString() is "System.Linq.Enumerable+SelectEnumerableIterator`2[MyPath.Groups.GroupYear,System.String]".
I imagine you will either:
Only have one - so do something like: src.Group.GroupYears.Select(x => x.RegisteredYear).Single()
Have multiples - so do something like: string.Join(", ", src.Group.GroupYears.Select(x => x.RegisteredYear))
You have many options, but you need to actually return a string to that property or else you will just get the ToString() version of IEnumerable<string>.
UPDATE:
Based on your comments below, you can try this:
Repository:
public IQueryable<GroupYear> GetGroupYears(string email, string groupName)
{
return _context
.UserGroups
.Where(x => x.Group.Name == groupName && x.Email == email)
.SelectMany(x => x.Group.GroupYears);
}
Controller:
public async Task<ActionResult<GroupYearsListDto[]>> GetGroupYears(string email, string groupName)
{
var groupYears = _repo.GetGroupYears(email, groupName);
var projection = _mapper.ProjectTo<GroupYearsListDto>(groupYears)
var mappedEntities = await projection.ToArrayAsync();
return Ok(mappedEntities);
}
Profile:
CreateMap<GroupYears, GroupYearsListDto>();

most efficient Entity Framework Code First method of flattening / projecting parent entity with specific child

I have a parent entity Widget with core members and multiple WidgetTranslation children that have language translated members i.e. Description text available in English, French, German etc.
e.g.
public class Widget
{
public int Id { get; set; }
public string Code { get; set; }
public virtual ICollection<WidgetTranslation> WidgetTranslations { get; set; }
}
public class WidgetTranslation
{
public int WidgetId { get; set; }
public virtual Widget Widget { get; set; }
public int LanguageId { get; set; }
public virtual Language Language { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Summary { get; set; }
}
What is the most efficient method of querying the widget collection, flattening for a given LanguageId & projecting to a TranslatedWidget DTO
public class TranslatedWidget
{
public int Id { get; set; }
public string Code { get; set; }
public int LanguageId { get; set; }
public virtual Language Language { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Summary { get; set; }
}
Given languageId I've started with
DbSet.Select(w => new TranslatedWidget
{
Id = w.Id,
Code = w.Code,
LanguageId = w.LanguageId,
Name = w.WidgetTranslations.First(wt=>wt.LanguageId == languageId).Name,
Description = w.WidgetTranslations.First(wt=>wt.LanguageId == languageId).Description,
Summary = w.WidgetTranslations.First(wt=>wt.LanguageId == languageId).Summary
});
But I've a feeling this is inefficient and won't scale for more properties on WidgetTranslation.
Thanks
Use SelectMany to flatten structures via a single join:
var widgetQuery = from w in dbSet.Widgets
from wt in w.WidgetTranslations
where wt.Language == languageId
select new TranslatedWidget
{
Id = w.Id,
Code = w.Code,
LanguageId = w.LanguageId,
Name = wt.Name,
Description = wt.Description,
Summary = wt.Summary
});
I'm assuming here that you only have a single translation for each widget in a given language.
I would move Name, Description and Summary into a nested class of your DTO...
public class TranslatedWidgetTranslation
{
public string Name { get; set; }
public string Description { get; set; }
public string Summary { get; set; }
}
public class TranslatedWidget
{
public int Id { get; set; }
public string Code { get; set; }
public int LanguageId { get; set; }
public TranslatedWidgetTranslation Translation { get; set; }
}
Then you can project into that class and need First only once which would result in only one TOP(1) subquery in SQL instead of three:
DbSet.Select(w => new TranslatedWidget
{
Id = w.Id,
Code = w.Code,
LanguageId = languageId,
Translation = w.WidgetTranslations
.Where(wt => wt.LanguageId == languageId)
.Select(wt => new TranslatedWidgetTranslation
{
Name = wt.Name,
Description = wt.Description,
Summary = wt.Summary
})
.FirstOrDefault()
});
You must use FirstOrDefault here, First is not supported in a LINQ-to-Entities projection.
If you don't want that nested type you can project into anonymous types first and then convert into your final class, but the code will be a bit longer:
DbSet.Select(w => new
{
Id = w.Id,
Code = w.Code,
LanguageId = languageId,
Translation = w.WidgetTranslations
.Where(wt => wt.LanguageId == languageId)
.Select(wt => new
{
Name = wt.Name,
Description = wt.Description,
Summary = wt.Summary
})
.FirstOrDefault()
})
.AsEnumerable()
.Select(x => new TranslatedWidget
{
Id = x.Id,
Code = x.Code,
LanguageId = x.LanguageId,
Name = x.Translation != null ? x.Translation.Name : null,
Description = x.Translation != null ? x.Translation.Description : null,
Summary = x.Translation != null ? x.Translation.Summary : null
});

Resources