Using a partial class property inside LINQ statement

后端 未结 6 851
灰色年华
灰色年华 2020-12-05 10:39

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

相关标签:
6条回答
  • 2020-12-05 10:55

    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)
    
    0 讨论(0)
  • 2020-12-05 11:00

    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.

    0 讨论(0)
  • 2020-12-05 11:04

    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.

    0 讨论(0)
  • 2020-12-05 11:11

    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.


    Explain:

    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.


    Additionally

    You can find 8 way to mix some property values together in EF here:

    Computed Properties and Entity Framework. (written by Dave Glick)

    0 讨论(0)
  • 2020-12-05 11:14

    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.

    0 讨论(0)
  • 2020-12-05 11:16

    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.

    0 讨论(0)
提交回复
热议问题