none
LINQ to SQL で左外部結合時の DefaultIfEmpty() の使いかたについて(NotSupportedExceptionがスローされる) RRS feed

  • 質問

  • mrt136-2 です。いつもお世話になっています。

     

    いつもながら、くだらない勘違いをしているのかも知れませんが、

    ご教授頂ければと思い、質問させて頂きます。。。

     

    LINQ to SQL で左外部結合を行いたく、以下の様なクエリを書いてみました。

    ところが、コンパイルは通るのですが、実行時に DefaultIfEmpty() で

    NotSupportedException がスローされてしまいます。。

     

    using (UserDBDataContext udc = new UserDBDataContext()) {

        Table<MT_USER> mtUser = udc.GetTable<MT_USER>();
        Table<RT_USER_GROUP> rtGroup = udc.GetTable<RT_USER_GROUP>();

     

        var q = from user in mtUser
            join userGroup in rtGroup
               on new { ID = user.USER_ID } equals new { ID = userGroup.USER_ID } into uGroups
            from uGroup in uGroups.DefaultIfEmpty(new RT_USER_GROUP { GROUP_ID = "<グループなし>" })
            select new {
                USER_ID = user.USER_ID,
                GROUP_ID = uGroup.GROUP_ID,
                USER_NAME = user.USER_NAME
            };

        foreach (var test in q) {
            Console.Write("USER_ID = " + test.USER_ID);
            Console.Write(", GROUP_ID = " + test.GROUP_ID);
            Console.Write(", USER_NAME = " + test.USER_NAME + "\n");
        }
    }

     

    Exceptionのメッセージは、

     

    "クエリ演算子 'DefaultIfEmpty' にサポートされないオーバーロードが使用されました。"

     

    と出力されています。

     

    上記の DefaultIfEmpty のパラメータを削除すると、通るのですが、デフォルト値を渡したい場合に困ります。

    また、上記ではありませんが、DefaultIfEmpty のパラメータ対象のクラスにNot Null フィールドがあった場合に、

    フィールドを Null に出来ないと言った例外になってしまいます。。。

    恥ずかしながら、何故NGか?と言う理由を探す手段が見つからないのです。。。

     

    どの様にすれば、解決出来ますでしょうか。手掛かりだけでもご教授頂ければ幸いです。。。

    よろしくお願いします。

     

    ちなみに、環境は、

    Windows XP SP3

    .NET Framerowk 3.5 SP1

    VisualStudio 2008 SP1

    です。
    2008年10月23日 2:58

回答

  •  mrt136-2 さんからの引用
    マジっすか。。。

    と、言うことは、LINQ to SQL では 左外部結合 出来ないと。。。

    それはかなり痛いですね。

    何か DefaultIfEnpty() を使わないで、左外部結合出来る方法がありますでしょうか。。

     

    最初に書かれていた Not Null フィールドの話はとりあえず保留するとして,ドキュメントによれば以下のように書くとあるのですが,この方法もうまくいかないということなのでしょうか?

    Code Snippet

    var q = from user in mtUser
        join userGroup in rtGroup
           on new { ID = user.USER_ID } equals new { ID = userGroup.USER_ID } into uGroups
        from uGroup in uGroups.DefaultIfEmpty()
        select new {
            USER_ID = user.USER_ID,
            GROUP_ID = uGroup != null ? uGroup.GROUP_ID : "<グループなし>",
            USER_NAME = user.USER_NAME
        };

     

    参考) 方法 : 左外部結合を実行する (C# プログラミング ガイド)

     

     mrt136-2 さんからの引用

    ただ、VS2008で 上記のDefaultIfEmpry をマウスでポイントすると、
    IEnumerable<RT_USER_GROUP> IEnumerable<RT_USER_GROUP>.DefaultIfEmpty<RT_USER_GROUP>(RT_USER_GROUP defaultValue) (+1オーバーロード)
    と表示される為、間違いでは無い様な気がするのですが。。。
    ちなみに、+1オーバーロードは、引数無しの DefaultIfEmpty() です。

     

    IntelliSense で表示されることは,その操作がサポートされていることの保証にはならないです.

    コード変換はLINQ プロバイダによって実行時に解釈されますので,ある操作が実際に成功するかどうかは,LINQ プロバイダに依存します.

     

    LINQ to SQL でのコード変換ルールについては,たとえば『標準クエリ演算子の変換 (LINQ to SQL)』というドキュメントが参考になります.DefaultIfEmpty (のデフォルト値を引数にとるバージョン) についても,同ドキュメントの「変換されない演算子」のところで言及されています.

     

    2008年10月24日 5:12

すべての返信

  •  mrt136-2 さんからの引用

            from uGroup in uGroups.DefaultIfEmpty(new RT_USER_GROUP { GROUP_ID = "<グループなし>" })


    ↓こうでしょうか?

            from uGroup in uGroups.DefaultIfEmpty(new RT_USER_GROUP() { GROUP_ID = "<グループなし>" })


    2008年10月23日 3:56
  • GX999様 ご回答ありがとうございます。

     

    from uGroup in uGroups.DefaultIfEmpty(new RT_USER_GROUP() { GROUP_ID = "<グループなし>" })

     

    私自身もこれはためしてはいたんですが、同じエラーでした。。

     

    また、あらかじめ、RT_USER_GROUP のインスタンス変数を用意して渡してみてもダメでした。。

    DefaultIfEmpty に RT_USER_GROUP 型の値を入れたらNGとなっているみたいです。。

    ただ、VS2008で 上記のDefaultIfEmpry をマウスでポイントすると、

     

    IEnumerable<RT_USER_GROUP> IEnumerable<RT_USER_GROUP>.DefaultIfEmpty<RT_USER_GROUP>(RT_USER_GROUP defaultValue) (+1オーバーロード)

     

    と表示される為、間違いでは無い様な気がするのですが。。。

    ちなみに、+1オーバーロードは、引数無しの DefaultIfEmpty() です。

     

    他に何か考えられる事はありますでしょうか。。

     

    2008年10月23日 4:09
  • Code Snippet

    namespace WindowsFormsApplication1
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }

            private void button1_Click( object sender, EventArgs e )
            {
                var employees=new EmployeeList().Item.ToList();
                var groups=new GroupList().Item.ToList();
                var items = from x in employees
                        join y in groups
                            on x.GroupId equals y.Id into z
                        from a in z.DefaultIfEmpty( new Group() { Id = "999", Name = "グループなし" } )
                        select new { Id = a.Id, Name = x.Name };
                // -----
                foreach ( var item in items )
                {
                    Console.WriteLine( item.Id + ":" + item.Name );
                }//foreach
            }

        }

        public class Employee
        {
            public Employee( string groupId, string id, string name )
            {
                this.GroupId = groupId;
                this.Id = id;
                this.Name = name;
            }//Employee

            public string GroupId { get; set; }
            public string Id { get; set; }
            public string Name { get; set; }
        }//class

        public class EmployeeList
        {
            public EmployeeList()
            {
                this.Item = new List<Employee>()
                    {
                        new Employee("1","1","あ"),
                        new Employee("1","2","い"),
                        new Employee("2","3","う"),
                        new Employee("3","5","お")
                    };
            }//EmployeeList

            public List<Employee> Item { get; set; }
        }//class

        public class Group
        {
            public Group()
            { }//Group

            public Group( string id, string name )
            {
                this.Id = id;
                this.Name = name;
            }//Group

            public string Id { get; set; }
            public string Name { get; set; }
        }//class

        public class GroupList
        {
            public GroupList()
            {
                this.Item = new List<Group>()
                    {
                        new Group("1","グループ1"),
                        new Group("2","グループ2")
                    };
            }//GroupList

            public List<Group> Item { get; set; }
        }//class
    }//namespace

     

     

    簡単なサンプルを作成してみましたが、正常に動作するようです。

     

     mrt136-2 さんからの引用

     

    他に何か考えられる事はありますでしょうか。。

     

     

    RT_USER_GROUPのコンストラクタに引数0個を指定できるものがないのではないでしょうか?

    2008年10月23日 5:44
  • GX999様

    わざわざサンプルまで作成して頂き、恐縮です。

     

    確かに、サンプルでは正常に動作する事を確認しました。ありがとうございます。

     

    > RT_USER_GROUPのコンストラクタに引数0個を指定できるものがないのではないでしょうか?

     

    RT_USER_GROUP クラスは、LINQ to SQL クラスで自動生成したクラスです。

    実際に内容を確認しましたが、引数0個のコンストラクタは存在しました。

     

    LINQ to SQL クラスに問題があるのでしょうか。。そこを疑ってかかる必要があるのか。。

     

    何か他にもチェックすべき点があればお教え頂けますでしょうか。。

     

    LINQ。。。便利なんですが、ちょっと凝ったことをしようと思うと、SQLの方が分かりやすかったりする様な。。。

    いまいちつかみ切れていないです。。。

    #左外部結合が 「凝ったこと」 ではないとは思いますが。。。

    2008年10月23日 6:40
  • Linq to SQLを使われる場合の注意点として、思い出したことがあります。
    直接関係ないかも知れませんが、データ取得後、ToListメソッドを呼び出しておいたほうが良いかも知れません。

    Table<MT_USER> mtUser = udc.GetTable<MT_USER>().ToList();
    Table<RT_USER_GROUP> rtGroup = udc.GetTable<RT_USER_GROUP>().ToList();

    2008年10月23日 7:47
  • GX999さま

     

    度々ご回答ありがとうございます。

     

    Code Snippet

     

    Table<MT_USER> mtUser = udc.GetTable<MT_USER>().ToList();
    Table<RT_USER_GROUP> rtGroup = udc.GetTable<RT_USER_GROUP>().ToList();

     

    とりあえず、これはコンパイルエラーになってしまいますね。。(^^ゞ

    また、ToList() を実行してしまうと、遅延実行にならないので、それで良い場合はいいのですが、

    都合が悪い場合もあるので、このあたりは問題ですね。。。

     

    う~ん、何故LINQ to SQL ではDefaultIfEmpty が出来ないんでしょう。。。もしかして、私の環境だけ??

    どなたか、LINQ to SQL で 左外部結合を使った事のある方はおられますでしょうか?

    2008年10月24日 0:35
  • Linq to SQLでUsersとGroupsのふたつのテーブルを追加し、以下のようなcode snippetで正常に動作することを確認しました。

    ToListをメソッドを呼び出さないと、mrt136-2さんのご指摘のExceptionがthrowされますね。

     

    違う部分は、この青文字の辺りだと思いますが...。

    Code Snippet
    using ( var dataContext = new LinqTestDataContext() )
    {
      var users = dataContext.Users.ToList();
      var groups = dataContext.Groups.ToList();

      // -----
      var items = from x in users
            join y in groups
            on x.GroupId equals y.Id into z
            from a in z.DefaultIfEmpty( new Group() { Id = "999", Name = "グループなし" } )
            select new { Id = a.Id, Name = x.Name };
      // -----
      foreach ( var item in items )
      {
        Console.WriteLine( item.Id + ":" + item.Name );
      }//foreach
    }//using

     

     

    Linqの作法や基本的な概念については、赤間さんの本が素晴らしいと思います。

    2008年10月24日 1:28
  • > var users = dataContext.Users.ToList();

    これをやっちゃうと、この時点でUsersテーブル内のすべてのデータとってきませんか?
    SQLとしてはUsersとGroupsのすべてのデータをとってきて、そのうえでLINQ to Objectを利用している状態になっているのではないかと思います。

    つまり、LINQ to SQLを使ってデータを取得している状態ではない、ということに。

     

    2008年10月24日 3:58
  • GX999 様、どっとねっとふぁん様

     

    ご回答ありがとうございます。

     

    どっとねっとふぁん様 のご指摘のとおり、GX999様の方法では、全件取ってきてしまうんですよね。。

    それから、LINQ to Object なんですよね。。。

    やはり大量のデータがある場合は、非現実的かと。。。(^^ゞ

     

    で、

     

    > ちょっと調べきれていないのですが、LINQ to SQLではDefaultIfEmpty に対応していない可能性はありますね。

    マジっすか。。。

    と、言うことは、LINQ to SQL では 左外部結合 出来ないと。。。

    それはかなり痛いですね。

    何か DefaultIfEnpty() を使わないで、左外部結合出来る方法がありますでしょうか。。

     

    もしかして、LINQ to SQL クラスで自動生成したクラスに細工すれば動くようになるのかな。。。

    例えば、デフォルトコンストラクタにあらかじめデフォルト値を埋め込んでおくとか。。。

    ちょっと試してみます。。

    2008年10月24日 4:15
  •  mrt136-2 さんからの引用
    マジっすか。。。

    と、言うことは、LINQ to SQL では 左外部結合 出来ないと。。。

    それはかなり痛いですね。

    何か DefaultIfEnpty() を使わないで、左外部結合出来る方法がありますでしょうか。。

     

    最初に書かれていた Not Null フィールドの話はとりあえず保留するとして,ドキュメントによれば以下のように書くとあるのですが,この方法もうまくいかないということなのでしょうか?

    Code Snippet

    var q = from user in mtUser
        join userGroup in rtGroup
           on new { ID = user.USER_ID } equals new { ID = userGroup.USER_ID } into uGroups
        from uGroup in uGroups.DefaultIfEmpty()
        select new {
            USER_ID = user.USER_ID,
            GROUP_ID = uGroup != null ? uGroup.GROUP_ID : "<グループなし>",
            USER_NAME = user.USER_NAME
        };

     

    参考) 方法 : 左外部結合を実行する (C# プログラミング ガイド)

     

     mrt136-2 さんからの引用

    ただ、VS2008で 上記のDefaultIfEmpry をマウスでポイントすると、
    IEnumerable<RT_USER_GROUP> IEnumerable<RT_USER_GROUP>.DefaultIfEmpty<RT_USER_GROUP>(RT_USER_GROUP defaultValue) (+1オーバーロード)
    と表示される為、間違いでは無い様な気がするのですが。。。
    ちなみに、+1オーバーロードは、引数無しの DefaultIfEmpty() です。

     

    IntelliSense で表示されることは,その操作がサポートされていることの保証にはならないです.

    コード変換はLINQ プロバイダによって実行時に解釈されますので,ある操作が実際に成功するかどうかは,LINQ プロバイダに依存します.

     

    LINQ to SQL でのコード変換ルールについては,たとえば『標準クエリ演算子の変換 (LINQ to SQL)』というドキュメントが参考になります.DefaultIfEmpty (のデフォルト値を引数にとるバージョン) についても,同ドキュメントの「変換されない演算子」のところで言及されています.

     

    2008年10月24日 5:12
  •  

    > ちょっと試してみます。。

     

    RT_USER_GROUPクラスのデフォルトコンストラクタに、デフォルト値をセットする様にして実行してみました。

    #これで、DefaultIfEmpty() が引数無しで自動的にデフォルトコンストラクタが呼び出されるかな?と期待して。。

     

    結果、

    結合対象が存在する場合は、正常に(?) RT_USER_GROUPクラスのデフォルトコンストラクタが呼び出されるのですが、

    結合対象が存在しない場合は、呼び出されず、結果 null 。。。

     

    まぁ、結局変わらないっちゅうことですね。

    う~ん、どうにか出来ないものでしょうか。。。ここだけ SQLCommand を使うのもいただけないですね。。。

     

    ただ、ひとつ分かったことは、テーブル内の全てのフィールドを Nullable型 にしておいて、

    DefaultIfEmpty() にパラメータ無しで指定する事で、エラーなく実行されるようです。

    でも、なんだかなぁ。。。

     

    2008年10月24日 5:26
  • ぉ?

    このドキュメントは見てませんでしたね。この書式だと正常に動作しますね。φ(^^)めもめも
    2008年10月24日 5:32
  • NyaRuRu様

     

    申し訳ありません。かぶってしまいました。。

     

    ご指摘ありがとうございます。

    あっさりうまくいきました。。。お恥ずかしい限りです。。。

     

    ご指摘いただいたドキュメント「方法:左外部結合を実行する(C#プログラミングガイド)」は読んでいたのですが、

    DefaultIfEmpty() のパラメータが空だったので、読み飛ばしてしまっていました。。

    ※デフォルト値を指定する方法が知りたかったので。。。つい。。

     

    「標準クエリ演算子の変換(LINQ to SQL)」 の方は、 DefaultIfEmpty で検索しても出て来なかったです。。。

    きっと後ろの方に出てたんでしょうね。。。根気が足りなかったか。。

     

    で、結果的には、DefaultIfEmpty() はLINQ to SQL では、現状では引数無ししかサポートされず、

    その場合、null が返ってくるので、select 句内で、nullの場合にデフォルト値をセットすると言うことですね。

     

    おかげさまで、解決しました。

    ご回答頂いた皆様、本当にありがとうございました。

     

    ※現状では、と敢えて書いたのは、出来れば、DefaultIfEmpty() でデフォルト値を渡せる様にして頂きたいと

     言う思いからです。。。今の仕様はDefaultIfEmpty ではなく、NullIfEmpry ですね。。

     NullIfEmpty と言う名前なら、これほど悩まなかったかも。。。と悔し紛れに言い訳してみる。。。

    2008年10月24日 6:06