WebApi无法返回包含另一个DTO列表的DTO

时间:2016-08-04 08:04:26

标签: c# entity-framework asp.net-web-api dto

我有一张我想要退回的发票DTO,其中包含发票项目列表,其中InvoiceLines是联结表格。

我的WebApi控制器代码:

var screenset =
    from inv in context.Invoices where inv.InvoiceId == invoiceID
    join line in context.InvoiceLines on inv.InvoiceId equals line.InvoiceId
    join track in context.Tracks on line.TrackId equals track.TrackId into T
    select new InvoiceDTO
    {
        InvoiceId = inv.InvoiceId,
        InvoiceDate = inv.InvoiceDate,
        CustomerId = inv.CustomerId,
        CustomerFullName = inv.Customer.LastName + ", " + inv.Customer.FirstName,
        CustomerPhoneNumber = inv.Customer.Phone,
        BillingAddress = inv.BillingAddress,
        BillingCity = inv.BillingCity,
        BillingState = inv.BillingState,
        BillingCountry = inv.BillingCountry,
        BillingPostalCode = inv.BillingPostalCode,
        Tracks = T.Select(t => new InvoiceTrackDTO
        {
            InvoiceLineId = line.InvoiceLineId,
            TrackId = t.TrackId,
            TrackName = t.Name,
            Artist = t.Album.Artist.Name,
            UnitPrice = line.UnitPrice,
            Quantity = line.Quantity
        })
    };

    var result = screenset.SingleOrDefault();
    var response = Request.CreateResponse(HttpStatusCode.OK, result);
    response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");

    return response;

我的DTO定义如下:

public class InvoiceDTO
{
    public int InvoiceId { get; set; }
    public DateTime InvoiceDate { get; set; }
    public int CustomerId { get; set; }
    public string CustomerFullName { get; set; }
    public string CustomerPhoneNumber { get; set; }
    public string BillingAddress { get; set; }
    public string BillingCity { get; set; }
    public string BillingState { get; set; }
    public string BillingCountry { get; set; }
    public string BillingPostalCode { get; set; }
    public IEnumerable<InvoiceTrackDTO> Tracks { get; set; }
}

public class InvoiceTrackDTO
{
    public int InvoiceLineId { get; set; }
    public int TrackId { get; set; }
    public string TrackName { get; set; }
    public string Artist { get; set; }
    public decimal UnitPrice { get; set; }
    public int Quantity { get; set; }
}

我的消费者代码是:

message = new HttpClient().GetMessage(host, path, q).Result;
invoice = message.GetObjectFromMessage<InvoiceDTO>().Result;
tracks = invoice.Tracks as IEnumerable<InvoiceTrackDTO>;

我的问题是,如果使用.ToList()调用ApiController,则会出错:

var result = screenset.ToList();

如果ApiController以.SingleOrDefault()返回,则它会成功返回发票,但发票项目不能超过一个。

var result = screenset.SingleOrDefault();

我正在尝试根据其ID获取单一发票,但发票中包含发票项目列表。

这可能吗?

提前致谢。

修改

如果我在消费者中这样做:

message = new HttpClient().GetMessage(host, path, q).Result;
invoice = message.GetObjectFromMessage<InvoiceDTO>().Result;
Debug.WriteLine("Invoice: " + invoice.ToJsonString());
tracks = invoice.Tracks as IEnumerable<InvoiceTrackDTO>;
Debug.WriteLine("Tracks: " + tracks.ToJsonString());

其中ToJsonString是我将任何对象转换为Json字符串的扩展方法。我可以获得发票,但不能获得跟踪作为发票项目的曲目。

我得到的输出和错误如下:

  

发票:{&#34; InvoiceId&#34;:0,&#34; InvoiceDate&#34;:&#34; 0001-01-01T00:00:00&#34;,&#34; CustomerId&#34 ;:0,&#34; CustomerFullName&#34;:空,&#34; CustomerPhoneNumber&#34;:空,&#34; BillingAddress&#34;:空,&#34;结算城市&#34;:空,&# 34; BillingState&#34;:空,&#34; BillingCountry&#34;:空,&#34; BillingPostalCode&#34;:空,&#34;曲目&#34;:空}

     

曲目:null

     

抛出异常:&#39; System.ArgumentOutOfRangeException&#39;在System.Windows.Forms.dll

编辑2: 好的,只是为了清理一点。

我正在将内连接与组连接进行比较,如下所示:

Parent  
Id  Value  
1   A  
2   B  
3   C  

Child  
Id  ChildValue  
1   a1  
1   a2  
1   a3  
2   b1  
2   b2  

内部加入

from p in Parent
join c in Child on p.Id equals c.Id
select new { p.Value, c.ChildValue }

Result  
Value ChildValue  
A     a1  
A     a2  
A     a3  
B     b1  
B     b2  

群组加入

from p in Parent
join c in Child on p.Id equals c.Id into g
select new { Parent = p, Children = g }

Result  
Value  ChildValues  
A      [a1, a2, a3]  
B      [b1, b2]  
C      []  

使用LINQ从Entity Framework返回时,内部联接可以工作,但不能进行组连接。

将像内连接一样返回组连接。

希望澄清我的问题:)

1 个答案:

答案 0 :(得分:0)

好的,我意识到了这个问题。实体框架最终基于L​​inq生成SQL。在SQL中,没有包含子对象列表的父对象的概念。返回的记录数将是重复父列的子对象数。

我最初很匆忙,忘了简单的浏览器或Fiddler输出会显示原来的ApiController方法会返回这个:

Single object containing child objects

所以修改你的DTO:

public class InvoiceAndItemsDTO
{
    // Invoice
    public int InvoiceId { get; set; }
    public DateTime InvoiceDate { get; set; }
    public int CustomerId { get; set; }
    public string CustomerFullName { get; set; }
    public string CustomerPhoneNumber { get; set; }
    public string BillingAddress { get; set; }
    public string BillingCity { get; set; }
    public string BillingState { get; set; }
    public string BillingCountry { get; set; }
    public string BillingPostalCode { get; set; }
    // Track
    public int InvoiceLineId { get; set; }
    public int TrackId { get; set; }
    public string TrackName { get; set; }
    public string Artist { get; set; }
    public decimal UnitPrice { get; set; }
    public int Quantity { get; set; }
}

并修改你的ApiController方法:

var screenset =
from inv in context.Invoices where inv.InvoiceId == invoiceID
join line in context.InvoiceLines on inv.InvoiceId equals line.InvoiceId
join track in context.Tracks on line.TrackId equals track.TrackId
select new InvoiceAndItemsDTO
{
    InvoiceId = inv.InvoiceId,
    InvoiceDate = inv.InvoiceDate,
    CustomerId = inv.CustomerId,
    CustomerFullName = inv.Customer.LastName + ", " +       inv.Customer.FirstName,
    CustomerPhoneNumber = inv.Customer.Phone,
    BillingAddress = inv.BillingAddress,
    BillingCity = inv.BillingCity,
    BillingState = inv.BillingState,
    BillingCountry = inv.BillingCountry,
    BillingPostalCode = inv.BillingPostalCode,
    InvoiceLineId = line.InvoiceLineId,
    TrackId = track.TrackId,
    TrackName = track.Name,
    Artist = track.Album.Artist.Name,
    UnitPrice = line.UnitPrice,
    Quantity = line.Quantity
};

和您的消费者一样:

message = new HttpClient().GetMessage(host, path, q).Result;
tracks = message.GetObjectFromMessage<List<InvoiceAndItemsDTO>>().Result;
if (tracks != null)
{
    invoice = tracks.First();
}