I am trying to figure out the best way to do what I thought would be easy. I have a database model called Line that represents a line in an invoice.
It looks roughly
Your extra property is simply a calculation of the data from the model, but the property is not something EF can naturally translate. SQL is perfectly capable of performing this same calculation, and EF can translate the calculation. Use it in place of the property if you need it in your query.
Total = i.Lines.Sum( l => l.Price * l.Quantity)
While not an answer to your subject, it may be an answer to your problem:
Since you seem willing to modify the database, why not create a new View in the database that is basically
create view TotalledLine as
select *, Total = (Price * Quantity)
from LineTable;
and then change your data model to use TotalledLine instead of LineTable?
After all, if your application needs Total, it isn't a stretch to think others might, and centralizing that type of calculation into the database is one reason for using a database.
Just to add some of my own thoughts here. Because I am such a perfectionist I do not want to pull the entire contents of the table into memory just so I can do the calculation then sort then page. So somehow the table needs to know what the total is.
What I was thinking was to create a new field in the line table called 'CalcTotal' that contains the calculated total of the line. This value would be set every time the line is changed, based on the value of .Total
This has 2 advantages. Firstly, I can change the query to this:
var invoices = ( from c in _repository.Customers
where c.Id == id
from i in c.Invoices
select new InvoiceIndex
{
Id = i.Id,
CustomerName = i.Customer.Name,
Attention = i.Attention,
Total = i.Lines.Sum( l => l.CalcTotal ),
Posted = i.Created,
Salesman = i.Salesman.Name
}
)
Sorting and paging will work because its a db field. And I can do a check ( line.CalcTotal == line.Total ) in my controller to detect any tom foolery. It does cause a little bit of overhead in my repository, namely when I go to save or create a new line I would have to add
line.CalcTotal = line.Total
but I think it may be worth it.
Why go to the trouble of having 2 properties that have the same data?
Well its true, I could put
line.CalcTotal = line.Quantity * line.Price;
in my repository. This would not be very orthogonal though, and if some change to line totals needed to happen, it would make much more sense to edit the partial Line class than the Line repository.
I think easiest way to resolve this problem is using DelegateDecompiler.EntityFramework (make by Alexander Zaytsev)
It's a library which is able to decompile a delegate or a method body to its lambda representation.
Asume we have a class with a computed property
class Employee
{
[Computed]
public string FullName
{
get { return FirstName + " " + LastName; }
}
public string LastName { get; set; }
public string FirstName { get; set; }
}
And you are going to query employees by their full names
var employees = (from employee in db.Employees
where employee.FullName == "Test User"
select employee).Decompile().ToList();
When you call .Decompile method it decompiles your computed properties to their underlying representation and the query will become simmilar to the following query
var employees = (from employee in db.Employees
where (employee.FirstName + " " + employee.LastName) == "Test User"
select employee).ToList();
If your class doesn't have a [Computed] attribute, you can use the .Computed() extension method..
var employees = (from employee in db.Employees
where employee.FullName.Computed() == "Test User"
select employee).ToList();
Also, you can call methods that return a single item (Any, Count, First, Single, etc) as well as other methods in identical way like this:
bool exists = db.Employees.Decompile().Any(employee => employee.FullName == "Test User");
Again, the FullName property will be decompiled:
bool exists = db.Employees.Any(employee => (employee.FirstName + " " + employee.LastName) == "Test User");
Async Support with EntityFramework
The DelegateDecompiler.EntityFramework
package provides DecompileAsync
extension method which adds support for EF's Async operations.
You can find 8 way to mix some property values together in EF
here:
Computed Properties and Entity Framework. (written by Dave Glick)
Try something like this:
var invoices =
(from c in _repository.Customers
where c.Id == id
from i in c.Invoices
select new
{
Id = i.Id,
CustomerName = i.Customer.Name,
Attention = i.Attention,
Lines = i.Lines,
Posted = i.Created,
Salesman = i.Salesman.Name
})
.ToArray()
.Select (i => new InvoiceIndex
{
Id = i.Id,
CustomerName = i.CustomerName,
Attention = i.Attention,
Total = i.Lines.Sum(l => l.Total),
Posted = i.Posted,
Salesman = i.Salesman
});
Basically this queries the database for the records you need, brings them into memory and then does the summing once the data is there.
There is another way, which is a bit more complex, but gives you the ability to encapsulate this logic.
public partial class Line
{
public static Expression<Func<Line,Decimal>> TotalExpression
{
get
{
return l => l.Price * l.Quantity
}
}
}
Then rewrite the query to
var invoices = ( from c in _repository.Customers
where c.Id == id
from i in c.Invoices
select new InvoiceIndex
{
Id = i.Id,
CustomerName = i.Customer.Name,
Attention = i.Attention,
Total = i.Lines.AsQueryable().Sum(Line.TotalExpression),
Posted = i.Created,
Salesman = i.Salesman.Name
}
)
It worked for me, performs queries server-side and complies with the DRY rule.