Базовый веб-API ASP.Net - ICollection не отображается в наборе результатов JSON - PullRequest
0 голосов
/ 01 марта 2019

Я пытаюсь выполнить 3 процедуры, чтобы построить объект клиента и вернуть его клиенту.Тем не менее, внутри моего API (на стороне сервера) я могу видеть результат правильно.Но, к сожалению, данные ICollection отсутствуют в наборе результатов (на стороне клиента)

Я использую Entity Framework и OData

Итак,вот моя модель:

  public class Customer
    {
        [Key]
        public Guid Id { get; set; }
        public string Code { get; set; }
        public string Name { get; set; }
        public string VatCode { get; set; }
        public string ChamberOfCommerceCode { get; set; }
        public DateTime Modified { get; set; }
        public DateTime Created { get; set; }
        public string LanguageCode { get; set; }
        public decimal Discount { get; set; }
        public string CustomerManager { get; set; }
        public Guid PriceList { get; set; }
        public Guid PaymentCondition { get; set; }
       // public bool VatLiable { get; set; }
        public bool IsBlocked { get; set; }
        public bool IsProspect { get; set; }
        public bool IsSuspect { get; set; }
        public string Website { get; set; }
        public string DashboardUrl { get; set; }
        public string Email { get; set; }
        public string Phone { get; set; }
        public string Fax { get; set; }
        public ICollection<FreeFields> FreeFields { get; set; }
  //      [NotMapped]
      //  public Dictionary<string, string> UknownElements { get; set; }
        [ForeignKey(nameof(Contacts.Customer))]
        public ICollection<Contacts> Contact { get; set; }
        [ForeignKey(nameof(Addresses.Customer))]
        public ICollection<Addresses> Address { get; set; }
    }

Как видите, у нас есть 2 ICollections: контакты и адреса.Вот модели этих двух:

Контакты , модель

public class Contacts
    {
        [Key]
        public Guid Id { get; set; }
        public string FirstName { get; set; }
        public string MiddleName { get; set; }
        public string LastName { get; set; }
        public string Initials { get; set; }
        public string Function { get; set; }
        [Column("Customer")]
        public Guid Customer { get; set; }
        public string Email { get; set; }
        public string Phone { get; set; }
        public string Mobile { get; set; }
        public string LanguageCode { get; set; }
        public bool IsMainContact { get; set; }
        public string Gender { get; set; }
        public string Username { get; set; }
    }

и Адреса

 public class Addresses
    {
        [Key]
        public Guid Id { get; set; }
        public string AddressLine1 { get; set; }
        public string AddressLine2 { get; set; }
        public string AddressLine3 { get; set; }
        public string PostalCode { get; set; }
        public string City { get; set; }
        public string Country { get; set; }
        public string CountryCode { get; set; }
        public string Type { get; set; }
        [Column("Customer")]
        public Guid Customer { get; set; }// This Property should be GUID instead of String..
        public bool IsMainAddress { get; set; }
        public string Route { get; set; }
        public string State { get; set; }
    }

Теперь внутриМой контроллер Я извлекаю данные из хранимых процедур и создаю модель:

  public IList<Customer> GetAllCustomers()
        {
            //Initialize the objects
            IList<Customer> customers = null;
            ICollection<Contacts> contacts = null;
            ICollection<Addresses> addresses = null;
            Dictionary<string, string> thisdict = null;
            //Fetch the data from stored procedures
            customers = db.Customers.FromSql("SIP_API_MONDIA_Customers_sel").ToList();
            contacts = db.Contacts.FromSql("SIP_API_MONDIA_Contacts_sel").ToList();
            addresses = db.Addresses.FromSql("SIP_API_MONDIA_Address_sel").ToList();

            //Loop through customers and add the contact and addresses when required
            foreach(var item in customers)
            {
                item.Contact = contacts.Where(x => x.Customer == item.Id).ToList();
                item.Address = addresses.Where(x => x.Customer == item.Id).ToList();
             //   item.UknownElements = thisdict;

            }
            return customers;
        }

Tihs работает просто отлично, с точкой останова я вижу, что объект Customers заполнен.Также адреса и контакты верны.Но потом, как только я возвращаю его клиенту, результат пропускает обе ICollections ..

enter image description here

Я чувствую, что мне не хватает одной маленькой детали, но яне найти его.

Ответы [ 3 ]

0 голосов
/ 05 марта 2019

Итак, прежде всего, если вы следуете соглашениям по именованию, вам не нужно явно использовать атрибуты.Key атрибут может быть безопасно удален.Атрибут ForeignKey можно удалить, если вы измените имя свойства на множественное число: Contacts и Addresses.Я настоятельно рекомендую вам использовать единичные имена для классов и множественные имена для свойств навигации по коллекциям (например, в отношении «один ко многим»).

// Contact is name of class, plural name for property: Contacts
public ICollection<Contact> Contacts { get; set; }

Если вы используете OData, вы должны создать свою Модель EDM (которую вы нам не показали).Также не используйте хранимые процедуры для простого SELECT, если у вас есть доступ к DbContext.Вся логика вашего контроллера не нужна и может быть заменена следующим образом:

[EnableQuery]
public IQueryable<Customer> GetAllCustomers()
{
    return db.Customers;
}

Таким образом, в коде это может выглядеть следующим образом:

Класс клиента

public class Customer
{
    public Guid Id { get; set; }
    public string Code { get; set; }
    public string Name { get; set; }
    public string VatCode { get; set; }
    public string ChamberOfCommerceCode { get; set; }
    public DateTime Modified { get; set; }
    public DateTime Created { get; set; }
    public string LanguageCode { get; set; }
    public decimal Discount { get; set; }
    public string CustomerManager { get; set; }
    public Guid PriceList { get; set; }
    public Guid PaymentCondition { get; set; }
    public bool IsBlocked { get; set; }
    public bool IsProspect { get; set; }
    public bool IsSuspect { get; set; }
    public string Website { get; set; }
    public string DashboardUrl { get; set; }
    public string Email { get; set; }
    public string Phone { get; set; }
    public string Fax { get; set; }
    public ICollection<FreeFields> FreeFields { get; set; }

    public ICollection<Contact> Contacts { get; set; }
    public ICollection<Address> Addresses { get; set; }
}

Класс контакта

public class Contact
{
    public Guid Id { get; set; }
    public string FirstName { get; set; }
    public string MiddleName { get; set; }
    public string LastName { get; set; }
    public string Initials { get; set; }
    public string Function { get; set; }
    public Guid Customer { get; set; }
    public string Email { get; set; }
    public string Phone { get; set; }
    public string Mobile { get; set; }
    public string LanguageCode { get; set; }
    public bool IsMainContact { get; set; }
    public string Gender { get; set; }
    public string Username { get; set; }

    public Guid CustomerId { get; set; }
    // If you want to use class reference navigation property (also called as "hard reference").
    // That can be used in "$expand" or "$select" for example.
    // Uncomment the following line:
    // public Customer Customer { get; set }
}

Адрес класса

public class Address
{
    public Guid Id { get; set; }
    public string AddressLine1 { get; set; }
    public string AddressLine2 { get; set; }
    public string AddressLine3 { get; set; }
    public string PostalCode { get; set; }
    public string City { get; set; }
    public string Country { get; set; }
    public string CountryCode { get; set; }
    public string Type { get; set; }
    public bool IsMainAddress { get; set; }
    public string Route { get; set; }
    public string State { get; set; }

    public Guid CustomerId { get; set; }
    // If you want to use class reference navigation property (also called as "hard reference").
    // That can be used in "$expand" or "$select" for example.
    // Uncomment the following line:
    // public Customer Customer { get; set }
}

Создайте модель EDM:

public class MyModelBuilder
{
    public IEdmModel GetEdmModel(IServiceProvider serviceProvider)
    {
        var builder = new ODataConventionModelBuilder(serviceProvider);

        builder.EntitySet<Address>("Addresses")
                        .EntityType
                        .Filter() // Allow for the $filter Command
                        .Count() // Allow for the $count Command
                        .Expand() // Allow for the $expand Command
                        .OrderBy() // Allow for the $orderby Command
                        .Page() // Allow for the $top and $skip Commands
                        .Select();// Allow for the $select Command; 

        builder.EntitySet<Contact>("Contacts")
                        .EntityType
                        .Filter() // Allow for the $filter Command
                        .Count() // Allow for the $count Command
                        .Expand() // Allow for the $expand Command
                        .OrderBy() // Allow for the $orderby Command
                        .Page() // Allow for the $top and $skip Commands
                        .Select() // Allow for the $select Command
                        .Expand(); 

        builder.EntitySet<Customer>("Customers")
                        .EntityType
                        .Filter() // Allow for the $filter Command
                        .Count() // Allow for the $count Command
                        .Expand() // Allow for the $expand Command
                        .OrderBy() // Allow for the $orderby Command
                        .Page() // Allow for the $top and $skip Commands
                        .Select() // Allow for the $select Command
                        .Expand(); 

        return builder.GetEdmModel();
    }
}

Используйте его в Запуск :

public void ConfigureServices(IServiceCollection services)
{
    // ... Other Configurations 

    services.AddOData();
    services.AddTransient<MyModelBuilder>();

    // ... MVC Service Configurations 
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, MyModelBuilder modelBuilder)
{
    // ... Other Configurations

    app.UseMvc(routeBuilder =>
    {
        routeBuilder.MapODataServiceRoute("ODataRoutes", "odata", modelBuilder.GetEdmModel(app.ApplicationServices));
    });
}

И, наконец, создайте контроллер:

[Produces("application/json")]
public class CustomersController : ODataController
{
    private readonly MyDbContext _context;

    public CustomersController (MyDbContext context) => _context = context;

    [EnableQuery]
    public IQueryable<Customer> GetAllCustomers() => _context.Customers;
}

(В приведенном выше коде я предполагаю, что вы правильно настроили DbContext)

Теперь вы сможете использовать $expand или $select, чтобы получить, например, все адреса клиентов.

HTTP GET /odata/Customers?$expand=Addresses
0 голосов
/ 09 марта 2019

Odata не поддерживает активную загрузку навигационных свойств без использования раскрытия.Но вы можете принудительно включить расширение в запрос, используя перехватчик и меняя URL.Ниже приведена реализация:

using System;
using System.Web.Http.Controllers;
using System.Web.Http.OData;

namespace ODataWebAPI.API
{
    public class EnableQueryForExpand : EnableQueryAttribute
    {
       public override void OnActionExecuting(HttpActionContext actionContext)
       {
           var url = actionContext.Request.RequestUri.OriginalString;
           var newUrl = ModifyUrl(url);
           actionContext.Request.RequestUri = new Uri(newUrl);
           base.OnActionExecuting(actionContext);
       }

       private string ModifyUrl(string url)
       {
           if (!url.Contains("expand"))
           {
               if (url.Contains("?$"))
               {
                   url = url + "&$";
               }
               else
               {
                   url = url + "?$";
               }
               url = url + "expand=Category,Supplier";
           }
           return url;
       }
   }
}

, и вы используете этот атрибут в методе контроллера:

 [EnableQueryForExpand]
 public IQueryable<Customer> GetAllCustomers()
 {

 }
0 голосов
/ 01 марта 2019

Я полагаю, что ваша проблема сводится к следующим строкам:

[ForeignKey(nameof(Contacts.Customer))]
public ICollection<Contacts> Contact { get; set; }
[ForeignKey(nameof(Addresses.Customer))]
public ICollection<Addresses> Address { get; set; }

ForeignKey применяется только к фактическому свойству внешнего ключа или свойству навигации для этого внешнего ключа:

[ForeignKey(nameof(Customer))]
public Guid CustomerId { get; set; }

public Customer Customer { get; set; }

Или

public Guid CustomerId { get; set; }

[ForeignKey(nameof(CustomerId)]
public Customer Customer { get; set; }

Для свойства коллекции необходимо использовать InverseProperty:

[InverseProperty(nameof(Contacts.Customer))]
public ICollection<Contacts> Contact { get; set; }

Длинно и коротко, я не думаю, что EF правильно создает отношения, потому чтовашей ошибки здесь, и поэтому вы получаете пустые списки контактов и адресов, потому что буквально нет связанных контактов или адресов.

FWIW, использование хранимых процедур здесь очень неэффективно, иоткровенно ненужный.Хранимая процедура для выполнения простого SELECT не более эффективна, чем просто выдача SELECT (нет стратегии выполнения для оптимизации, если нет нескольких таблиц и тому подобных).Фактически, делая это таким образом, вы запрашиваете все контакты и все адреса, даже если только некоторые или даже ни один из них не связан с клиентами, с которыми вы работаете.Это может быть тонна данных, которые вы бесполезно передаете по проводам.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...