0

I'm having an issue performing a DTO projection with Entity Framework Core 2.2 whereby one class has a property that references another property from a parent class. When I attempt the projection a NullReferenceException is thrown because the parent object is null.

Here is a contrived example of what I'm trying to achieve:

public class Vendor
{
    public ICollection<Item> Items { get; set; }
    public decimal Tax { get; set; }
}

public class Item
{
    public Vendor Vendor { get; set; }
    public string Name { get; set; }
    public decimal ItemPrice { get; set; }
    public decimal TotalPrice => ItemPrice * (1 + Vendor.Tax);
}

public class ItemDto
{
    public string Name { get; set; }
    public decimal TotalPrice { get; set; }
}

private Task<IEnumerable<ItemDto>> Projection()
{
    var results = await _context.Item
        .Select(t => new ItemDto()
        {
            Name = t.Name,
            TotalPrice = t.TotalPrice
        })
        .ToListAsync();

    return results;
}

I am able to get this to work by either:

  • Grabbing the results of the query first and then performing the projection, but this ends up pulling unnecessary data from the database
  • Specifying TotalPrice = t.ItemPrice * (1 + t.Vendor.Tax) within the projection, but this is redefining the logic, so isn't ideal

I've also tried including Vendor in the query, but that doesn't help either. Any ideas on how to solve this would be greatly appreciated!

Adam Greenall
  • 189
  • 2
  • 10
  • `Vendor!=null ? ItemPrice * (1 + Vendor.Tax) : ItemPrice;` – Neil Mar 05 '21 at 11:36
  • @Neil that is a workaround, but there will never be a scenario whereby an Item does not have a corresponding Vendor so we would want it to fail if that was the case. In the example I've given, if I pull the data first, the Item does have an associated Vendor, but during the projection it's not able to access it because I guess it hasn't been populated yet as it's still just a query. – Adam Greenall Mar 05 '21 at 11:43
  • Actually, `Item` shouldn't have any logic in it IMO, it should just be used for persistance. – Neil Mar 05 '21 at 12:03
  • @Neil - I think you could be right about that and I believe the goal will eventually be to remove it, but it's just something I'm having to work around currently – Adam Greenall Mar 05 '21 at 16:10

2 Answers2

2

EF Translator cannot look into property body, so it cannot create appropriate query.

Simpler solution is to do that in the query

private Task<IEnumerable<ItemDto>> Projection()
{
    var results = await _context.Item
        .Select(t => new ItemDto()
        {
            Name = t.Name,
            TotalPrice = t.ItemPrice * (1 + t.Vendor.Tax)
        })
        .ToListAsync();

    return results;
}

For sure it is not reusable way. So maybe you will find this my answer helpful: https://stackoverflow.com/a/66386142/10646316

Svyatoslav Danyliv
  • 21,911
  • 3
  • 16
  • 32
  • I thought that would be the case. I'll take a look at your other answer, as it would be nice if I could avoid redefining the logic in the query. – Adam Greenall Mar 05 '21 at 12:15
0

Your projection is not actually loading all the data it requires:

private Task<IEnumerable<ItemDto>> Projection()
{
  var results = await _context.Item
    .Include(x=>x.Vendor) <--- Make sure you have all the data required
    .Select(t => new ItemDto()
    {
        Name = t.Name,
        TotalPrice = t.TotalPrice
    })
    .ToListAsync();

  return results;
}
Neil
  • 11,059
  • 3
  • 31
  • 56