I have a code first Blazor WebAssembly application in which I have a Many-to-Many relationship.
public class A
{
public A()
{
this.Bs = new HashSet<B>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<B> Bs { get; set; }
}
public class B
{
public B()
{
this.As= new HashSet<A>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<A> As { get; set; }
}
In my client, I call the Get method of the server AController. I would like to have in each A object, the Bs ICollection.
If the Get method is like this, the Bs collection is null :
[HttpGet]
public IEnumerable<A> Get()
{
return _context.A.ToList();
}
If it is like this, to inlude Bs, I have an exception ("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. Consider using ReferenceHandler.Preserve on JsonSerializerOptions to support cycles.")
[HttpGet]
public IEnumerable<A> Get()
{
return _context.A.Include(a => a.Bs).ToList();
}
So in my Startup.cs (on the server) I had the following in the ConfigureServices method
services.AddControllers().AddJsonOptions(o =>
o.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve);
So now, the serialization works, but the deserialization fails because the JSON is different, not only a List of A.
In the client, I call the get method of the AController like this :
var response = await _httpClient.GetAsync("my_api_adresse");
return await response.Content.ReadFromJsonAsync<List<A>>();
But du to the ReferenceHandler.Preserve the JSON is like this, so the desirialization can't work and raises an exception :
{
"$id": "1",
"$values": [
{
"$id": "2",
"id": 4,
"name": "nameA3",
"Bs": {
"$id": "3",
"$values": [
{
"$id": "4",
"id": 1,
"Name": "NameB1",
"As": {
"$id": "5",
"$values": [
{
"$ref": "2"
}
]
}
}
]
}
}
]
}
What could I do to be able to include Bs collection into A objects and be able to serialize and deserialize the response without any trouble ?
Try using DTO objects (data transfer objects).
There are many ways. One is:
Create two new classes that are similar to class A and class B, but this one without a list of A objects. Instead, use a list of A Ids. That would solve your problem.
Comment gere if you need code sample
Related
I have a one-to-many relationship between two entities: Maps and MapNodes. When I eager load MapNodes with Maps (via an Include()), I get Map, and I get related MapNodes, but in the related MapNodes, I also get the full parent Map again. That Map then has associated MapNodes (again), and so on...
I need to limit the contents of the query to one level (i.e MapNodes) and not have the eager loading go deeper.
[Table("maps")]
public partial class Maps
{
public Maps()
{
MapNodes = new HashSet<MapNodes>();
}
<...>
[InverseProperty("Map")]
public virtual ICollection<MapNodes> MapNodes { get; set; }
}
[Table("map_nodes")]
public partial class MapNodes
{
public MapNodes()
{
}
<...>
[Column("map_id", TypeName = "int(10) unsigned")]
public uint MapId { get; set; }
public Maps Map { get; set; }
}
When I execute the following query:
var map = await context.Maps.Include( x => x.MapNodes).FirstOrDefaultAsync( x => x.Id == id);
I get an infinate eager loading:
{
"id": 1063,
"mapNodes": [
{
"id": 25784,
"mapId": 1063,
"map": {
"id": 1063,
"mapNodes": [
{
"id": 25784,
"mapId": 1063,
"map": {
"id": 1063,
"mapNodes": [
...
Microsoft replaced Microsoft.AspNetCore.Mvc.NewtonsoftJson in ASP.NET Core 3 with their own implementation which is System.Text.Json but it doesn't support Reference Loop Handling handling yet.
So in order to configure Reference Loop Handling you need to add the nuget package for Microsoft.AspNetCore.Mvc.NewtonsoftJson then configure it this way:
services.AddControllersWithViews().AddNewtonsoftJson(options => {
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});
I inherited a EF CF project and my knowledge of EF is inadequate. I am almost there. I have been reading and experimenting and I just can't figure out what I am doing wrong.
I have read numerous articles, tried reverse navigation, experimented and discovered strange new errors but no luck so far.
This is my setup. Of course the models have many more properties like Name that I left out for brevity.
public class VendorModel
{
[key]
public int Id { get; set; }
[ForeignKey("VendorId")]
public virtual List<VendorPersonnelModel> VendorPersonnel { get; set; }
}
//This model represents an intersect table in the DB
public class VendorPersonnelModel
{
[Key]
public int Id { get; set; }
public int VendorId { get; set; } //This is a FK to Vendor table defined in DB
public int PersonnelId { get; set; } //This is a FK to Personnel table defined in DB
}
//This model definition is here but not used till the second half of the question
public class PersonnelModel
{
[key]
public int Id { get; set; }
public string Name { get; set; }
public string Phone { get; set; }
}
This is how I am navigating in the controller:
var models = await _db.Vendors
.Include(o => o.Addresses)
.Include(o => o.VendorPersonnel)
.ToListAsync();
This is db context definition:
var ent = modelBuilder.Entity<VendorPersonnelModel>();
ent.HasKey(obj => obj.Id);
ent.ToTable("VendorPersonnel");
//Navigation properties...do I put something here like this???
//ent.HasMany().HasForeignKey(y => y.PersonnelId);
This produces a json return like this:
[{
"name": "Vendor ABC",
"vendorPersonnel": [
{
"id": 1,
"vendorId": 1001001,
"personnelId": 1231
},
{
"id": 2,
"vendorId": 1001001,
"personnelId": 1776
}
]
}]
This is very close to what I want. Now I want to resolve the other half of the intersect table...the personnel. I do NOT need the intersect details I was just trying to step my way towards the solution.
I want the json to look something like this:
[{
"name": "Vendor ABC",
"personnelDetails": [
{
"id": 1,
"Name": "Jane",
"Phone": "333-123-4567"
},
{
"id": 2,
"Name": "Joe",
"Phone": "675-943-6732"
}
]
}]
This is where I start getting all sorts of strange errors. I CAN make it work by doing two DIFFERENT queries and then mashing them together in a new object and return the new object but that just seems to me to be poor coding.
I'm sure there is a way to do this with EF I just don't know what to look for / read up on.
I have now been able to read up and answer my question. This article helped me with what was missing:
https://www.entityframeworktutorial.net/code-first/configure-many-to-many-relationship-in-code-first.aspx
This mapping will create a self referencing "appearance" that will freak a json formatter out.
Simply add:
HttpConfiguration config = GlobalConfiguration.Configuration;
config.Formatters.JsonFormatter
.SerializerSettings
.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
Source: Entity framework self referencing loop detected
So I have a very simple controller that accepts a Search object
public class ProfileController : ApiController
{
[AcceptVerbs("GET", "POST")]
public async Task<ProfileDTO> GetProfile(Search profile)
{
//My code
}
}
The Search object only contains very simple, primitive data types
public class Search
{
public string Uuid { get; set; }
public string Email { get; set; }
public string Name { get; set; }
public bool Censored { get; set; }
}
For testing purposes I am using Swagger to call my API with sample data. For some reason if I make the same call to api/profile with the same data the profile argument will only contain data during the POST call. On a GET it is always null.
I only added the AcceptVerbs("POST") to demonstrate this issue, but by RESTful design I want the endpoint to only accept GET verbs
As of right now I'm just sending some boilerplate sample data such as
{
"Uuid": "string",
"Email": "string",
"Name": "string",
"Censored": true
}
I know this is probably a very simple issue, but why is the parameter always null only on a GET request?
Domain model:
public class Course
{
public int CourseId { get; set; }
public virtual ICollection<TeeSet> TeeSets { get; set; }
}
public class TeeSet
{
public int TeeSetId { get; set; }
public int CourseId { get; set; }
public CourseRating MensRating { get; set; }
}
The following query does not include the CourseRating complex type when Courses are expanded to include TeeSets.
GET /api/courses?$expand=TeeSets
public class CoursesController : ApiController
{
[Queryable]
public IQueryable<Course> Get()
{
return _uow.Courses.GetAll();
}
}
The JSON serialized result does not include the MensRating complex type (CourseRating):
[
{
"teeSets": [
{
"teeSetId": 1,
"courseId": 7
},
{
"teeSetId": 2,
"courseId": 7
}
],
"courseId": 7,
}
]
However, a quick test against the DbContext returns the CourseRating complex type on TeeSets like I would expect:
[TestMethod]
public void Get_Course_With_TeeSets()
{
using (CoursesContext ctx = new CoursesContext())
{
var courses = ctx.Courses.Where(x => x.CourseId == 7).Include(x => x.TeeSets).FirstOrDefault();
}
}
Entity Framework 6 and Web API 2 used.
You should expand MensRating as well like this, GET /api/courses?$expand=TeeSets/MensRating
When we build an implicit EDM model for supporting QueryableAttribute with ApiController, we treat every type as an entity type to get around the OData V3 limitation that complex types cannot refer to entity types. And, this means that you have to expand explicitly every non primitive type.
Add AutoExpand on MensRating Property can make this work.
I'm building a ReST API that supports linked resource expansion, and I can't work out how to use ServiceStack's native binding capabilities to translate a URL into a populated 'request DTO' object.
For example, say my API allowed you to retrieve information about a band using this request:
GET /bands/123
< 200 OK
< Content-Type: application/json
{
"href": "/bands/123",
"name": "Van Halen",
"genre": "Rock",
"albums" {
"href" : "/bands/1/albums",
}
}
If you wanted to expand the band's album list, you could do this:
GET /bands/1?expand=albums
< 200 OK
< Content-Type: application/json
{
"href": "/bands/123",
"name": "Van Halen",
"genre": "Rock",
"albums" {
"href" : "/bands/1/albums",
"items": [
{ "href" : "/bands/1/albums/17892" },
{ "href" : "/bands/1/albums/28971" }
]
}
}
I'm using ServiceStack, and I'd like to perform this inline expansion by re-using existing service methods.
My ServiceStack response DTOs look like this:
public class BandDto {
public string Href { get; set; }
public string Name { get; set; }
public AlbumListDto Albums { get; set; }
}
public class AlbumListDto {
public string Href { get; set; }
public IList<AlbumDto> Items { get; set;}
}
public class AlbumDto {
public string Href { get; set; }
public string Name { get; set; }
public int ReleaseYear { get; set; }
}
My ServiceStack request/route objects are like this:
[Route("/bands/{BandId}", "GET")]
public class Band : IReturn<BandDto> {
public string Expand { get; set; }
public int BandId { get; set; }
}
[Route("/bands/{BandId}/albums", "GET")]
public class BandAlbums : IReturn<AlbumListDto> {
public int BandId { get; set; }
}
and the actual services that handle the requests are like this:
public class BandAlbumService : Service {
public object Get(BandAlbums request) {
return(musicDb.GetAlbumsByBand(request.BandId));
}
}
public class BandService : Service {
private IMusicDatabase db;
private BandAlbumService bandAlbumService;
public BandService(IMusicDatabase musicDb, BandAlbumService bandAlbumService) {
this.db = musicDb;
this.bandAlbumService = bandAlbumService;
}
public object Get(Band request) {
var result = musicDb.GetBand(request.BandId);
if (request.Expand.Contains("albums")) {
// OK, I already have the string /bands/123/albums
// How do I translate this into a BandAlbums object
// so I can just invoke BandAlbumService.Get(albums)
var albumsRequest = Translate(result.Albums.Href);
result.Albums = bandAlbumService.Get(albumsRequest);
}
}
In the example above, say I have calculated the string /bands/123/albums as the HREF of Van Halen's album list.
How can I now use ServiceStack's built-in binding capabilities to translate the string /bands/123/albums into a BandAlbums 'request' object that I can pass directly into the BandAlbumService, get back a populated BandAlbumsDto object and include it in my response object?
(and yes, I'm aware this probably isn't an optimal approach in terms of minimising database hits. I'm going to worry about that later.)
RestPath should be able to help you:
I think this should work:
var restPath = EndpointHostConfig.Instance.Metadata.Routes.RestPaths.Single(x => x.RequestType == typeof(AlbumRequest));
var request = restPath.CreateRequest("/bands/123/albums")