繁体   English   中英

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

[英]Filtering A C# LINQ Statement With A 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语句进行过滤的方式。 我发现代码非常可读。 在无需将额外属性添加到视图模型的情况下,最理想的实现此目的的方法是什么?

谢谢。

如果我理解正确,则可以使用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可以更改为正确的上下文值。

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

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

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

您可以将_db.Books提取为子查询,以便于管理。 我假设Publisher在您的Books DbSet中。 如果是这样,那么:

var bookQueryable = _db.Books.AsQueryable();

switch (currentUser.Role)
{
     case "Admin": break;
     case "Publisher": bookQueryable = bookQueryable.Where(x => x.Publisher == currentUser.PublisherId);
     default: throw new UnauthorizedAccessException(); // Given role is not authorized.
} 

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

return query.ToList();

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

这意味着,当Where被应用, Publisher仍然应该在你的项目,但之后Where它应该被删除。

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

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

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(或类似语言)的语句。 这意味着当它是IQueryable时,您不能使用new BookViewModel

解决方案:您必须将其设置为AsEnumerable ,然后才能使用new BookViewModel最终的Select。

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();
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)。

当您在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;
}

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM