0

I have asp.net web api application. I have the table Companies in the databse which have two fields: id and description. Recently I've updated the database and added a new column called CustomerID. After that when I am trying to call getCompanies

private readonly BackendContext _context;

public CompaniesController(BackendContext context)
{
    _context = context;
}

// GET: api/Companies
[HttpGet]
public IEnumerable<Company> GetCompanies()
{
    return _context.Companies;
}

I get

enter image description here

I think the controller tries to return the old companies model but can't achieve it because it doesnt exist now but I don't know how to fix this though the controller should return the updated model. Maybe I should somehow rebuild the app to make it use the updated version?

Additional code: Context

public class BackendContext : Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityDbContext<IdentityUser>//DbContext
    {
        public BackendContext(DbContextOptions<BackendContext> options) : base(options) { }

        public DbSet<Company> Companies { get; set; }
        public DbSet<CompanyToProduct> CompanyToProducts { get; set; }
        public DbSet<Product> Products { get; set; }
        public DbSet<Customer> Customers { get; set; }
        public DbSet<Vendor> Vendors { get; set; }
        public DbSet<VendorToProduct> VendorToProducts { get; set; }
        public DbSet<Invoice> Invoices { get; set; }
        public DbSet<InvoiceItem> InvoiceItems { get; set; }

    }

Model

public class Company
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }

        public int CustomerID { get; set; }
        public virtual Customer Customer { get; set; }

        public virtual ICollection<CompanyToProduct> CompaniesToProducts { get; set; }
        public virtual ICollection<Invoice> Invoices { get; set; }
    }

UPDATE I've added some values to the table and I got the response of the first company:

[{"id":1,"name":"Google","description":"free food","customerID":6,"customer":null,"companiesToProducts":null,"invoices":null}

BUT I also got the fields which is not specified in the table: customer, companiesToProducts,invoices. Invoices and companiesToProducts are tables in my database and I don't know what is customer referred to. I should also mention that these tables are connected by foreign key. enter image description here

enter image description here

UPDATE
Error: enter image description here

Storm
  • 557
  • 1
  • 8
  • 25
  • This could be related: https://stackoverflow.com/questions/44322809/net-core-webapi-connection-reset-when-i-have-include-in-my-controller If your models have cyclical references it could be failing to serialize. What happens if you append a `.ToList()` to the collection you're returning? Or use `.Select()` to project it into an anonymous type to specify the exact properties you want? – David Dec 23 '18 at 13:40
  • @David I am recieving the server error when I do it like this: return _context.Companies.ToList(); Startup modifying like in related question makes no change – Storm Dec 23 '18 at 13:54
  • Before messing with the startup I'd recommend projecting the models into a specific subset of values. After all, if these models reference other models, which reference other models, and so on, then are you really looking to return *that much* data in this API call? What is you return specifically the fields you want to return? – David Dec 23 '18 at 13:57
  • @David I want to return only the data about companies but the controller also returns another fields like customer, companiesToProducts,invoices (I've updated the post) which were not in the response before database modifying. Also it returns only the first company. I should edit that previously the data was returned correctly without adding ToList() etc. – Storm Dec 23 '18 at 14:03

1 Answers1

1

Based on the comments on the question above, it sounds like the related tables are all trying to serialize and the overall process is failing likely due to circular references in the object graph. This comment above in particular hints at a solution:

I want to return only the data about companies but the controller also returns another fields like customer, companiesToProducts,invoices

While it's convenient to just return directly from the data context, this has the added side-effect of coupling the API with the database (and with the data access framework, which appears to be the issue here). In API design in general it's always a good idea to explicitly define the "shape" of that API. The fields to return, etc.

Project your result into an explicitly defined shape and return only what you want to return:

var result = _context.Companies
                     .Select(c => new 
                     {
                         c.ID,
                         c.Name,
                         c.Description,
                         c.CustomerID
                     })
                     .ToList();

This defines specifically what you want to return, fetches only that information from the backing data, materializes it into an in-memory list, and finally then returns it through the API.

There is a potential downside to this, however. Because now we also need to change the return type of your API method. There are a couple options there, such as returning a generic response object or creating a view model which closely approximates your already existing model and starts to feel like duplication.

As with just about anything, it's a balance. Too far in any one direction and that direction starts to become a problem. Personally I often go the route of defining a view model to return:

public class CompanyViewModel
{
    public int ID { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public int CustomerID { get; set; }
}

and returning that:

return _context.Companies
               .Select(c => new CompanyViewModel
               {
                   ID = c.ID,
                   Name = c.Name,
                   Description = c.Description,
                   CustomID = c.CustomerID
               })
               .ToList();

But the reason I normally do this is because I normally work in an environment where the web application is just one application attached to a common shared business domain, so the view models don't feel like code duplication. They're in a separate project, often take a different shape than the backing data objects, etc. But if your domain models are already in your web project and that's the only project you have, there's a strong desire to want to return those.

Another option when that's the case could be to universally set your JSON serialization to ignore circular references:

services.AddMvc()
          .AddJsonOptions(
                options => options.SerializerSettings.ReferenceLoopHandling
                    = Newtonsoft.Json.ReferenceLoopHandling.Ignore );

But do keep in mind that this still couples your API to your DB models. Maybe that's okay in this project, but if you ever add a column to your DB that you don't want users to see then it becomes an issue. As with anything, you have options.

David
  • 208,112
  • 36
  • 198
  • 279
  • Can u help me to deal with the type error which I've updated to the post, pls. And one more question: Does such responses means that it's something wrong in my database? Also thanks a lot – Storm Dec 23 '18 at 14:26
  • @Storm: Oh, I overlooked the return type. Sorry about that :) What I usually do in that case is define a view model and project into that view model to return. It's kind of a trade-off between what feels like repeating code (having very similar models and view models) vs. wrangling with the DB framework as an API model. There's nothing wrong with the DB per se, it's just that EF creates model trees larger and more involved than what you'd generally want an API to return. – David Dec 23 '18 at 14:31