Better Way of Dealing With Circular References? - EF Core 3.1 and Web API - asp.net-core

I am currently trying to progress with EF Core with a one-to-many (a user has many items). A tutorial or three later I managed to get things working with two very small and simple tables; however, I got a json exception: A possible object cycle was detected which is not supported which indicated that I had circular references.
Here is my code that gets around the issue using DTO objects, but is there a more cleaner way I can get around this issue as typing the, though it works, felt a bit wrong.
User:
namespace TestWebApplication.Database
{
public class User
{
[Key]
public int UserId { get; set; }
public string UserName { get; set; }
public string Dob { get; set; }
public string Location { get; set; }
public ICollection<Items> Items { get; set; }
}
}
Items:
namespace TestWebApplication.Database
{
public class Items
{
[Key]
public int ItemId { get; set; }
public string Item { get; set; }
public string Category { get; set; }
public string Type { get; set; }
public virtual User User { get; set; }
}
}
DtoItems:
namespace TestWebApplication.Database.DTOs
{
public class DtoItems
{
public string Item { get; set; }
public string Category { get; set; }
public string Type { get; set; }
public DtoUser User { get; set; }
}
}
DtoUser:
namespace TestWebApplication.Database.DTOs
{
public class DtoUser
{
public string UserName { get; set; }
public string Dob { get; set; }
public string Location { get; set; }
}
}
TestController:
[HttpGet]
[Route("getitems")]
public ActionResult<List<Items>> GetItems()
{
List<Items> items = _myContext.Items.Include(i => i.User).ToList();
// DTOs
List<DtoItems> dtoItems = new List<DtoItems>();
foreach (var i in items)
{
var dtoItem = new DtoItems
{
Item = i.Item,
Category = i.Category,
Type = i.Type,
User = new DtoUser
{
UserName = i.User.UserName,
Dob = i.User.Dob,
Location = i.User.Location
}
};
dtoItems.Add(dtoItem);
}
return Ok(dtoItems);
}
The output from endpoint:
[
{
"item": "xxx",
"category": "xxx",
"type": "xxx",
"user": {
"userName": "xxx",
"dob": "xxx",
"location": "xx"
}
},
{
"item": "xxx",
"category": "xxx",
"type": "xxx",
"user": {
"userName": "xxx",
"dob": "xxx",
"location": "xxx"
}
}
]

In my opinion, the use of a DTO is the correct way of dealing with this issue. The fact that your datamodel does not serialize well is trying to hint to you that you should not be serializing your datamodel from the API at all.
I think returning a DTO also solves further issues down the road (What if you want to return all properties of the UserModel except one, maybe it's a sensitive property you don't want to just return from your API, what if your UserModel in the DB gets more navigation properties that you don't want to return?).
There is really only two other ways of handling this.
You can switch to Newtonsoft.Json which has support for handling reference loops and you can configure it one single line
Like so :
services.AddControllers().AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
System.Text.Json does not have support for doing this (yet). Follow this Github Issue for more info : https://github.com/dotnet/runtime/issues/30820
You use the JsonIgnore attribute to force non serialization of properties which will work but... It looks weird to have an EntityFramework model have JSON Serialization options on it...
So your best bet, stick with the DTO.
More info :
https://dotnetcoretutorials.com/2020/03/15/fixing-json-self-referencing-loop-exceptions/
https://github.com/dotnet/runtime/issues/30820
https://www.newtonsoft.com/json/help/html/ReferenceLoopHandlingIgnore.htm

Related

Bind property to another input name in nested array property

The data in JSON format is sent to api endpoint.
Data sample:
{
"templateId": "dc15e4d1-ccbd-4581-a819-5b7f90b32cc5",
"name": "abc",
"steps": [
{
"id": "34b4f406-120e-4d80-8018-6c780c80a6c4",
"visible": false,
}
]
}
Api gets data in this format:
public class TemplateRequest {
public Guid TemplateId { get; set; }
public string Name { get; set; }
public StepRequest[] Steps { get; set; }
}
StepRequest class:
public class StepRequest {
[ModelBinder(Name = "id")]
public Guid StepId { get; set; }
public bool? Visible { get; set; }
}
The JSON has id key instead of stepId, but I can't get it in controller.
When I check, the StepId is always an empty Guid.
What is wrong here, why the StepId property is not having the value from id key?
Yes, like #daremachine said, it was NewtonSoft and JsonProperty(PropertyName = "id") helped.

Duplicate Foreign Key entries being created on insert - EF Core and Web Api 3.1

I am currently learning how to work with EF Core with a simple one to many setup, where a user can have many items. In terms of retrieving the data from the tables, this is fine with some DTO models; however, when I try and add a user with multiple items via Postman, I noticed that for each item it had duplicated the user that many times (i.e. a user with 3 items will create 3 items and 3 users):
Postman (POST)
{
"username": "xxx",
"dob": "xxx",
"location": "xxx",
"items":[{
"item": "xxx",
"category": "xxx",
"type": "xxx"
},
{
"item": "xxx",
"category": "xxx",
"type": "xxx"
},
{
"item": "xxx",
"category": "xxx",
"type": "xxx"
}]
}
Context:
namespace TestWebApplication.Database
{
public class MyContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Items> Items { get; set; }
public MyContext(DbContextOptions<MyContext> options)
: base(options)
{
// erm, nothing here
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasMany(i => i.Items)
.WithOne(u => u.User);
}
public override int SaveChanges()
{
var entities = from e in ChangeTracker.Entries()
where e.State == EntityState.Added
|| e.State == EntityState.Modified
select e.Entity;
foreach (var entity in entities)
{
var validationContext = new ValidationContext(entity);
Validator.ValidateObject(entity, validationContext);
}
return base.SaveChanges();
}
}
}
Controller:
[HttpPost]
[Route("insertuseranditems")]
public ActionResult InsertUserAndItems(User user)
{
if (ModelState.IsValid)
{
using (MyContext myContext = _myContext as MyContext)
{
myContext?.Users?.Add(user);
int changes = myContext.SaveChanges();
if (changes > 0)
{
return Created("User saved", user);
}
}
}
return Accepted();
}
Items:
namespace TestWebApplication.Database
{
public class Items
{
[Key]
public int ItemId { get; set; }
public string Item { get; set; }
public string Category { get; set; }
public string Type { get; set; }
public virtual User User { get; set; }
}
}
Users:
namespace TestWebApplication.Database
{
public class User
{
[Key]
public int UserId { get; set; }
public string UserName { get; set; }
public string Dob { get; set; }
public string Location { get; set; }
public ICollection<Items> Items { get; set; }
}
}
I revisited my code and changed my Items.cs model to:
namespace TestWebApplication.Database
{
public class Items
{
[Key]
public int ItemId { get; set; }
public string Item { get; set; }
public string Category { get; set; }
public string Type { get; set; }
[ForeignKey("User")] // added this
public int? UserId { get; set; } // added this
public virtual User User { get; set; }
}
}
Now, when I send the above json via Postman, I get multiple items with the same user as a Foreign Key.
The site below seemed to help:
The ForeignKey Attribute

Restful api to receive an array from request

I have been tasked with building an API, that as a request, will take a product number which will have a quantity and size , zip code, shipping method.
The customer has a cart and in that cart is the product number, quantity and size
so basically he would send a json request that looks like the below
{
"ShoppingCart": {
"Products": [
{
"Sku": "123",
"Size": "S",
"Quantity": "1"
},
{
"Sku": "456",
"Size": "M",
"Quantity": "2"
},
{
"Sku": "789",
"Size": "L",
"Quantity": "3"
}
],
"ShipToZip": "54452",
"ShipMethod": "Ground"
}
}
is it possible to receive an HTTP json request on my .net core rest webapi that im making.
If so, what would the route look like to send json like that? it'sgoing to be pretty long if they have to put the entire json in the url right?
EDIT:
After doing more research, I find that I can receive a POST request with JSON in the body, from there i should be able to read that json, do some stuff with it, and then return json back right? Am i correct?
After doing more research, I find that I can receive a POST request with JSON in the body, from there i should be able to read that json, do some stuff with it, and then return json back right? Am i correct?
Yes. You are correct. For instance, the following controller action would accept a POST body with the JSON from your question and respond with that same JSON.
public class Product
{
public string Sku { get; set; }
public string Size { get; set; }
public int Quantity { get; set; }
}
public class Cart
{
public List<Product> Product { get; set; }
public string ShipToZip { get; set; }
public string ShipMethod { get; set; }
}
public class CartBody
{
public Cart Cart { get; set; }
}
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// POST api/values
[HttpPost]
public ActionResult<CartBody> Post(CartBody cartBody)
{
return cartBody;
}
}
I guess I did not look hard enough :(
Anyway the more questions I feel like the more exposure some newbie problems will have.
How to receive json in ASP.NET web api?
I got my solution from there,
All I had to do was create a "Model" class like below that matches the exact JSON format being sent, no deserielization needed
public class RequestModel
{
public ShoppingCart RequestShoppingCart { get; set; }
}
public class ShoppingCart
{
public Products[] Products { get; set; }
public int ShipToZip { get; set; }
public string ShipMethod { get; set; }
}
public class Products
{
public string Sku { get; set; }
public string Size { get; set; }
public int Quantity { get; set; }
}
Then from there in my API Controller i can do the following to see it working
[Produces("application/json")]
[Route("api/ShippingCalculator")]
public class ShippingCalculatorController : Controller
{
// POST: api/ShippingCalculator
[HttpPost]
public string Post([FromBody]RequestModel jsonRequest)
{
// Debug.WriteLine(jsonRequest.RequestShoppingCart.Products);
return jsonRequest.RequestShoppingCart.ShipMethod;
}
}

Nested json deserialized incompletely

I mentioned in title that the json is deserialized incompletely because some of the objects have the right value in the controller (I send the json to an asp.net mvc 4 controller), but the problem occurs with properties of objects stored in arrays (to be exact, the problem appears with Data)
I have the following classes:
public class Node
{
[JsonProperty("id")]
public int? Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
private Data _data = new Data();
[JsonProperty("data")]
public Data Data { get; set; }
private List<Adjacency> _adjacencies = new List<Adjacency>();
[JsonProperty("adjacencies")]
public List<Adjacency> Adjacencies
{
get { return _adjacencies; }
set { _adjacencies = value; }
}
private List<Instance> _dependendStates = new List<Instance>();
public List<Instance> DependentStates
{
get { return _dependendStates; }
set { _dependendStates = value; }
}
}
public class Data
{
[JsonProperty("$posX")]
public decimal PosX { get; set; }
[JsonProperty("$posY")]
public decimal PosY { get; set; }
}
public class Adjacency
{
[JsonProperty(PropertyName = "nodeTo")]
public string NodeTo { get; set; }
[JsonProperty(PropertyName = "data")]
public AdjData Data { get; set; }
//doesn't contain definition for automated transitions
}
public class AdjData
{
[JsonProperty(PropertyName = "$labelid")]
public string LabelId { get; set; }
[JsonProperty(PropertyName = "$labeltext")]
public string Label { get; set; }
[JsonProperty(PropertyName = "$type")]
public string Type { get; set; }
}
I've highlighted fields that don't have the right value.
The null problem http://img19.imageshack.us/img19/530/36976913.png
The json looks like this:
{
"result":[
{
"id":"100",
"name":"Start",
"data":{
"$posX":-100,
"$posY":-100,
"$deployed":true,
"$color":"#000000",
"$selected":"true"
},
"adjacencies":[
{
"nodeTo":"188",
"data":{
"$type":"labeled_arrow",
"$labelid":"Label Name",
"$labeltext":"Label Name"
}
}
]
},
{
"id":"188",
"name":"Second ",
"data":{
"$dim":20,
"$color":"#000000",
"$selected":"true"
},
"adjacencies":[
]
}
],
"smName":"gftrds"
}
I'm having a problem sorting this out as I do not understand or see where the problem is.
Disclaimer: I wasn't able to reproduce this exactly, as in my case Label and LabelId deserialized fine, though Type did not, so my anwser may not address the problem you are having.
In my case, I was only able to deserialize the "$type":"labeled_arrow" property if it was not the first one - if it appeared after "$labelid":"Label Name" or "$labeltext":"Label Name" it worked fine.
Strangely, if the $type property was called type in the Json and referred to as [JsonProperty(PropertyName = "type")] in the C#, it would deserialize fine, regardless of the order.
In your case you can't deserialize Label or LabelId either, so it could be that your problem is caused by something else. To rule out what I have suggested, it might pay to try modifying some of the property names (possibly just take off the $) to see if they are causing the properties to be deserialized incorrectly.
I experienced the same issue, however in my case this was related to how the data was being transmitted from client side. Make sure the AJAX request is using the proper headers and formatting. For example:
dataType: 'json',
contentType: 'application/json; charset=UTF-8',
data: JSON.stringify({
MemberId : '123',
UserName: '456',
Parameters: [
{ Value : 'testing' },
{ Value : 'test2' }
]
}),

How to structure in-memory entity classes to load denormalized referenced documents from RavenDB

I am receiving a FormatException when trying to load a an Album document from the default RavenDB database:
using (var session = _documentStore.OpenSession())
{
var album = session.Load<Album>(500);
//....
}
The Album JSON document in the database looks like this:
{
"AlbumArtUrl": "/Content/Images/placeholder.gif",
"Genre": {
"Id": "genres/10",
"Name": "Classical"
},
"Price": 8.99,
"Title": "The Best of Beethoven",
"CountSold": 0,
"Artist": {
"Id": "artists/203",
"Name": "Nicolaus Esterhazy Sinfonia"
}
}
And my in-memory entity Album class looks like this:
public class Album
{
public long Id { get; set; }
public string AlbumArtUrl { get; set; }
public DenomralizedGenre Genre { get; set; }
public decimal Price { get; set; }
public string Title { get; set; }
public int CountSold { get; set; }
public DenomralizedArtist Artist { get; set; }
}
public class DenomralizedGenre
{
public int Id { get; set; }
public string Name { get; set; }
}
public class DenomralizedArtist
{
public int Id { get; set; }
public string Name { get; set; }
}
What am I doing wrong here?
Make all your Id strings. You have them as int and long. In RavenDB Id's are strings.
The Id as a string would be Album/24 in RavenDB. The Class name or type plus the HiLo value (created by the client tools) make up the Id.