使用Linq查询将以下代码简化为单行代码

时间:2018-07-31 11:53:24

标签: c# entity-framework linq lambda

有两个表,User和Expense,具有一对多关系。因此,UserClass包含类型为ICollection <>的名为Expenses的导航属性。我想要的是让所有名字叫John的用户都包括在内,并且仅包括费用描述为“午餐”的费用。以下是我的想法。但是我觉得有更好的方法。我要转换此:

List<User> users = db.Users.Where(user => user.Name == "John").ToList();
            foreach (User user in users)
            {
                List<Expense> expenses = db.Expenses.Where(expense => expense.Description == "Lunch" && expense.AddedBy == user.Id).ToList();
                user.Expenses = expenses;
            }

像这样:

List<User> users = db.Users.Where(user => user.Name == "John").Include(user => user.Expenses.Where(expence => expence.Description == "Lunch")).ToList();

但它会引发以下异常

  

引发的异常:EntityFramework.dll中的“ System.ArgumentException”   包含路径表达式必须引用在类型上定义的导航属性。使用虚线路径作为参考导航属性,使用“选择”运算符作为集合导航属性。   参数名称:路径

我想要的SQL查询如下所示:

SELECT * FROM dbo.[User] AS A 
INNER JOIN Expense AS B 
ON A.Id = B.AddedBy
WHERE A.Name = 'John' AND B.Description = 'Lunch'

型号: Image

PS:我是实体框架的新手

谢谢。

2 个答案:

答案 0 :(得分:1)

我的主张是使用投影查询。

db.Users.Where(u => u.Name == "John")
            .Where(u => u.Expenses.Any(e => e.Description == "Lunch")
            .Select(u => new
            {
                u,
                Expenses= u.Expenses.Where(e => e.Description == "Lunch")
            });

一个名为u的属性的类型为User,它具有所有费用,另一个属性Expenses仅具有为Launch生成的那些费用< / p>

或使用Entity Framework Plus

db.Users.Where(user => user.Name == "John")
   .IncludeFilter(user => user.Expenses.Where(e => e.Description == "Launch"));

答案 1 :(得分:1)

通常,将所选数据从数据库传输到本地进程是查询中较慢的部分之一。因此,优良作法是不要获取比您实际计划使用的更多的属性。

您想要几个Users和它们的部分或全部Expenses。每个User都有一个主键Id,每个Expense都有一个外键UserId,您可以确定它等于其{{1 }}。

如果您获取1000个用户(每个用户有1000个费用),则您的方法将转移100万个Id,您已经知道其价值。真浪费!

  

仅当计划更改获取的值时才使用包括。如果没有,请使用Select`

要使用某些User来选择某些UserIds的某些属性:

Users

有时您确实需要Expenses,例如,您需要将它们用作过程的返回值。即使这样,也可以使用Select并填充用例中真正需要的属性(或填充所有属性)

var result = myDbContext.Users               // From the collection of all Users
    .Where(user => user.Name == ...)         // Select only those that have a name ...
    .Select(user => new                      // from every remaining user make one new object
    {
        // Select only the properties you actually plan to use:
        Id = user.Id,
        Name = user.Name,
        ...

        Expenses = user.Expenses
            .Where(expense => expense.Description == ...)
            .Select(expense => new
            {
                 // again, select only the properties you plan to use
                 Id = expense.Id,   
                 Description = expense.Description,

                 // not needed, you know it equals user.Id:
                 // UserId = expense.UserId

                 ...
            })
            .ToList(),
    });

此方法的缺点是数据库表的布局会渗入您的通信层。如果您的数据库发生更改,则所有调用者都需要更改,即使他们不使用新更改的属性。我的建议是在数据层之外使用所需的类。这可能会导致Users with their Expensesvar result = myDbContext.Users .Where(user => user.Name == ...) .Select(user => new User() { // Select only the properties you actually plan to use: Id = user.Id, Name = user.Name, ... Expenses = user.Expenses .Where(expense => expense.Description == ...) .Select(expense => new Expense() { // again, select only the properties you plan to use Id = expense.Id, Description = expense.Description, ... }) .ToList(), }); ,甚至可能导致UsersWithTheirAddresses。如果用户获得了诸如UsersWithTheirExpenses之类的额外列,则UsersWithTheirFoodExpenses无需更改。