簡體   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