C# Linq rollup child array with parent field value - c#

Take the following example:
public class Team
{
public int Id { get; set; }
public string TeamName { get; set; }
public Members[] Members { get; set; }
}
public class Members
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
If I have a C# list of teams comprised of the following:
[
{
"Id" : 1,
"TeamName" : "A",
"Members": [
{
"Id": 1,
"FirstName": "Arthur",
"LastName": "Nudge"
},
{
"Id": 2,
"FirstName": "Ken",
"LastName": "Shabby"
}
]
},
{
"Id" : 2,
"TeamName" : "B",
"Members": [
{
"Id": 1,
"FirstName": "Spiny",
"LastName": "Norman"
},
{
"Id": 2,
"FirstName": "Raymond",
"LastName": "Luxury-Yacht"
}
]
}
]
How can I write a Linq query to giva a result of Members but also with the Team Id e.g.
{
"TeamId": 1,
"Id": 1,
"FirstName": "Arthur",
"LastName": "Nudge"
},
{
"TeamId": 1,
"Id": 2,
"FirstName": "Ken",
"LastName": "Shabby"
},
{
"TeamId": 2,
"Id": 1,
"FirstName": "Spiny",
"LastName": "Norman"
}
I can return a Member list from the Team list by using
Teams.SelectMany(t => t.Members).ToList(); but how do I also include the TeamId

Use SelectMany in this way:
var q = Teams
.SelectMany(t => t.Members
.Select(tm => new{ TeamId = t.Id, tm.Id, tm.FirstName, tm.LastName }));

You could use a custom LINQ query to combine this into a list of anonymous objects with the fields you need:
var list = (from team in Teams
from member in team.Members
select new
{
TeamId = team.Id,
member.Id,
member.FirstName,
member.LastName
}).ToList();

Use a combination of SelectMany and Select:
teams.SelectMany(team => team.Members.Select(member => new
{
TeamId = team.Id,
MemberId = member.Id,
member.FirstName,
member.LastName
})).ToList();
The inner Select will map each member and the teams Id to an anonymous type.
The outer SelectMany flattens the nested IEnumerable<...> into a single IEnumerable<...>

Related

Group and Non Group list items at same level in C#

I have list that has few items and I want the final result as a combination of groups and items at a same level.
Example:
public class Response
{
public int? Id { get; set; }
public int? Parent { get; set; }
public string ItemDescription { get; set; }
public string CommonDescription { get; set; }
public IEnumerable<Response> Children { get; set; }
}
var tempResult = GetFromdatabase(); //getting records from database and the type is List<Response>
//The above variable contains all the data as flat structure, means all the items where Parent is null and items with Parent
Now I want the final result to be looks like this:
[
{
"id": 1,
"itemDescription": "Desc 1",
"commonDescription": "Com. desc 1"
},
{
"id": 2,
"itemDescription": "Desc 2",
"commonDescription": "Com. desc 2"
},
{
"Parent": 3,
"Children":[
{
"id": 4,
"itemDescription": "Desc 4",
"commonDescription": "Com. desc 4"
},
{
"id": 5,
"itemDescription": "Desc 5",
"commonDescription": "Com. desc 5"
}
]
}
]
I am trying to group by with "Parent", but failing where it's null. I also tried to append in the select, but the syntax seems not supported.
use Lookup like this:
IEnumerable<Response> GetHierarhicalResponse(IEnumerable<Response> items) {
var lookup = items.Where(i => i.Parent != null).ToLookup(i => i.Parent);
foreach(var item in items)
item.Children = lookup[item.Id];
return items.Where(i => i.Parent == null);
}

Why is Entity Framework behaving as expected in a MVC app and totally diferent in a WebAPI?

I've been working with Entity Framework 6 for some time now and today I started with a new Web API project and as soon as I execute a query that includes some other entity I receive an error like this:
System.Text.Json.JsonException: A possible object cycle was detected. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32.
The thing and do not understand is that, when I used the same Eager Loading while running a regular MVC App it will bring me exactly what I need, nothing else.
So far this is the relevant code I'm working with:
Beer.cs
public class Beer
{
public int Id { get; set; }
[Required, StringLength(100)]
public string Name { get; set; } = null!;
public int BrandId { get; set; }
public virtual Brand Brand { get; set; } = null!;
}
Brand.cs
public class Brand
{
public int Id { get; set; }
[Required, StringLength(100)]
public string Name { get; set; } = null!;
public virtual ICollection<Beer> Beers { get; set;} = null!;
}
BeerRepository.cs
public class BeerRepository : IBeerRepository
{
private readonly BeerContext _context;
public BeerRepository(BeerContext context)
{
_context = context;
}
public async Task CreateBeerAsync(Beer beer)
{
if(beer == null)
throw new ArgumentNullException(nameof(beer));
await _context.Beers.AddAsync(beer);
}
//BUG: This is the one causing me issues!
public async Task<ICollection<Beer>> GetAsync() => await _context.Beers.Include(b => b.Brand).ToListAsync();
public async Task<Beer> GetByIdAsync(int id) => await _context.Beers.FirstAsync(b => b.Id == id);
public async Task SavaChangesAsync()
{
await _context.SaveChangesAsync();
}
}
BeerContext.cs
#nullable disable
public partial class BeerContext : DbContext
{
public DbSet<Beer> Beers { get; set; }
public DbSet<Brand> Brands { get; set; }
public BeerContext(DbContextOptions opt) : base(opt)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//BEER ENTITY
modelBuilder.Entity<Beer>(entity => {
entity.HasData(
new Beer { Id = 1, BrandId = 1, Name = "Torobayo" },
new Beer { Id = 2, BrandId = 1, Name = "Bock" },
new Beer { Id = 3, BrandId = 1, Name = "Lager Sin Filtrar" },
new Beer { Id = 4, BrandId = 2, Name = "Super Dry" },
new Beer { Id = 5, BrandId = 2, Name = "Soukai" }
);
});
//BRAND ENTITY
modelBuilder.Entity<Brand>(entity => {
entity.HasData(
new Brand{ Id = 1, Name = "Kunstman" },
new Brand{ Id = 2, Name = "Asahi" }
);
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}
BeersController.cs
[Route("api/[controller]")]
[ApiController]
public class BeersController : ControllerBase
{
private readonly IBeerRepository _repository;
private readonly IMapper _mapper;
public BeersController(IMapper mapper, IBeerRepository repository)
{
_mapper = mapper;
_repository = repository;
}
[HttpGet]
public async Task<ActionResult<IEnumerable<BeerReadDTO>>> GetAllBeers()
{
var beers = await _repository.GetAsync();
return Ok(_mapper.Map<IEnumerable<BeerReadDTO>>(beers));
}
}
I also changed the Program.cs file like this:
builder.Services.AddControllers().AddJsonOptions(opt => {
opt.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
});
It works (at least it will remove the error) but instead I'm getting this "nice" data!
[
{
"id": 1,
"name": "Torobayo",
"brand": {
"id": 1,
"name": "Kunstman",
"beers": [
{
"id": 1,
"name": "Torobayo",
"brandId": 1,
"brand": null
},
{
"id": 2,
"name": "Bock",
"brandId": 1,
"brand": null
},
{
"id": 3,
"name": "Lager Sin Filtrar",
"brandId": 1,
"brand": null
}
]
}
},
{
"id": 2,
"name": "Bock",
"brand": {
"id": 1,
"name": "Kunstman",
"beers": [
{
"id": 1,
"name": "Torobayo",
"brandId": 1,
"brand": null
},
{
"id": 2,
"name": "Bock",
"brandId": 1,
"brand": null
},
{
"id": 3,
"name": "Lager Sin Filtrar",
"brandId": 1,
"brand": null
}
]
}
},
{
"id": 3,
"name": "Lager Sin Filtrar",
"brand": {
"id": 1,
"name": "Kunstman",
"beers": [
{
"id": 1,
"name": "Torobayo",
"brandId": 1,
"brand": null
},
{
"id": 2,
"name": "Bock",
"brandId": 1,
"brand": null
},
{
"id": 3,
"name": "Lager Sin Filtrar",
"brandId": 1,
"brand": null
}
]
}
},
{
"id": 4,
"name": "Super Dry",
"brand": {
"id": 2,
"name": "Asahi",
"beers": [
{
"id": 4,
"name": "Super Dry",
"brandId": 2,
"brand": null
},
{
"id": 5,
"name": "Soukai",
"brandId": 2,
"brand": null
}
]
}
},
{
"id": 5,
"name": "Soukai",
"brand": {
"id": 2,
"name": "Asahi",
"beers": [
{
"id": 4,
"name": "Super Dry",
"brandId": 2,
"brand": null
},
{
"id": 5,
"name": "Soukai",
"brandId": 2,
"brand": null
}
]
}
}
]
This is the query running in the background...
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (49ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT [b].[Id], [b].[BrandId], [b].[Name], [b0].[Id], [b0].[Name]
FROM [Beers] AS [b]
INNER JOIN [Brands] AS [b0] ON [b].[BrandId] = [b0].[Id]
And what I actually need is this:
[
{
"id": 1,
"name": "Torobayo",
"brand":"Kunstman"
},
{
"id": 2,
"name": "Bock",
"brand":"Kunstman"
},
{
"id": 3,
"name": "Lager Sin Filtrar",
"brand":"Kunstman"
},
{
"id": 4,
"name": "Super Dry",
"brand":"Asahi"
},
]
Any help will be a good start since I ran out of ideas. Thank you very much!
EDIT:
So, i decided to also post my DTO File and also the Automapper configuration... looks like it is not clear that i'm actually mapping from Beer entity to the corresponding DTO.
BeerReadDTO.cs
public class BeerReadDTO
{
public int Id { get; set; }
public string Name { get; set; }
public Brand Brand { get; set; }
}
AutoMapperConf.cs
public class AutoMapperConf : Profile
{
public AutoMapperConf()
{
// Source -> Target
CreateMap<Beer,BeerReadDTO>();
}
}
Okay.. thanks to Kamran Khan who pointed me in the right direction!... the issue was fixed just by removing the navigation property from Brands to Beers and changing the Brands for the DTO to a string.
Here are the files i changed and it worked fine!
public class Brand
{
[Key]
public int Id { get; set; }
[Required, StringLength(100)]
public string Name { get; set; } = null!;
//public virtual ICollection<Beer> Beers { get; set;} = null!;
}
public class BeerReadDTO
{
public int Id { get; set; }
public string Name { get; set; }
public string Brand { get; set; }
}
public class AutoMapperConf : Profile
{
public AutoMapperConf()
{
// Source -> Target
CreateMap<Beer,BeerReadDTO>()
.ForMember(d => d.Brand, opt => opt.MapFrom(src => src.Brand.Name));
}
}
Thank you very much!

Linq where doesn't exits in current context

I have list of User and Department Like below:
public class Users
{
public int id { get; set; }
public string jobTitle { get; set; }
public int officeLocation { get; set; }
public string userPrincipalName { get; set; }
}
public class Departments
{
public int id { get; set; }
public string department { get; set; }
public string displayName { get; set; }
}
Here is my Json
"Departments": [
{
"department": "Azure",
"id": "1",
"displayName": "Thad"
},
{
"department": "Visual Studio",
"id": "2",
"displayName": "Scott Hansalman"
},
{
"department": "C#",
"id": "3",
"displayName": "Paul"
}
]
"Users": [
{
"jobTitle": "Senior Program manager ",
"officeLocation": "Redmond",
"userPrincipalName": "thad.teams.com",
"id": "1"
},
{
"jobTitle": ""Technical Lead,
"officeLocation": "Redmond",
"userPrincipalName": "scott.teams.com",
"id": "2"
},
{
"jobTitle": "Development Engineer II",
"officeLocation": "Canada",
"userPrincipalName": "paul.teams.com",
"id": "3"
}
]
I want to join two list using linq where I would pass id of user
and get department.
I tried like this
var query = from user in Users
join dept in Departments
on user.id equals dept.id
select new
{
user.id ,
user.jobTitle,
user.officeLocation,
dept.department
}.where(id = 1) ;
But where clause doesn't seems okay, say's where doesn't exits in current context
This will work, you're just missing some ( )s, and there is a syntax error in the where...
(from user in Users
join dept in Departments
on user.id equals dept.id
select new
{
user.id ,
user.jobTitle,
user.officeLocation,
dept.department
}).Where(c => c.id == 1) ;
But usually, the Where is done upfront, as query expression.

Is there a better and optimized way to create a nested list with C#?

I want to create a flat list based on three Enumerable lists. The data of these list are stored in three tables in my DB : Continent, Country, City.
To do so, i fetch trough 3 loops for each Entity, so if i add another entity called District, i need a fourth loop and so on. So there's a way to optimize my code to use LooKup or a cleaner Linq Syntax and also improve the performance ? See my code below :
Continent
Id Name
1 NorthAmerica
2 Europe
Country
Id Name ContinentId
1 USA 1
2 CANADA 1
3 FRANCE 2
4 ENGLAND 2
Country
Id Name CountryId
1 PARIS 3
2 MONTREAL 2
3 NEW YORK 1
4 LYON 3
5 LONDRES 4
In C#, i get the data for each table from a repository
var lists = new List<Select> { };
var continents = _unitOfWork.ContinentRepository.Get().
Select(x => new { id = x.Id, name = x.name });
var countries = _unitOfWork.CountryRepository.Get().
Select(x => new { id = x.Id, continentId = x.ContinentId, name = x.name });
var cities = _unitOfWork.CityRepository.Get().
Select(x => new { id = x.Id, countryId = x.CountryId, name = x.name });
if (continents.Count() > 0)
{
foreach (var continent in continents)
{
lists.Add(new Select { Id = continent.id, Value = continent.name, Level = "first-level" });
foreach (var country in countries.Where(x => x.continentId == continent.id))
{
lists.Add(new Select { Id = country.id , Value = country.name, Level = "second-level" });
foreach (var city in cities.Where(x => x.countryId == country.id))
{
lists.Add(new Select { Id = city.id , Value = city.name, Level = "third-level" });
}
}
}
}
NORTHAMERICA
USA
NEW YORK
CANADA
MONTREAL
EUROPE
FRANCE
LYON
PARIS
...
The class attribute will add the spaces (indentation), so i need it.
[
{
"id": 1
"value": "NorthAmerica",
"class": "first-level"
},
{
"id": 1
"value": "USA",
"class": "second-level"
},
{
"id": 3
"value": "NEW YORK",
"class": "third-level"
},
{
"id": 2
"value": "CANADA",
"class": "second-level"
},
{
"id": 3
"value": "MONTREAL",
"class": "third-level"
},
{
"id": 2
"value": "Europe",
"class": "first-level"
},
{
"id": 1
"value": "FRANCE",
"class": "second-level"
},
{
"id": 3
"value": "PARIS",
"class": "third-level"
},
{
"id": 4
"value": "LYON",
"class": "third-level"
},
{
"id": 4
"value": "ENGLAND",
"class": "second-level"
},
"id": 5
"value": "LONDRES",
"class": "third-level"
},
]
I dont know why you are doing something like that in those days where there is library that could handle this much better like
EntityFramework and EntityWorker.Core
In Entityworker.Core this could easly be done with a single select
here is an example of your modules
public class Continent {
[PrimaryKey]
public int Id { get; set; }
public string Name { get; set; }
public List<Country> Countries { get; set; }
}
public class Country {
[PrimaryKey]
public int Id { get; set; }
public string Name { get; set; }
[ForeignKey(typeof(Continent))]
public int ContinentId { get; set; }
public List<City > Cities { get; set; }
}
public class City {
[PrimaryKey]
public int Id { get; set; }
public string Name { get; set; }
[ForeignKey(typeof(Country))]
public int CountryId { get; set; }
}
now all you have to do is get the data and loadChildren
using (var rep = new Repository())
{
// this will load all Continent and Countries and Cities all togather with a single call
List<Continent> Continents = rep.Get<Continent>().Where(x=> x.Id == 54).LoadChildren().Execute();
}
here more information about the library
https://www.codeproject.com/Tips/1222424/EntityWorker-Core-An-Alternative-to-Entity-Framewo
Entityframework can do the same thing except instead of LoadChildren you do Include()
http://www.entityframeworktutorial.net/basics/context-class-in-entity-framework.aspx

How can I return data in this specific way

I am building an angularjs app with a c# backend with dapper micro orm which gets me data from the database.
I wanted the data returned to look like this:
[{
"CategoryId": 1,
"CategoryName": "cat1",
"Items": [{
"ItemId": 1,
"ItemName": "Item1"
}, {
"ItemId": 2,
"ItemName": "Item2"
}]
}, {
"CategoryId": 2,
"CategoryName": "cat2",
"Items": [{
"ItemId": 3,
"ItemName": "Item3"
}, {
"ItemId": 4,
"ItemName": "Item4"
}]
}
]
but this is what my data looks like
[{
"CategoryId": 1,
"CategoryName": "cat1",
"Items": {
"ItemId": 1,
"ItemName": "Item1"
}
}, {
"CategoryId": 1,
"CategoryName": "cat1",
"Items": {
"ItemId": 2,
"ItemName": "Item2"
}
},
{
"CategoryId": 2,
"CategoryName": "cat2",
"Items": {
"ItemId": 3,
"ItemName": "Item3"
}
},
{
"CategoryId": 2,
"CategoryName": "cat2",
"Items": {
"ItemId": 4,
"ItemName": "Item4"
}
}
]
This is what I have in my repository:
public IEnumerable<CategoryModel> GetAllCategories()
{
using (var conn = ConnectionSettings.GetSqlConnection())
{
const string sql = #" SELECT
c.CategoryName,
c.CategoryId,
i.ItemId,
i.ItemName,
i.CategoryId
from Category c
INNER JOIN item i ON c.CategoryId = i.CategoryId";
var categoriesList = conn.Query<CategoryModel, ItemModel, CategoryModel>(sql, (cat, it) =>
{
cat.Item = it;
return cat;
}, splitOn: "ItemId");
return categoriesList;
}
}
And these are my Category and Item Models
public class CategoryModel
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public ItemModel Item { get; set; }
}
public class ItemModel
{
public int ItemId { get; set; }
public int CategoryId { get; set; }
public string ItemName { get; set; }
}
Could someone please point me in the right direction?
Please let me know what I am doing wrong here.
Thanks in advance
I would suggest a following approach to achieve the expected result:
Modify the entities as shown below, use Newtonsoft Json attributes to ignore the CategoryId in the ItemModel, to avoid the serialization
[JsonObject]
public class CategoryModel
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public IEnumerable<ItemModel> Items { get; set; }
}
[JsonObject]
public class ItemModel
{
public int ItemId { get; set; }
[JsonIgnore]
public int CategoryId { get; set; }
public string ItemName { get; set; }
}
Now for fetching data, since its one to many mapping inside the Category Model, which contains multiple Item Model, use the QueryMultiple to fetch both the result sets separately, code will look like:
public IEnumerable<CategoryModel> GetAllCategories()
{
using (var conn = ConnectionSettings.GetSqlConnection())
{
const string sql = #" SELECT
c.CategoryName,
c.CategoryId from Category c;
SELECT
i.ItemId,
i.ItemName,
i.CategoryId
from item i";
var reader = conn.QueryMultiple(sql);
IEnumerable<CategoryModel> categoriesList = reader.Read<CategoryModel>();
IEnumerable<ItemModel> itemList = reader.Read<ItemModel>();
foreach(Category c in categoriesList)
{
c.items = itemList.Where(i => i.CategoryId = c.CategoryId)
}
return categoriesList;
}
}

Categories