Reduce size of JSON produced by System.Text.Json - c#

I am building a web app using Blazor WASM with a .NET backend. We are hitting a problem, where the json returned from the API is bigger than we want/need it to be. Maybe the right solution is to write custom serializer methods for each action on the controllers, but was hoping there was a simpler way of reducing the size of the serialized json without having to do that.
Consider the following classes:
public class Comment
{
[Key]
public int CommentID { get; set; }
public int FilterSelectionGroupID { get; set; }
[InverseProperty("Comments")]
public virtual FilterSelectionGroup FilterSelectionGroup { get; set; }
[Required(ErrorMessage = "The comment should not be empty.")]
public string Comment { get; set; }
}
public class FilterSelectionGroup
{
[Key]
public int FilterSelectionGroupID { get; set; }
[ForeignKey("FilterSelectionGroupID")]
public virtual ICollection<Comment> Comments { get; set; }
[ForeignKey("FilterSelectionGroupID")]
public virtual ICollection<FilterSelectionGroupValueAssigment> FilterSelectionGroupValueAssigments { get; set; }
}
I get them from the database using EFCore and then return the data from the controller:
[HttpGet]
public async Task<IActionResult> Get([FromQuery] PaginationFilter pagination)
{
var validPagination = new PaginationFilter(pagination.PageNumber, pagination.PageSize);
var items = await _powerBICommentService.Get(validPagination);
return Ok(new PagedResponse<Comment>(items.Data, validPagination.PageNumber, validPagination.PageSize, items.TotalRecords));
}
Finally the resulting json is something like this. Note that the comments get repeated in the FilterSelectionGroup (it doesn't cause a loop because I have set o.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles on Startup.cs).
This gets even worse, because in the real model, there are other classes that have references to the comments, so I can have the comments repeating 3 or 4 times in the final structure.
What I'd like would be of a way to say if class Comment has been serialized on "level 1" then don't serialized it again.
"data": [
{
"CommentID": 1,
"FilterSelectionGroupID": 1,
"FilterSelectionGroup": {
"FilterSelectionGroupID": 1,
"Comments": [
null,
{
"CommentID": 2,
"FilterSelectionGroupID": 1,
"FilterSelectionGroup": null
},
{
"CommentID": 3,
"FilterSelectionGroupID": 1,
"FilterSelectionGroup": null
}]
}
},
{
"CommentID": 2,
"FilterSelectionGroupID": 1,
"FilterSelectionGroup": {
"FilterSelectionGroupID": 1,
"Comments": [
{
"CommentID": 1,
"FilterSelectionGroupID": 1,
"FilterSelectionGroup": null
},
null,
{
"CommentID": 3,
"FilterSelectionGroupID": 1,
"FilterSelectionGroup": null
}]
}
},
{
"CommentID": 3,
"FilterSelectionGroupID": 1,
"FilterSelectionGroup": {
"FilterSelectionGroupID": 1,
"Comments": [
{
"CommentID": 1,
"FilterSelectionGroupID": 1,
"FilterSelectionGroup": null
},
{
"CommentID": 2,
"FilterSelectionGroupID": 1,
"FilterSelectionGroup": null
},
null
]
}
}
]
Thanks for any help/suggestions.

Related

.NET Web API + MySQL: Issues with POST/PUT methods asking for too many parameters and GET method not giving binded entities

As a beginner in .Net and in api development, I encountered an issue recently.
My API is linked to a MySQL database but it seems that I may have not set it up correctly.
For example, I have these tables: 'user' and 'profession'.
One user can have one profession but many users could share the same profession.
I followed a tutorial on Youtube to link my api to the database and I managed it for the methods GET, GETById and DELETE only.
The first issue I get is that when I use the GET method for one entity of the database, the api only returns data about this entity and not the one it is binded to. For example, if i try to get the users, I don't have any information about the users' professions:
{
"idUser": 1,
"userProfessionId": 1,
"userProfessionFieldId": 1,
"userName": "user_test",
"userMail": "mail#test.com",
"userCompany": "TestCompany",
"userPicture": null,
"userProfession": null,
"userProfessionField": null,
"dataUsages": []
}
In the database, both the profession_id and the profession_idfield_id, which are foreign keys to other tables, are worth '1'.
This is my GET method:
// GET: api/Users
[HttpGet]
public async Task<ActionResult<IEnumerable<User>>> GetUsers()
{
return await _context.Users.ToListAsync();
}
How can I display information about the profession instead of having a 'null' value ?
My second issue happens when I want to post an user for example.
I have to fill every information of the binded tables as for example:
{
"idUser": 0,
"userProfessionId": 0,
"userProfessionFieldId": 0,
"userName": "string",
"userMail": "string",
"userCompany": "string",
"userPicture": "string",
"userProfession": {
"idProfession": 0,
"professionName": "string",
"professionFields": [
{
"idProfessionField": 0,
"professionFieldName": "string",
"professionId": 0,
"profession": "string",
"users": [
"string"
]
}
],
"users": [
"string"
]
},
"userProfessionField": {
"idProfessionField": 0,
"professionFieldName": "string",
"professionId": 0,
"profession": "string",
"users": [
"string"
]
},
"dataUsages": [
{
"idDataUsage": 0,
"openDataId": 0,
"dateOfUsage": "2022-09-30T11:48:02.777Z",
"dataFormatId": 0,
"languageId": 0,
"isDownloaded": 0,
"usedBy": 0,
"dataFormat": {
"idDataFormat": 0,
"dataFormatName": "string",
"dataUsages": [
"string"
]
},
"language": {
"idDataLanguage": 0,
"dataLanguageName": "string",
"dataUsages": [
"string"
]
},
"openData": {
"idData": 0,
"dataUrl": "string",
"dataOpenLicense": 0,
"dataOwnerId": 0,
"updateFrequencyId": 0,
"dataThemeId": 0,
"dataOwner": {
"idDataOwner": 0,
"dataOwnerName": "string",
"openData": [
"string"
]
},
"dataTheme": {
"idDataTheme": 0,
"dataThemeName": "string",
"openData": [
"string"
]
},
"updateFrequency": {
"idUpdateFrequency": 0,
"updateFrequencyName": "string",
"openData": [
"string"
]
},
"dataUsages": [
"string"
]
},
"usedByNavigation": "string"
}
]
}
I don't know much about this subject but it doesn't feel like it should be working this way.
My models for both 'user' and 'profession' have been generated by EntitytFramework after having linked the database.
Those are my models for both 'user' and 'profession':
namespace Sims.Models
{
public partial class User
{
public User()
{
DataUsages = new HashSet<DataUsage>();
}
public long IdUser { get; set; }
public int UserProfessionId { get; set; }
public int UserProfessionFieldId { get; set; }
public string? UserName { get; set; }
public string? UserMail { get; set; }
public string? UserCompany { get; set; }
public byte[]? UserPicture { get; set; }
public virtual Profession UserProfession { get; set; } = null!;
public virtual ProfessionField UserProfessionField { get; set; } = null!;
public virtual ICollection<DataUsage> DataUsages { get; set; }
}
}
and
namespace Sims.Models
{
public partial class Profession
{
public Profession()
{
ProfessionFields = new HashSet<ProfessionField>();
Users = new HashSet<User>();
}
public int IdProfession { get; set; }
public string ProfessionName { get; set; } = null!;
public virtual ICollection<ProfessionField> ProfessionFields { get; set; }
public virtual ICollection<User> Users { get; set; }
}
}
Maybe this is all usual but as I'm a beginner learning by myself, I do not want to be mistaken.
Thanks for reading ! :)
When calling .ToListAsync() with related entities that are virtual, you have to explicitely tell entity framework to include those related entities:
return await _context.Users.Include(u => u.UserProfession).ToListAsync();
Also, in your example, this approach might lead to an object cycle that will likely throw a JsonException. This can be avoided by changing the JsonSerializerOptions for your project or only querying for relevant data:
return await context.Users
.Select(u => new
{
u.IdUser,
u.UserName,
u.UserMail,
u.UserCompany,
u.UserProfessionId,
UserProfessionName = u.UserProfession.ProfessionName,
})
.ToListAsync();

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!

EF Core Customize JSON Serialization with LINQ

I am building out a demo for an API in .NET Core and constructing a nested JSON object that needs to get constructed through a series of LINQ queries.
My current issue is that when i get about 4 layers deep, I want to customize what actually gets serialized, more specifically, i want to "Not Include" specific navigation properties for this query specifically, but not for general purpose, only for this specific query.
My first thought was to do a DTO, but that seems like an unnecessary extra model just for this one specific case...i'd like to work with LINQ directly to manipulate for my result.
I have already added the following to my Startup.cs file to avoid cycles:
services.AddControllers().AddNewtonsoftJson(options =>
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
I have 5 models currently in action that look like this:
public class Hotel
{
public int ID { get; set; }
public string Name { get; set; }
public string StreetAddress { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Phone { get; set; }
public List<HotelRooms> HotelRooms { get; set; }
}
public class Room
{
public int ID { get; set; }
public string Name { get; set; }
public Layout Layout { get; set; }
public List<RoomAmenities> RoomAmenities { get; set; }
public List<HotelRooms> HotelRooms { get; set; }
}
public class HotelRooms
{
public int HotelID { get; set; }
public int RoomNumber { get; set; }
public int RoomID { get; set; }
public decimal Rate { get; set; }
public bool PetFriendly { get; set; }
public Hotel Hotel { get; set; }
public Room Room { get; set; }
}
public class Amenities
{
public int ID { get; set; }
public string Name { get; set; }
// Navigation Properties
public List<RoomAmenities> RoomAmenities { get; set; }
}
public class RoomAmenities
{
public int RoomID { get; set; }
public int AmenitiesID { get; set; }
public Room Room { get; set; }
public Amenities Amenity { get; set; }
}
In my services, i have this logic that is actually doing the querying:
public async Task<Hotel> GetById(int id)
{
var hotel = await _context.Hotel.FindAsync(id);
// Get a list of rooms
var rooms = await _context.HotelRooms.Where(r => r.HotelID == id)
.Include(d => d.Room)
.ThenInclude(a => a.RoomAmenities)
.ThenInclude(x => x.Amenity).ToListAsync();
hotel.HotelRooms = rooms;
return hotel;
}
The current output is:
{
"id": 1,
"name": "Amanda's Hotel",
"streetAddress": "123 CandyCane Lane",
"city": "Seattle",
"state": "WA",
"phone": "123-456-8798",
"hotelRooms": [
{
"hotelID": 1,
"roomNumber": 101,
"roomID": 2,
"rate": 75.00,
"petFriendly": false,
"room": {
"id": 2,
"name": "Queen Suite",
"layout": 2,
"roomAmenities": [
{
"roomID": 2,
"amenitiesID": 1,
"amenity": {
"id": 1,
"name": "Coffee Maker",
"roomAmenities": [
{
"roomID": 1,
"amenitiesID": 1,
"room": {
"id": 1,
"name": "Princess Suite",
"layout": 1,
"roomAmenities": [
{
"roomID": 1,
"amenitiesID": 2,
"amenity": {
"id": 2,
"name": "Mini Bar",
"roomAmenities": []
}
}
],
"hotelRooms": [
{
"hotelID": 1,
"roomNumber": 123,
"roomID": 1,
"rate": 120.00,
"petFriendly": true
}
]
}
}
]
}
}
],
"hotelRooms": []
}
},
{
"hotelID": 1,
"roomNumber": 123,
"roomID": 1,
"rate": 120.00,
"petFriendly": true,
"room": {
"id": 1,
"name": "Princess Suite",
"layout": 1,
"roomAmenities": [
{
"roomID": 1,
"amenitiesID": 1,
"amenity": {
"id": 1,
"name": "Coffee Maker",
"roomAmenities": [
{
"roomID": 2,
"amenitiesID": 1,
"room": {
"id": 2,
"name": "Queen Suite",
"layout": 2,
"roomAmenities": [],
"hotelRooms": [
{
"hotelID": 1,
"roomNumber": 101,
"roomID": 2,
"rate": 75.00,
"petFriendly": false
}
]
}
}
]
}
},
{
"roomID": 1,
"amenitiesID": 2,
"amenity": {
"id": 2,
"name": "Mini Bar",
"roomAmenities": []
}
}
],
"hotelRooms": []
}
}
]
}
This is much too nested for me, i'd like the inner "roomAmenities" inside the "amenity" object to not be included. I'd like my object to look like this:
{
"id": 1,
"name": "Amanda's Hotel",
"streetAddress": "123 CandyCane Lane",
"city": "Seattle",
"state": "WA",
"phone": "123-456-8798",
"hotelRooms": [
{
"hotelID": 1,
"roomNumber": 101,
"roomID": 2,
"rate": 75,
"petFriendly": false,
"room": {
"id": 2,
"name": "Queen Suite",
"layout": 2,
"roomAmenities": [
{
"roomID": 2,
"amenitiesID": 1,
"amenity": {
"id": 1,
"name": "Coffee Maker"
}
}
],
"hotelRooms": []
}
},
{
"hotelID": 1,
"roomNumber": 123,
"roomID": 1,
"rate": 120,
"petFriendly": true,
"room": {
"id": 1,
"name": "Princess Suite",
"layout": 1,
"roomAmenities": [
{
"roomID": 1,
"amenitiesID": 1,
"amenity": {
"id": 1,
"name": "Coffee Maker"
}
},
{
"roomID": 1,
"amenitiesID": 2,
"amenity": {
"id": 2,
"name": "Mini Bar"
}
}
],
"hotelRooms": []
}
}
]
}
Does anyone have any guidance on how i can achieve this with EFCore and LINQ?
you can check this post
What is the difference between PreserveReferencesHandling and ReferenceLoopHandling in Json.Net?
I see that you have already implemented referenceloopHandling, but even when you are using that you can only make the inner reference as null but not totally remove the key- value pair from json

PgAdmin Import/Export did not keep foreign keys with Entity Framework

I am including a bunch information but feel free to skip the heading "The Problem" to read the actual issue.
Introduction
In my local database, I built out a schema of tables and populated them with data.
Basic flow:
FORMS table has a one-to-many relationship with the QUESTIONS table
QUESTIONS has a one-to-many relationship with the ANSWERS table.
The QUESTIONS table references the FORMS table with a FormId.
The ANSWERS table references the QUESTIONS with a QuestionId
Here is the code for the FORMS and QUESTIONS table.
public class AppointmentForm
{
[Key]
public long Id { get; set; }
public string FormName { get; set; }
public int Order { get; set; }
public List<AppointmentQuestion> Questions { get; set; }
}
public class AppointmentQuestion
{
[Key]
public long Id { get; set; }
[ForeignKey("FormId")]
public virtual AppointmentForm Form { get; set; }
public long FormId { get; set; }
public int Order { get; set; }
public bool? Required { get; set; } = false;
public string Question { get; set; }
public virtual List<AppointmentAnswer> Answers { get; set; }
}
So, I used PostMan to populate my local database.
Now, this worked out because everything was referenced and maintained with the Entity Framework.
I wrote the code you will see below to send this JSON structure to the client.
{
"forms": [
{
"id": 1,
"formName": "Inclusion",
"order": 1,
"questions": [1, 2]
}
],
"questions": [
{
"id": 1,
"formId": 1,
"order": 1,
"required": true,
"question": "Are you able to go for a walk of at least 15 minutes?",
"answers": [1, 2, 3, 4, 5]
}
],
"answers": [
{
"id": 1,
"questionId": 1,
"typeId": 2,
"label": "Unable to do",
"order": 1
},
{
"id": 2,
"questionId": 1,
"typeId": 2,
"label": "Without much difficulty",
"order": 2
},
{
"id": 3,
"questionId": 1,
"typeId": 2,
"label": "With some difficulty",
"order": 3
},
{
"id": 4,
"questionId": 1,
"typeId": 2,
"label": "With a little difficulty",
"order": 4
},
{
"id": 5,
"questionId": 1,
"typeId": 2,
"label": "Without any difficulty",
"order": 5
}
],
"types": [
{
"id": 1,
"type": "Manual enter"
},
{
"id": 2,
"type": "Multiple choice"
}
]
}
To create this, I would use the following script (or else everything would have been nested)
public async Task<IActionResult> GetModelNormalized()
{
AppointmentModelNormalized Model = new AppointmentModelNormalized();
List<AppointmentForm> Forms = await _formManager.GetAppointmentFormsAsync();
List<AppointmentAnswerType> Types = await _typeManager.GetAppointmentAnswerTypesAsync();
foreach(AppointmentForm f in Forms)
{
AppointmentFormReference _f = new AppointmentFormReference() {
Id = f.Id,
FormName = f.FormName,
Order = f.Order
};
foreach(AppointmentQuestion q in f.Questions)
{
_f.Questions.Add(q.Id);
AppointmentQuestionReference _q = new AppointmentQuestionReference()
{
Id = q.Id,
Question = q.Question,
FormId = q.FormId,
Order = q.Order,
Required = q.Required
};
foreach(AppointmentAnswer a in q.Answers)
{
_q.Answers.Add(a.Id);
AppointmentAnswerReference _a = new AppointmentAnswerReference()
{
Id = a.Id,
Label = a.Label,
Order = a.Order,
QuestionId = a.QuestionId,
TypeId = a.TypeId
};
Model.Answers.Add(_a);
}
Model.Questions.Add(_q);
}
Model.Forms.Add(_f);
}
Model.Types = Types;
return Ok(Model);
}
In my local env, this all worked perfectly.
The Problem
When I went to test QA, I exported my data using PgAdmin4 and then imported the csv file into the QA database.
Now, the line of code with foreach(AppointmentQuestion q in f.Questions) does not work because the FK did not get transferred with the Export/Import.
I do not understand why this happened because the FKs are all the same.
Is there a better way to export and import the data that will keep the FK relationship?
If necessary, I can grab all the questions and answers like I am grabbing the Forms. List<AppointmentForm> Forms = await _formManager.GetAppointmentFormsAsync();
But doesn't linq do that for me? I mean that is the point of a virtual method? It gets created when it gets called, I think so at least. Should I write custom getter and setters?
Attached is the stack trace of the exception. It is a NullReferenceException.
you don't include your related object use following code and for more info read this answer
_context.AppointmentForms.Include(x => x.Questions ).Include(x => x.Questions.Select(q => q.Answers)).ToListAsync();
Edit from Christian4423:
I was able to get this to do the same thing with the syntax.
List<AppointmentForm> Forms = await _context.AppointmentForms
.Include("Questions.Answers")
.ToListAsync();

C# Extract JSON from nested array

I'm trying to iterate through nested JSON arrays using C# and JSON.NET. The JSON represents categories for an online webstore - below is an example. My goal is to create a list of all of the names of categories.
{
"id": 2,
"parent_id": 1,
"name": "Main Category List",
"is_active": true,
"position": 1,
"level": 1,
"product_count": 0,
"children_data": [
{
"id": 9,
"parent_id": 2,
"name": "Mens Clothing",
"is_active": true,
"position": 6,
"level": 2,
"product_count": 0,
"children_data": []
},
{
"id": 8,
"parent_id": 2,
"name": "Womens Clothing",
"is_active": true,
"position": 7,
"level": 2,
"product_count": 0,
"children_data": [
{
"id": 223,
"parent_id": 8,
"name": "Outdoor Clothing",
"is_active": true,
"position": 1,
"level": 3,
"product_count": 0,
"children_data": []
},
{
"id": 224,
"parent_id": 8,
"name": "Hiking Clothing",
"is_active": true,
"position": 2,
"level": 3,
"product_count": 0,
"children_data": []
},
{
"id": 596,
"parent_id": 8,
"name": "Dresses",
"is_active": true,
"position": 3,
"level": 3,
"product_count": 0,
"children_data": [
{
"id": 694,
"parent_id": 596,
"name": "Summer Dresses",
"is_active": true,
"position": 13,
"level": 4,
"product_count": 0,
"children_data": [
{
"id": 720,
"parent_id": 694,
"name": "Accessories",
"is_active": true,
"position": 1,
"level": 5,
"product_count": 0,
"children_data": [ ]
}
]
}
]
}
]
},
{
"id": 10,
"parent_id": 2,
"name": "Sale & Clearance",
"is_active": true,
"position": 8,
"level": 2,
"product_count": 0,
"children_data": []
}
]
}
There could be varying levels of categories and I need to parse every one. I want to get every category and create a map. For example (Main Category List --> Women's Clothing --> Outdoor Clothing). I'm thinking I can check the depth of children data but I don't know how to keep checking deeper and deeper into the next Json object.
JObject responseObject = JObject.Parse(response.Content);
foreach (JObject category in getCatResponseObj.SelectToken("children_data"))
{
while loop checking depth of children_data
}
Were it me, I would create the most complete JSON file I can come up with (including all possible entries), then use json2csharp or an equivalent tool to create c# classes then deserialize the Json and work with it natively. You may have to massage the results a bit - but I think you can get there. If that didn't work, I would use Newtonsofts JSON LINQ Features (documentation). JToken gives you parent / children combinations that allows you to traverse up and down the tree. The samples in the documentation are really good, so no need of my filling up the page with duplicate info.
(Generated from your example)
public class ChildrenData
{
public int id { get; set; }
public int parent_id { get; set; }
public string name { get; set; }
public bool is_active { get; set; }
public int position { get; set; }
public int level { get; set; }
public int product_count { get; set; }
public List<object> children_data { get; set; }
}
public class RootObject
{
public int id { get; set; }
public int parent_id { get; set; }
public string name { get; set; }
public bool is_active { get; set; }
public int position { get; set; }
public int level { get; set; }
public int product_count { get; set; }
public List<ChildrenData> children_data { get; set; }
}
This appears to be a recursively defined structure. You should create a function to extract the values of each of the children so you could reuse it recursively.
IEnumerable<string> GetCategoryNames(JObject data)
{
yield return (string)data["name"];
foreach (var name in data["children_data"].Cast<JObject>().SelectMany(GetCategoryNames))
yield return name;
}
Then call it on the root object to get your names putting in a list or whatever.
var obj = JObject.Parse(response.Content);
var names = GetCategoryNames(obj).ToList();
Otherwise, if you want to indiscriminately get all names in the object tree, just keep in mind that SelectToken()/SelectTokens() also takes json path queries. So to get all names of all descendants, you'd just do this:
let names = obj.SelectTokens("..name").ToList();

Categories