使用switch语句过滤C#LINQ语句

时间:2018-10-03 00:00:57

标签: c# linq switch-statement

我将在这里使用一个简单的示例来说明我的问题。我正在尝试的是通过switch语句基于过滤的LINQ查询返回视图模型。我的代码是这样的:

var query = (
    from b in _db.Books
    join a in _db.Authors on b.AuthorId = a.Id
    select new BookViewModel
    {
       AuthorName = a.Name,
       BookName = b.Name
    });

switch (currentUser.Role)
{
     case "Admin": return query.ToList(); // Return all books for an admin.
     case "Publisher": return query.Where(x => x.Publisher == currentUser.PublisherId).ToList();
     default: throw new UnauthorizedAccessException(); // Given role is not authorized.
} 

例如,在“ Publisher”案例语句中,我要过滤并通过当前用户的发布者ID返回查询。但是我无法用当前的代码设置来实现这一点,因为我通过LINQ查询选择的BookViewModel对象中不存在此属性。

我喜欢这种使用switch语句进行过滤的方式。我发现代码非常可读。无需我将额外的属性添加到视图模型中的最完美的方法是什么?

谢谢。

6 个答案:

答案 0 :(得分:2)

如果我的理解正确,您可以将linq where与某些逻辑配合使用。

var query = (
    from b in _db.Books
    join a in _db.Authors on b.AuthorId = a.Id
    where (b.Publisher == currentUser.PublisherId && currentUser.Role == "Publisher") ||
          (currentUser.Role == "Admin")
    select new BookViewModel
    {
       AuthorName = a.Name,
       BookName = b.Name
    });

注意

linq中的Publisher可以更改为正确的上下文值。

答案 1 :(得分:0)

当您说case "Publisher": return query.Where(x => x.Publisher == currentUser.PublisherId).ToList();时,case语句x.Publisher就是BookViewModel中的一个属性。

但是您没有像使用AuthorNameBookName那样用任何值初始化该属性,因此Publisher将为空。

如何在不将Publisher属性添加到视图模型中的情况下解决此问题,完全取决于该属性的存储方式/存储位置,即您未提供的信息。

答案 2 :(得分:0)

您可以提取if("knitr" %in% installed.packages()[,1]){ library(knitr) }else{ install.packages("knitr") library(knitr) } 作为子查询来简化管理。我假设_db.Books在您的Publisher DbSet中。如果是这样,那么:

Books

答案 3 :(得分:0)

问题是您想从查询结果中使用属性Publisher,但又不想从函数中返回Publisher作为属性。

这意味着,在应用Where时,Publisher应该仍在您的项目中,但是在Where之后,应该将其删除。

解决方案是:在Select之后执行Where

(我更习惯于方法语法。当然,该方法也适用于查询语法

var initialQuery = db.Books
    .join(db.Authors                // join Books and Authors
    book => book.AuthorId,          // from every book take the AuthorId
    author => author.Id             // from every Author take the Id
    (book, author) => new           // when they match make one new object
    {                               // containing the matching book and author
        PublisherId = book.PublisherId,
        AuthorName = author.Name,
        BookTitle = book.Title,
    });

请注意,查询已创建,但尚未执行!您仍然可以使用其他LINQ语句扩展查询,而无需花费太多成本。

幸运的是,您的switch语句不会更改initialQuery的类型,因此您可以重复使用它:

switch (switch (currentUser.Role)
{
    case "Admin": 
        // nothing to do
        break;
    case "Publisher":
        initialQuery =  initialQuery
            .Where(joinResult => joinResult.Book.Publisher == currentUser.PublisherId);
        break;
    default: 
        throw new UnauthorizedAccessException();
} 

现在执行选择:

问题!

  

尽管您没有这么说,但是db.Booksdb.Authors是   数据库。他们实现IQueryable<Book>和   IQueryable<Author>。执行查询时,您只能使用   可以转换为SQL(或类似)的语句。这表示   new BookViewModel是IQueryable时不能使用。

解决方案:您必须先完成AsEnumerable,然后才能使用new BookViewModel进行最终选择

var queryResult = initialQuery
    // Transfer only the data you actually plan to use to your process
    .Select(joinResult => new
    { 
         AuthorName = joinResult.AuthorName,
         BookName = joinResult.BookTitle,
    })
    // move the selected data to local process in efficient way
    .AsEnumerable()
    // now you are free to use your local constructor:
    .Select(fetchedData => new BookViewModel()
    {
         BookName = fetchedData.BookName,
         AuthorName = fetchedData.AuthorName,
    });

顺便说一句:仍然没有执行查询。立即执行并返回所有获取的数据:

return queryResult.ToList();

答案 4 :(得分:0)

var query = (
    from b in _db.Books
    join a in _db.Authors on b.AuthorId = a.Id
    select new BookViewModel
    {
       AuthorName = a.Name,
       BookName = b.Name
    });

if (currentUser.Role.Equals("Admin")
{
    query = query.Where(x => x.Publisher == currentUser.PublisherId);
}

return query.ToList();

通过使用Authorize属性修饰该方法,可以将授权与逻辑分开(如果您使用的是MVC / MVC Core)。

答案 5 :(得分:0)

在LINQ中进行联接时,编译器会创建一个匿名对象,该对象称为透明标识符,这使它可以在范围内保留多个范围变量。当您从各个部分构建查询表达式时,可以包括LINQ表达式和扩展方法调用的混合(如下所示),您可以自己执行此操作,尽管在这种情况下,我猜它可能是一个不透明的标识符。

我同意@ErikE的观点,在进行到这一步之前,您的访问检查应该已经完成​​,所以下面的代码反映了这一点。

var query =
    from b in _db.Books
    join a in _db.Authors on b.AuthorId = a.Id
    select new { a, b };

// We'll test just the role that needs additional filtering.  Here,
// 'x' is the aforementioned opaque identifier.
if (currentUser.Role == "Publisher")
    query = query.Where(x => x.b.PublisherId == currentUser.PublisherId);

从这里开始,您可以在此基础上进一步扩展,例如将结果限制为由特定代理人代表的人所图解的书籍。这可能超出了您的需求,但它展示了该技术的灵活性。

if (illustratorAgentId != null)
{
    // Join the Illustrators table and test its AgentId.  Since we only need the book
    // and author, we can continue to project 'x'.
    query =
        from x in query
        join ill in _db.Illustrators on x.b.IllustratorId equals ill.IllustratorId
        where ill.AgentId == illustratorAgentId
        select x;
}

然后,当您完成查询的构建时,可以将范围变量的属性(通过不透明标识符)投影为BookViewModel

IQueryable<BookViewModel> result =
    from x in query
    select new BookViewModel
    {
        AuthorName = x.a.Name,
        BookName = x.b.Name
    };

然后您可以在result上调用任何常用的验证方法,在这种情况下,可能是ToList