Reputation: 7102
I have this simple controller in a NetCore 2 Entity Framework API app that is supposed to get a list of nationalparks based on the stateID:
[HttpGet("ListNationalParksByState/{stateId}")]
public async Task<ActionResult<IEnumerable<NationalParkList>>> GetNationalParksForState(Guid stateID)
{
var nationalparkList = await _context.NationalParkList.Where(n => n.StateId == stateID).ToListAsync();
return nationalparkList;
}
But my returned JSON only shows this:
[{"state":{"nationalparkList":[
However, when I set a breakpoint in that controller, it shows it found 3 nationalparks (I'm not sure was Castle.Proxies is):
[0] {Castle.Proxies.NationalParkListProxy}
[1] {Castle.Proxies.NationalParkListProxy}
[2] {Castle.Proxies.NationalParkListProxy}
Expanding those shows the 3 nationalparks and all their properties.
Here is my NationalParkList model:
public partial class NationalParkList
{
public NationalParkList()
{
NationalParkLinks = new HashSet<NationalParkLinks>();
}
public string NationalParkId { get; set; }
public Guid StateId { get; set; }
public string NationalParkTitle { get; set; }
public string NationalParkText { get; set; }
public virtual StateList State { get; set; }
public virtual ICollection<NationalParkLinks> NationalParkLinks { get; set; }
}
Here is how it's defined in my dbcontext:
modelBuilder.Entity<NationalParkList>(entity =>
{
entity.HasKey(e => e.NationalParkId)
.HasName("PK_NationalParkList");
entity.ToTable("nationalparkList");
entity.Property(e => e.NationalParkId)
.HasColumnName("nationalparkID")
.HasMaxLength(50)
.ValueGeneratedNever();
entity.Property(e => e.StateId).HasColumnName("stateID");
entity.Property(e => e.NationalParkText).HasColumnName("nationalparkText");
entity.Property(e => e.NationalParkTitle)
.HasColumnName("nationalparkTitle")
.HasMaxLength(3000);
entity.HasOne(d => d.State)
.WithMany(p => p.NationalParkList)
.HasForeignKey(d => d.StateId)
.HasConstraintName("FK_nationalparkList_stateList");
});
I'm not getting any errors, I'm just not getting any data.
Does anyone see why I'd get no data when I hit this controller?
Thanks!
Upvotes: 2
Views: 362
Reputation: 28434
As discussed in the comments, most likely the existence of cycles in your object graph is causing the serialization to malfunction. Im surprised that you arent getting exceptions during run time if this is the case.
AFAIK you have two options:
Decorate props in your class that you dont want to be serialized with [JsonIgnore]
or similar, in order to avoid cycles in your object graphs.
The biggest issue with this approach that I see is its inflexibility: consider 2 endpoints A, B and an entity class Foo
with multiple properties, including both x,y; A needs all props of Foo
except x and B all props except y. How would you handle this having only one possible serialization configuration for class Foo
?
Furthermore, from a purist point of view, adding such decorators increases the responsabilities/knowledge of an entity class with stuff unrelated to the business logic.
Map your entities into lower-level objects AKA DTOs
Another approach is to map your entities into instances of (mostly) behaviorless classes that can be considered data transfer objects. In most web/controller layers you will see data objects going in and out. In your case for example, you could refactor into the following:
public class NationalParkListData
{
public string Id { get; set; }
public Guid StateId { get; set; }
public string Title { get; set; }
public string Text { get; set; }
// Depending on how rest-compliant your approach is, you
// might include non-collection nested objects or not
public StateListData State { get; set; }
public int NationalParkLinksCount { get; set; }
}
[HttpGet("/states/{stateId:guid}/national-parks")]
public async Task<IActionResult> GetNationalParksForState(Guid stateId, CancellationToken cancellationToken)
{
var stateNationalParks = await _context.NationalParkList
.Where(n => n.StateId == stateId)
.ToListAsync(cancellationToken);
IEnumerable<NationalParkListData> result = // your mapper logic
return this.Ok(result);
}
In this case, you can easily notice that the issue mentioned in the previous approach does not exist as its handled by the mapping layer. For implementing a mapping layer the most common approach is to use libraries like AutoMapper.
Upvotes: 2