【问题标题】:ASP.Net Core Web API - ICollection not showing up in JSON resultsetASP.Net Core Web API - ICollection 未显示在 JSON 结果集中
【发布时间】:2019-03-01 10:09:38
【问题描述】:

我正在尝试执行 3 个过程以构建客户对象并将其返回给客户端。但是,在我的 api(服务器端)中,我可以正确看到结果。但是,不幸的是,结果集中(客户端)中缺少 ICollection 数据

我正在使用 Entity FrameworkOData

所以,这是我的模型:

  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..

我觉得我错过了一个小细节,但我找不到它。

【问题讨论】:

  • XML 序列化在将接口集合作为成员名称时存在问题。也许这同样适用于 json。尝试换成更具体的集合接口或者具体的类(如IList&lt;&gt;List&lt;&gt;
  • 问题是我无法访问客户端(它是第 3 方)并且在文档中他们明确要求 ICollection 编辑:转换为 ilist 以进行测试并没有解决它:(跨度>

标签: c# json rest asp.net-core


【解决方案1】:

首先,如果您遵循命名约定,则无需显式使用属性。 Key 属性可以安全地删除。 ForeignKey 属性可以在将属性名称更改为复数名称时删除:ContactsAddresses。我强烈建议您对类使用单数名称,对集合导航属性使用复数名称(例如在一对多关系中)。

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

如果您使用的是 OData,您应该构建您的 EDM model(您没有向我们展示)。如果您有权访问DbContext,也不要对简单的SELECT 使用存储过程。 您的整个控制器逻辑是不必要的,可以替换为:

[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

【讨论】:

    【解决方案2】:

    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()
     {
    
     }
    

    【讨论】:

      【解决方案3】:

      我相信您的问题归结为以下几行:

      [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 更有效(如果不涉及多个表等,则没有优化的执行策略)。事实上,这样做,您正在查询所有联系人和所有地址,即使它们中只有一些或什至没有与您正在使用的客户相关。这可能是您通过网络无用传输的 数据。

      【讨论】:

      • 嗨!我想我还是很困惑。你能给我一个样品吗?
      • 我给了你代码示例。你到底对什么感到困惑?也许我们可以从那里开始工作。
      • 感谢您的回复。我按照你告诉我的做了,当我做断点时,我实际上可以看到带有地址和联系人的对象,但它仍然没有以 json 格式返回。
      • 看起来您正在使用 OData,因此无论您查询什么,它只会返回所请求的内容。您需要在请求中使用$expand 来获取相关项目。
      • 我确实在使用 OData。谢谢..我会调查的。客户端是由第三方应用程序编写的,并且在他们明确告知使用 OData 的文档中,但我没有在任何地方看到 $expand 函数
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-08-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-08
      • 1970-01-01
      • 2013-12-22
      相关资源
      最近更新 更多