I do not fully understand why nested invokes in select do not work within LinqKit and was wondering if someone would be able to help me understand.
My problem:
First let me line out what works.
So lets say we have three db objects:
public class Customer {
public int Id {get; set;}
public string Name {get; set;}
public ICollection<Address> Addresses {get;set;}
}
public class Address {
public int Id {get;set;}
public string AddressLine {get;set;}
public int? Customer_Id { get; set; }
[ForeignKey("Customer_Id")]
public virtual Customer Customer {get; set;}
public int? Coordinates_Id { get; set; }
[ForeignKey("Coordinates_Id")]
public virtual Coordinates Coordinates {get; set;}
}
public class Coordinates {
public int Id {get;set;}
public double Latitude {get;set;}
public double Longitude {get;set;}
}
And I have three models
public class CustomerModel {
public int Id {get; set;}
public string Name {get;set;}
public AddressModel Address {get;set;}
}
public class AddressModel{
public int Id {get;set;}
public string AddressLine {get;set;}
public CoordinatesModel Coordinates {get;set;}
}
public class CoordinatesModel {
public int Id {get;set;}
public double Latitude {get;set;}
public double Longitude {get;set;}
}
So, now I want to create reusable select expressions. So I create these
public static Expression<Func<Address, AddressModel>> ToAddressModel = address =>
new AddressModel {
Id = address.Id,
AddressLine = address.AddressLine,
Coordinates = new Coordinates {
Id = address.Coordinates.Id,
Latitude = address.Coordinates.Latitude,
Longitude = address.Coordinates.Longitude
}
};
public static Expression<Func<Customer, CustomerModel>> ToCustomerModel = customer =>
new CustomerModel {
Id = customer.Id,
Name = customer.Name
Address = customer.Addresses.AsQueryable().Select(ToAddressModel).ToList()
};
And finally if I go to query this I would write
dbContext.Customers
.AsExpandable()
.Select(ToCustomerModel)
.ToList();
This works as expected. But I would like to now make CoordinatesModel more reusable and make an Expression to map it and invoke it in AddressModels Expression.
public static Expression<Func<Coordinates, CoordinatesModel>> ToCoordinates = coordinates =>
new Coordinates {
Id = coordinates.Id,
Latitude = coordinates.Latitude,
Longitude = coordinates.Longitude
}
public static Expression<Func<Address, AddressModel>> ToAddressModel = address =>
new AddressModel {
Id = address.Id,
AddressLine = address.AddressLine,
Coordinates = ToCoordinatesModel.Invoke(address.Coordinates)
};
Now if I query
dbContext.Customers
.AsExpandable()
.Select(ToCustomerModel)
.ToList();
It throws
System.NotSupportedException: LINQ to Entities does not recognize the method 'CoordinatesModel Invoke[Coordinates,Model](System.Linq.Expressions.Expression`1[System.Func`2[Coordinates,CoordinatesModel]], Coordinates)' method
This is fine if this is incorrect, I was just trying to understand why this is incorrect. If I don't invoke against a collection (like indefinite navigation properties) its fine with nested invokes.
You have to expressly tell linq that you are expanding the sub expressions - just like when you utilize .AsExpandable()
on the table - when invoking in a sub set you must expand the parent to traverse the tree and expand the sub expressions.
So.... all you need is to add an Expand
to the parent expression, in this case
public static Expression<Func<Customer, CustomerModel>> ToCustomerModel = customer =>
new CustomerModel {
Id = customer.Id,
Name = customer.Name
Address = customer.Addresses.AsQueryable().Select(ToAddressModel.Expand()).ToList()
};
The navigation properties are fine, because when you expand the parent of that it will pull in all the sub expressions in order to expand the expression - but the selects (I can only assume are another projection) don't go unless explicitly stated.
Here is the documentation: http://www.albahari.com/nutshell/linqkit.aspx