web-dev-qa-db-ja.com

LINQでサブクエリを実行する方法は?

以下は、LINQに変換しようとしているクエリの例です。

SELECT *
FROM Users
WHERE Users.lastname LIKE '%fra%'
    AND Users.Id IN (
         SELECT UserId 
         FROM CompanyRolesToUsers 
         WHERE CompanyRoleId in (2,3,4) )

CompanyRolesToUsersUsersの間にはFK関係がありますが、多対多の関係であり、CompanyRolesToUsersはジャンクションテーブルです。

すでにサイトの大部分が構築されており、PredicateExtensionsクラスを使用してExpressionsを構築することにより、ほとんどのフィルタリングが既に機能しています。

簡単なフィルターのコードは次のようになります。

 if (!string.IsNullOrEmpty(TextBoxLastName.Text))
 {
     predicateAnd = predicateAnd.And(c => c.LastName.Contains(
                                     TextBoxLastName.Text.Trim()));
 }

e.Result = context.Users.Where(predicateAnd);

別のテーブルの副選択に述語を追加しようとしています。 (CompanyRolesToUsers

私が追加できるようにしたいのは、これを行うものです:

int[] selectedRoles = GetSelectedRoles();
if( selectedRoles.Length > 0 )
{
    //somehow only select the userid from here ???:
    var subquery = from u in CompanyRolesToUsers
                   where u.RoleID in selectedRoles
                   select u.UserId;

    //somehow transform this into an Expression ???:
    var subExpression = Expression.Invoke(subquery);

    //and add it on to the existing expressions ???:
    predicateAnd = predicateAnd.And(subExpression);
}

これを行う方法はありますか?ストアドプロシージャを簡単に記述できるのでイライラしますが、このLINQのことは初めてで、期限があります。一致する例を見つけることができませんでしたが、どこかにあると確信しています。

65
marcel_g

これがあなたのためのサブクエリです!

List<int> IdsToFind = new List<int>() {2, 3, 4};

db.Users
.Where(u => SqlMethods.Like(u.LastName, "%fra%"))
.Where(u =>
    db.CompanyRolesToUsers
    .Where(crtu => IdsToFind.Contains(crtu.CompanyRoleId))
    .Select(crtu =>  crtu.UserId)
    .Contains(u.Id)
)

質問のこの部分に関して:

predicateAnd = predicateAnd.And(c => c.LastName.Contains(
                                TextBoxLastName.Text.Trim()));

クエリを作成する前に、テキストボックスから文字列を抽出することを強くお勧めします。

string searchString = TextBoxLastName.Text.Trim();
predicateAnd = predicateAnd.And(c => c.LastName.Contains( searchString));

データベースに送信されるものを適切に制御する必要があります。元のコードでは、トリミングのためにトリミングされていない文字列がデータベースに送信されるという可能性があります。これは、データベースが実行するのに適した作業ではありません。

77
Amy B

このステートメントにはサブクエリは必要ありません。

select u.* 
from Users u, CompanyRolesToUsers c
where u.Id = c.UserId        --join just specified here, perfectly fine
and u.lastname like '%fra%'
and c.CompanyRoleId in (2,3,4)

または

select u.* 
from Users u inner join CompanyRolesToUsers c
             on u.Id = c.UserId    --explicit "join" statement, no diff from above, just preference
where u.lastname like '%fra%'
  and c.CompanyRoleId in (2,3,4)

とはいえ、LINQでは次のようになります。

from u in Users
from c in CompanyRolesToUsers 
where u.Id == c.UserId &&
      u.LastName.Contains("fra") &&
      selectedRoles.Contains(c.CompanyRoleId)
select u

または

from u in Users
join c in CompanyRolesToUsers 
       on u.Id equals c.UserId
where u.LastName.Contains("fra") &&
      selectedRoles.Contains(c.CompanyRoleId)
select u

繰り返しますが、どちらもこれを表現するための立派な方法です。私はどちらの場合も明示的な「結合」構文を好みますが、それは...

22
TheSoftwareJedi

これが私がLINQでサブクエリを行っている方法です。これはあなたが望むものを手に入れるべきだと思います。明示的なCompanyRoleId == 2 ...を、必要な異なるロールの別のサブクエリに置き換えるか、同様に結合できます。

from u in Users
join c in (
    from crt in CompanyRolesToUsers
    where CompanyRoleId == 2
    || CompanyRoleId == 3
    || CompanyRoleId == 4) on u.UserId equals c.UserId
where u.lastname.Contains("fra")
select u;
5
Noah

さて、正しいレコードを取得する基本的な結合クエリを次に示します。

   int[] selectedRolesArr = GetSelectedRoles();
    if( selectedRolesArr != null && selectedRolesArr.Length > 0 ) 
    {

    //this join version requires the use of distinct to prevent muliple records
        //being returned for users with more than one company role.
    IQueryable retVal = (from u in context.Users
                        join c in context.CompanyRolesToUsers
                          on u.Id equals c.UserId
                        where u.LastName.Contains( "fra" ) &&
                            selectedRolesArr.Contains( c.CompanyRoleId )
                        select  u).Distinct();
}

しかし、これは、すでに用意されているアルゴリズムと最も簡単に統合できるコードです。

int[] selectedRolesArr = GetSelectedRoles(); 
if ( useAnd ) 
       { 
          predicateAnd = predicateAnd.And( u => (from c in context.CompanyRolesToUsers 
                       where selectedRolesArr.Contains(c.CompanyRoleId) 
                       select c.UserId).Contains(u.Id)); 
        } 
        else 
        { 
           predicateOr = predicateOr.Or( u => (from c in context.CompanyRolesToUsers 
                          where selectedRolesArr.Contains(c.CompanyRoleId) 
                         select c.UserId).Contains(u.Id) ); 
        } 

LINQtoSQLフォーラム のポスターのおかげです。

2
marcel_g

あなたの場合、このようなことをすることができます 構文は少しずれているかもしれません)。こちらもご覧ください---(link

subQuery = (from crtu in CompanyRolesToUsers where crtu.RoleId==2 || crtu.RoleId==3 select crtu.UserId).ToArrayList();

finalQuery = from u in Users where u.LastName.Contains('fra')  && subQuery.Contains(u.Id) select u;
2
Perpetualcoder

正しいレコードを返すSQLのバージョンは次のとおりです。

select distinct u.* 
from Users u, CompanyRolesToUsers c
where u.Id = c.UserId        --join just specified here, perfectly fine
and u.firstname like '%amy%'
and c.CompanyRoleId in (2,3,4)

また、(2,3,4)はWebアプリのユーザーがチェックボックスリストから選択したリストであり、簡単にするためにハードコーディングしただけであることを忘れていました。本当にそれはCompanyRoleId値の配列なので、(1)または(2,5)または(1,2,3,4,6,7,99)の可能性があります。

また、より明確に指定する必要があるもう1つのことは、Webアプリケーションユーザーが入力したフォームフィールドに応じて、PredicateExtensionsを使用してクエリのWhereに述語句を動的に追加することです。作業中のクエリを、式の動的リストに添付できるLINQ式に変換します。

サンプルLINQクエリの一部を試して、それらをコードに統合できるかどうかを確認し、結果を投稿します。ありがとう!

マルセル

1
marcel_g