Null children subcategories or db definition - c#

I am trying to develop a very simple ASP.net core 2.0 web API. It contains two model classes: Categories and Subcategories. However, when I call the CategoriesController as:
http://localhost:51242/api/categories
... all children Subcategories are null.
[
{
"id":1,
"en":"Sports",
"de":"Sports",
"fr":"Sports",
"it":"Sport",
"es":"Deportes",
"shown":0,
"subcategories":null,
"subscribers":0
},
{
"id":2,
"en":"Computers",
"de":"Computers",
"fr":"Computers",
"it":"Computers",
"es":"Computadoras",
"shown":0,
"subcategories":null,
"subscribers":0
}
]
When I seed to DB (UseInMemoryDatabase), I see that the DbSet Categories contains all subcategories present in the json file used for seeding (see image). So I am not sure what is it happening? Did I miss something?
I am new to ASP.net core, this is my first application :-)
[Produces("application/json")]
[Route("api/Categories")]
public class CategoriesController : Controller
{
private readonly NetAdContext _context;
public CategoriesController(NetAdContext context)
{
_context = context;
}
// GET: api/Categories
[HttpGet]
public IEnumerable<Category> GetCategories()
{
return _context.Categories;
}
}
public class Category
{
public long Id { get; set; }
public string En { get; set; }
public string De { get; set; }
public string Fr { get; set; }
public string It { get; set; }
public string Es { get; set; }
public long Shown { get; set; }
public List<Subcategory> Subcategories { get; set; }
}
public class Subcategory
{
public long Id { get; set; }
public string En { get; set; }
public string De { get; set; }
public string Fr { get; set; }
public string It { get; set; }
public string Es { get; set; }
public long Shown { get; set; }
public long Subscribers { get; set; }
public long CategoryId { get; set; }
public long LocationId { get; set; }
}
NetAdSeed is called from Program.cs
static public class NetAdSeed
{
public static void Initialize(NetAdContext context)
{
context.Database.EnsureCreated();
// Seed categories and subcategories
if (context.Categories.Count() == 0)
{
// Load list of categories and subcategories from file to string
string json = System.IO.File.ReadAllText(#".\json\categories.json");
// Deserialize
List<Category> cats = JsonConvert.DeserializeObject<List<Category>>(json);
context.Categories.AddRange(cats);
context.SaveChanges();
}
}
}
public class NetAdContext : DbContext
{
public DbSet<Category> Categories { get; set; }
public DbSet<Subcategory> Subcategory { get; set; }
public NetAdContext(DbContextOptions<NetAdContext> options)
: base(options)
{
}
}

This piece of code has many issues:
// GET: api/Categories
[HttpGet]
public IEnumerable<Category> GetCategories()
{
return _context.Categories;
}
Database requests should be asynchronous, specially with Entity Framework Core that supports this out of the box.
You should always return IActionResult in ASP.NET Core, so you can return different results and keep it agnostic of the result
You should not return entities directly, instead, Data Transfer Objects should be used. This allows you to change what you return according to the client
So, this should look something like this:
public class CategoryDto // or some other better name
{
public string Name { get; set; }
// Choose the MINIMUM amount of properties that the client needs
}
// GET: api/Categories
[HttpGet]
public async Task<IActionResult> GetCategories()
{
var categories = await _context.Categories
.Include(x => x.SubCategories) // this line you are missing
.ToListAsync();
return Ok(categories);
}

Related

'Products' does not contain a definition for 'Warehouse' and no accessible extension method 'Warehouse'

I am actually trying a simple web app with relational database using web api. But i got some unexpected error.
product.cs and Warehouse.cs
//Product.cs
public class Products
{
public int Id { get; set; }
public string Name { get; set; }
//relationship
public int WarehouseId { get; set; }
public Warehouse WarehouseList { get; set; }
}
//Warehouse.cs
public class Warehouse
{
public int Id { get; set; }
public string WarehouseList { get; set; }
}
Now shaping the data to return with DTO's
ProductToReturnDto.cs
public class ProductToReturnDto
{
public int Id { get; set; }
public string Name { get; set; }
public string WarehouseList { get; set; }
}
ProductRepository.cs
...
public async Task<Products> GetProductByIdAsync(int id)
{
return await _context.Products
.Include(p=>p.WarehouseList)
.FirstOrDefaultAsync(p=>p.Id==id);
}
ProductController.cs
...
[HttpGet("{id}")]
public async Task<ActionResult<ProductToReturnDto>> GetProduct(int id)
{
var product = await _repo.GetProductByIdAsync(id);
return new ProductToReturnDto
{
Id = product.Id,
Name = product.Name,
WarehouseList = product.Warehouse.WarehouseList, //here i found main error
};
}
Here i found this error:-
enter image description here
My goal is actully show "Warehouselist" via cascade in Angular. I am beginner. i don't understand how i resolve this error problem.

C# REST API - returning an array as JSON

I'm trying to build a REST API. I have been using this guide by Microsoft Docs and I'd appreciate some help.
I have 2 models Library and Book. Each have their own controllers as well.
I want each to reference each other so I can get all books within a library and I want a book to reference what library it belongs to. I am using an in-memory database by Microsoft Entity Framework
My current model classes look like this:
Library:
public class Library
{
[Key]
public long id { get; set; }
public Book[] bookArray { get; set; }
public string postalCode { get; set; }
public string street { get; set; }
public string city { get; set; }
public string country { get; set; }
}
Book:
public class Book
{
[Key]
public long id { get; set; }
public long libraryId { get; set; }
public string title { get; set; }
public string author { get; set; }
public string description { get; set; }
}
I want a GET endpoint like so "api/Libraries/{id}/books" that will return the array of books within a library as JSON, but I can't return the array. I get the error "Can't implicitly convert Models.Book to Microsoft.AspNetCore.Mvc.ActionResult<A2.Models.Library>". Have I setup the model classes correctly? and how do I resolve this error.
The Code:
// GET: api/Libraries/5/books
[HttpGet("{id}/books")]
public async Task<ActionResult<Library>> GetLibraryBooks(long id)
{
var library = await _context.Libraries.FindAsync(id);
if (library == null)
{
return NotFound();
}
return library.bookArray;
}
Your Method should return Book[] like this:
[HttpGet("{id}/books")]
public async Task<ActionResult<Book[]>> GetLibraryBooks(long id)
{
var library = await _context.Libraries.FindAsync(id);
if (library == null)
{
return NotFound();
}
return Ok(library.bookArray);
}
UPDATE
public class Library
{
public Libary(){
books = new List<Book>();
}
[Key]
public long id { get; set; }
public List<Book> books { get; set; }
public string postalCode { get; set; }
public string street { get; set; }
public string city { get; set; }
public string country { get; set; }
}
UPDATE 2
public class LibraryController : Controller
{
private readonly LibraryContext _context;
public LibraryController(LibraryContext context)
{
_context = context;
}
[HttpPost("{id}")]
public IActionResult AddBookToLibrary([FromRoute]long id ,[FromBody] Book bookToAdd)
{
var libraryToAddBook = _context.Libraries.Include(l => l.books)
.FirstOrDefault(l => l.id == id);
if (libraryToAddBook == null)
return NotFound();
libraryToAddBook.books.Add(bookToAdd);
_context.SaveChanges();
return Ok();
}
}
UPDATED CONTEXT
public class LibraryContext : DbContext
{
public LibraryContext(DbContextOptions<LibraryContext> options)
: base(options)
{
}
public DbSet<Library> Libraries { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Library>()
.OwnsMany<Book>(l => l.books);
}
}
startup.cs
var connectionString = Configuration.GetConnectionString("myDatabaseConnectionString");
services.AddDbContext<LibraryContext>(options =>
{
//options.USEYOURDATABASE(connectionString); //you might need install a NuGet eg. Microsoft.EntityFrameworkCore.SqlServer
});

JsonException: A possible object cycle was detected which is not supported. This can either be due to a cycle or if the object depth is larger than

In my web API when I run project to get data from the database got this error
.net core 3.1
JsonException: A possible object cycle was detected which is not supported. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32.
These are my codes:
my Model
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string ProductText { get; set; }
public int ProductCategoryId { get; set; }
[JsonIgnore]
public virtual ProductCategory ProductCategory { get; set; }
}
my productCategory class is:
public class ProductCategory
{
public int Id { get; set; }
public string Name { get; set; }
public string CatText { get; set; }
public string ImagePath { get; set; }
public int Priority { get; set; }
public int Viewd { get; set; }
public string Description { get; set; }
public bool Active { get; set; }
public DateTime CreateDate { get; set; }
public DateTime ModifyDate { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
my repo is
public async Task<IList<Product>> GetAllProductAsync()
{
return await _context.Products.Include(p => p.ProductCategory).ToListAsync();
}
my interface
public interface IProductRepository
{
...
Task<IList<Product>> GetAllProductAsync();
...
}
and this is my controller in api project
[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
private readonly IProductRepository _productRepository;
public ProductsController(IProductRepository productRepository)
{
_productRepository = productRepository;
}
[HttpGet]
public ActionResult Get()
{
return Ok(_productRepository.GetAllProduct());
}
}
When I run API project and put this URL: https://localhost:44397/api/products
I got that error,
I can't resolve it
this is happening because your data have a reference loop.
e.g
// this example creates a reference loop
var p = new Product()
{
ProductCategory = new ProductCategory()
{ products = new List<Product>() }
};
p.ProductCategory.products.Add(p); // <- this create the loop
var x = JsonSerializer.Serialize(p); // A possible object cycle was detected ...
You can not handle the reference loop situation in the new System.Text.Json yet (netcore 3.1.1) unless you completely ignore a reference and its not a good idea always. (using [JsonIgnore] attribute)
but you have two options to fix this.
you can use Newtonsoft.Json in your project instead of System.Text.Json (i linked an article for you)
Download the System.Text.Json preview package version 5.0.0-alpha.1.20071.1 from dotnet5 gallery (through Visual Studio's NuGet client):
option 1 usage:
services.AddMvc()
.AddNewtonsoftJson(
options => {
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});
// if you not using .AddMvc use these methods instead
//services.AddControllers().AddNewtonsoftJson(...);
//services.AddControllersWithViews().AddNewtonsoftJson(...);
//services.AddRazorPages().AddNewtonsoftJson(...);
option 2 usage:
// for manual serializer
var options = new JsonSerializerOptions
{
ReferenceHandling = ReferenceHandling.Preserve
};
string json = JsonSerializer.Serialize(objectWithLoops, options);
// -----------------------------------------
// for asp.net core 3.1 (globaly)
services.AddMvc()
.AddJsonOptions(o => {
o.JsonSerializerOptions
.ReferenceHandling = ReferenceHandling.Preserve
});
these serializers have ReferenceLoopHandling feature.
Edit : ReferenceHandling changed to ReferenceHandler in DotNet 5
but if you decide to just ignore one reference use [JsonIgnore] on one of these properties. but it causes null result on your API response for that field even when you don't have a reference loop.
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string ProductText { get; set; }
public int ProductCategoryId { get; set; }
// [JsonIgnore] HERE or
public virtual ProductCategory ProductCategory { get; set; }
}
public class ProductCategory
{
public int Id { get; set; }
// [JsonIgnore] or HERE
public ICollection<Product> products {get;set;}
}
.NET 5 Web API
public static void ConfigureServices(this IServiceCollection services, IConfiguration configuration)
{
services.AddControllers()
.AddJsonOptions(o => o.JsonSerializerOptions
.ReferenceHandler = ReferenceHandler.Preserve);
}
I have the same issue, my fix was to add async and await keyword since I am calling an async method on my business logic.
Here is my original code:
[HttpGet]
public IActionResult Get()
{
//This is async method and I am not using await and async feature .NET which triggers the error
var results = _repository.GetAllDataAsync();
return Ok(results);
}
To this one:
HttpGet]
public async Task<IActionResult> Get()
{
var results = await _repository.GetAllDataAsync();
return Ok(results);
}
In .Net 6, you can use System.Text.Json to initialize a startup action with AddControllersWithViews like this in Program.cs,
using System.Text.Json.Serialization;
builder.Services.AddControllersWithViews()
.AddJsonOptions(x => x.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles);
also you can use AddMvc like this,
builder.Services.AddMvc()
.AddJsonOptions(x => x.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles);
but quote from Ryan
asp.net core 3.0+ template use these new
methodsAddControllersWithViews,AddRazorPages,AddControllers instead of
AddMvc.
I will recommend to use the first solution.
Ensure you have [JsonIgnore] on the correct fields to avoid a circular reference.
In this case you will need
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string ProductText { get; set; }
[JsonIgnore]
public virtual ProductCategory ProductCategory { get; set; }
}
You probably don't need the ProductCategoryId field (depends if you are using EF and code first to define your DB)
Edit - In answer to noruk
There is often confusion in connected objects and navigation properties. You can get the data you want in JSON but also define the EF structures to get the correct DB structure (foreign keys, indexes, etc).
Take this simple example. A Product (for example a T-Shirt) has many sizes or SKUs (e.g. Small, Large, etc)
public class Product
{
[Key]
[MaxLength(50)]
public string Style { get; set; }
[MaxLength(255)]
public string Description { get; set; }
public List<Sku> Skus { get; set; }
}
public class Sku
{
[Key]
[MaxLength(50)]
public string Sku { get; set; }
[MaxLength(50)]
public string Barcode { get; set; }
public string Size { get; set; }
public decimal Price { get; set; }
// One to Many for Product
[JsonIgnore]
public Product Product { get; set; }
}
Here you can serialise a Product and the JSON data will include the SKUs. This is the normal way of doing things.
However if you serialise a SKU you will NOT get it's parent product. Including the navigation property will send you into the dreaded loop and throw the "object cycle was detected" error.
I know this is limiting in some use cases but I would suggest you follow this pattern and if you want the parent object available you fetch it separately based on the child.
var parent = dbContext.SKUs.Include(p => p.Product).First(s => s.Sku == "MY SKU").Product
I fixed my API Core Net6.0 adding [JsonIgnore]:
public class SubCategoryDto
{
public int Id { get; set; }
public string Name { get; set; }
public string Image { get; set; }
public int CategoryId { get; set; }
[JsonIgnore]
public Category Category { get; set; }
}
For net core 3.1 you have to add in Startup.cs:
services.AddMvc.AddJsonOptions(o => {
o.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve;
o.JsonSerializerOptions.MaxDepth = 0;
})
and import at least this package using nuget.org include prerelease:
<PackageReference Include="System.Text.Json" Version="5.0.0-rc.1.20451.14" />
following code is working for me in dotnet 5.0 :
services.AddControllersWithViews()
.AddJsonOptions(o => o.JsonSerializerOptions
.ReferenceHandler = ReferenceHandler.Preserve);
Finally fixed mine with System.Text.Json not NewtonSoft.Json using
var options = new JsonSerializerOptions()
{
MaxDepth = 0,
IgnoreNullValues = true,
IgnoreReadOnlyProperties = true
};
Using options to serialize
objstr = JsonSerializer.Serialize(obj,options);
My project built with a similar error.
Here's the code before
public class PrimaryClass {
public int PrimaryClassId
public ICollection<DependentClass> DependentClasses { get; set; }
}
public class DependentClass {
public int DependentClassId { get; set; }
public int PrimaryClassId { get; set; }
public PrimaryClass primaryClass { get; set; }
}
I took away the PrimaryClass object from the DependentClass model.
Code after
public class PrimaryClass {
public int PrimaryClassId
public ICollection<DependentClass> DependentClasses { get; set; }
}
public class DependentClass {
public int DependentClassId { get; set; }
public int PrimaryClassId { get; set; }
}
I also had to adjust the OnModelCreating method from
modelBuilder.Entity<PrimaryClass>().HasMany(p => p.DependentClasses).WithOne(d => d.primaryClass).HasForeignKey(d => d.PrimaryClassId);
to
modelBuilder.Entity<PrimaryClass>().HasMany(p => p.DependentClasses);
The DbSet query that's running is
public async Task<List<DependentClass>> GetPrimaryClassDependentClasses(PrimaryClass p)
{
return await _dbContext.DependentClass.Where(dep => dep.PrimaryClassId == p.PrimaryClassId).ToListAsync();
}
The error could have been with any of these 3 sections of code, but removing the primary object reference from the dependent class and adjusting the OnModelCreating resolved the error, I'm just not sure why that would cause a cycle.
In my case the problem was when creating the entity relationships. I linked the main entity using a foreign key inside the dependent entity like this
[ForeignKey("category_id")]
public Device_Category Device_Category { get; set; }
also I referred the dipendend entity inside the main entity as well.
public List<Device> devices { get; set; }
which created a cycle.
Dependent Entity
public class Device
{
[Key]
public int id { get; set; }
public int asset_number { get; set; }
public string brand { get; set; }
public string model_name { get; set; }
public string model_no { get; set; }
public string serial_no { get; set; }
public string os { get; set; }
public string os_version { get; set; }
public string note { get; set; }
public bool shared { get; set; }
public int week_limit { get; set; }
public bool auto_acceptance { get; set; }
public bool booking_availability { get; set; }
public bool hide_device { get; set; }
public bool last_booked_id { get; set; }
//getting the relationships category 1 to many
public int category_id { get; set; }
[ForeignKey("category_id")]
public Device_Category Device_Category { get; set; }
public List<Booking> Bookings { get; set; }
}
Main Entity
public class Device_Category
{
public int id { get; set; }
public string name { get; set; }
public List<Device> devices { get; set; }
}
}
So I commented the
public List<Device> devices { get; set; }
inside main entity (Device_Category) and problem solved

Mapping an IQueryable to domain model for OData in .NET

I recently implemented OData in my ASP .NET Core web API. I have found success as long as I am returning the database models directly. I run into trouble, however, as soon as I attempt to return domain models instead.
The underlying issue involves mapping a data class to a domain class while maintaining the IQueryable return type. While I have found partial success using AutoMapper's MapTo extension method, I find that I am unsuccessful when using the $extend method to expand a collection of entities that are also domain objects.
I have created a sample project to illustrate this issue. You may view or download the full project on github here. See the description below.
Given the following two database classes:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ICollection<Order> Orders { get; set; }
public Product() {
Orders = new Collection<Order>();
}
}
public class Order
{
public int Id { get; set; }
public Double Price { get; set; }
public DateTime OrderDate { get; set; }
[Required]
public int ProductId { get; set; }
public Product Product { get; set; }
}
And the following domain models...
public class ProductEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ICollection<OrderEntity> Orders { get; set; }
}
public class OrderEntity
{
public int Id { get; set; }
public Double Price { get; set; }
public DateTime OrderDate { get; set; }
[Required]
public int ProductId { get; set; }
public Product Product { get; set; }
}
And the Products Controller
public class ProductsController
{
private readonly SalesContext context;
public ProductsController(SalesContext context) {
this.context = context;
}
[EnableQuery]
public IQueryable<ProductEntity> Get() {
return context.Products
.ProjectTo<ProductEntity>()
.AsQueryable();
}
}
All the following OData queries Pass:
http://localhost:51004/odata/Products
http://localhost:51004/odata/Orders
http://localhost:51004/odata/Orders?$expand=Products
The following query, however, does not pass:
http://localhost:51004/odata/Products?$expand=Orders
An HTTP response is never returned. The only failure message I get comes from the console:
System.InvalidOperationException: Sequence contains no matching element at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source, Func`2 predicate)
Finally, here is a reference to the mapping profile:
public static class MappingProfile
{
public static void RegisterMappings() {
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Order, OrderEntity>();
cfg.CreateMap<Product, ProductEntity>();
});
}
}
I can solve the issue by simply returning a List instead of an IEnumerable in the controller, but this of course would trigger a large query against the database that would be performance intensive.
As stated above, you can find a link to the full project on Github here. Let me know if you find any answers!
I was able to get this working with a few small revisions.
Updating the domain models:
public class ProductEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ICollection<Order> Orders { get; set; }
}
public class OrderEntity
{
public int Id { get; set; }
public double Price { get; set; }
public DateTime OrderDate { get; set; }
[Required]
public int ProductId { get; set; }
public Product Product { get; set; }
}
Manually enabling expansion on the route builder:
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, SalesModelBuilder modelBuilder)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc(routeBuilder =>
{
routeBuilder.Expand().Select();
routeBuilder.MapODataServiceRoute("ODataRoutes", "odata",
modelBuilder.GetEdmModel(app.ApplicationServices));
});
}
Using the following Queries:
http://localhost:51004/odata/Products
http://localhost:51004/odata/Orders
http://localhost:51004/odata/Orders?$expand=Product
http://localhost:51004/odata/Products?$expand=Orders

Some many to one objects missing in DataGridView

HI I have two Tables( Movie as Movy Entity and Producers as Producer). Movie has one producer and producer has many movies.here is the er diag
Picture: ER Diagram
When i set the data soure of my DataGridView to ctx.Movies.ToList() some movie in the list doesnt have any producer. even though its not null in database.
Picture:Program with missing producer
//Movie Entity Class
public partial class Movy
{
public short VideoCode { get; set; }
public string MovieTitle { get; set; }
public string MovieType { get; set; }
public string Rating { get; set; }
public Nullable<float> RentalPrice { get; set; }
public string ProducerID { get; set; }
public string Director { get; set; }
public string Media { get; set; }
public Nullable<short> TotalStock { get; set; }
public Nullable<short> NumberRented { get; set; }
public virtual Producer Producer { get; set; }
}
}
//Producer Entity Class
public partial class Producer
{
public Producer()
{
this.Movies = new HashSet<Movy>();
}
public string ProducerID { get; set; }
public string ProducerName { get; set; }
public string CountryCode { get; set; }
public virtual ICollection<Movy> Movies { get; set; }
}
// MyDBEntities ctx
public partial class MYDBEntities : DbContext
{
public MYDBEntities()
: base("name=MYDBEntities")
{
}
public virtual DbSet<Movy> Movies { get; set; }
public virtual DbSet<Producer> Producers { get; set; }
}
//this is my function in windows form which is having some producer missing in movie datalist. i have added the picture of output
private void Form1_Load(object sender, EventArgs e)
{
MYDBEntities ct = new MYDBEntities();
dataGridView1.DataSource = ct.Movies.ToList();
}
P.S: [Picture:Database data]
/* i checked each product and its producer. some producer is null there.
even though its not null in database(I have uploaded db pic) [3]*/
List<Movy> ls = ct.Movies.ToList();
foreach(Movy mov in ls)
{
//some mov.Producer is null here.
}
You can try with Include method.
MYDBEntities ct = new MYDBEntities();
dataGridView1.DataSource = ct.Movies.Include("Producer").ToList();
Alright I found the problem. it is Entity Framework wasn't able to automap some string foreign key.
for me those movie was having producerID = 'Universal' foreign key, that movies Producer Object was null(Couldn't map). so once I update my database like using following query follwoing: it working now:
update Movies set Producer='Warner' where Producer='Universal';

Categories

Resources