简体   繁体   English

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

[英]Filtering A C# LINQ Statement With A Switch Statement

I will use a simple example here to explain my problem. 我将在这里使用一个简单的示例来说明我的问题。 What I am trying to so is return a view model based upon a filtered LINQ query via a switch statement. 我正在尝试的是通过switch语句基于过滤的LINQ查询返回视图模型。 My code reads like this: 我的代码如下所示:

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.
} 

In the "Publisher" case statement I want to filter and return the query by the current user's publisher ID, for example. 例如,在“ Publisher”案例语句中,我想按当前用户的发布者ID过滤并返回查询。 But I cannot achieve this with my current code setup because this property does not exist in the BookViewModel object which I am selecting with my LINQ query. 但是我无法用当前的代码设置来实现这一点,因为该属性在我使用LINQ查询选择的BookViewModel对象中不存在。

I like this way of filtering using a switch statement. 我喜欢使用switch语句进行过滤的方式。 I find the code very readable. 我发现代码非常可读。 What is the most elegant way to achieve this please without my having to add the additional property to the view model? 在无需将额外属性添加到视图模型的情况下,最理想的实现此目的的方法是什么?

Thanks. 谢谢。

If I understand correctly, you can use linq where with some logic to make it. 如果我理解正确,则可以使用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
    });

NOTE 注意

The Publisher in the linq can change to your correctly context value. linq中的Publisher可以更改为正确的上下文值。

The case statement case "Publisher": return query.Where(x => x.Publisher == currentUser.PublisherId).ToList(); case语句case "Publisher": return query.Where(x => x.Publisher == currentUser.PublisherId).ToList(); is referring to a property in your BookViewModel when you say x.Publisher . 当您说x.Publisher时,它指的是BookViewModel中的属性。

But you have not initialized that property with any value like you did with AuthorName and BookName , thus, Publisher will be null. 但是您没有像使用AuthorNameBookName那样使用任何值初始化该属性,因此Publisher将为null。

How to resolve this issue without adding the Publisher property to your view model depends entirely on how/where that property is stored, which is information you did not provide. 如何在不将Publisher属性添加到视图模型的情况下解决此问题,完全取决于该属性的存储方式/存储位置,即您未提供的信息。

You can extract _db.Books as a subquery for easier management. 您可以将_db.Books提取为子查询,以便于管理。 I'm assuming Publisher is in your Books DbSet. 我假设Publisher在您的Books DbSet中。 If that's the case then: 如果是这样,那么:

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

The problem is that you want to use property Publisher from the result of your query, but you don't want to return Publisher as a property from your function. 问题是您想从查询结果中使用属性Publisher ,但又不想从函数中将Publisher作为属性返回。

This means, that when the Where is applied, Publisher should still be in your items, but after the Where it should have been removed. 这意味着,当Where被应用, Publisher仍然应该在你的项目,但之后Where它应该被删除。

The solution is: do the Select after the Where : 解决方案是:在Where之后执行Select

(I'm more accustomed to method syntax. Of course this method also works for query syntax (我更习惯于方法语法。当然,该方法也适用于查询语法

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,
    });

Note that the query is created, but it is not executed yet! 请注意,查询已创建,但尚未执行! You still can extend the query with other LINQ statements without much cost. 您仍然可以使用其他LINQ语句扩展查询,而无需花费太多成本。

Luckily, your switch statement doesn't change the type of the initialQuery, so you can re-use it: 幸运的是,您的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();
} 

Now do the select: 现在执行选择:

Problem! 问题!

Although you didn't say so, db.Books and db.Authors are tables in a database. 尽管您没有这么说, db.Booksdb.Authors是数据库中的表。 They implement IQueryable<Book> and IQueryable<Author> . 它们实现IQueryable<Book>IQueryable<Author> When executing the query you can only use statements that can be translated into SQL (or similar). 执行查询时,您只能使用可以转换为SQL(或类似语言)的语句。 This means that you can't use new BookViewModel while it is an IQueryable. 这意味着当它是IQueryable时,您不能使用new BookViewModel

Solution: You'll have to make it AsEnumerable , after which you can do the final Select with your 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,
    });

By the way: still no query performed. 顺便说一句:仍然没有执行查询。 Perform it now and return all fetched data: 立即执行并返回所有获取的数据:

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

Authorization may be separated from logic by decorating the method with Authorize attribute (if you are using MVC/MVC Core). 通过使用Authorize属性装饰该方法,可以将授权与逻辑分开(如果使用的是MVC / MVC Core)。

When you do a join in LINQ, the compiler creates an anonymous object it refers to as a transparent identifier , which allows it to keep multiple range variables in scope. 当您在LINQ中进行联接时,编译器会创建一个匿名对象,该对象称为透明标识符 ,这使它可以在范围内保留多个范围变量。 When you're building query expressions from parts, which can include a mix of LINQ expressions and extension method calls (as you'll see below), you can do this yourself, although I guess it would be an opaque identifier in that case. 当您从各个部分构建查询表达式时,可以包括LINQ表达式和扩展方法调用的混合(如下所示),您可以自己执行此操作,尽管在这种情况下,我猜它可能是一个不透明的标识符。

I agree with @ErikE that your access checks should have already done before getting to this point, so the code that follows reflects that. 我同意@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);

From here, you can build on it in further, like limiting the results to books that were illustrated by someone represented by a specific agent. 从这里开始,您可以在此基础上进一步发展,例如将结果限制为由特定代理人代表的人所图解的书籍。 This may be more than you need but it demonstrates the flexibility of the technique. 这可能超出了您的需求,但它展示了该技术的灵活性。

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;
}

And then when you're done building your query, you can project properties from your range variables (by way of your opaque identifier) as a BookViewModel . 然后,当您完成查询的构建时,可以将范围变量的属性(通过不透明标识符) BookViewModelBookViewModel

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

And then you can call any of the usual reification methods on result , which will probably be ToList in this case. 然后,您可以对result调用任何常规的验证方法,在这种情况下,可能是ToList

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

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