かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

DLinqの癖にはまるパターン?

DLinqで頑張る - かずきのBlog@Hatenaの続きです。

DLinqはちょとした癖がある。
それは、LINQのあるステートメントが実行されたときにSQL文を発行するわけじゃないということです。
その実例については、過去に書いたから置いといて…


はまったらどうなるかを書いてみる。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using DLinqSample2.Properties;
using DLinqSample;

namespace DLinqSample2
{
    class Program
    {
        static void Main(string[] args)
        {
            IEnumerable<Employees> emps = null;
            
            using (SqlConnection conn = new SqlConnection(Settings.Default.Edu))
            {
                conn.Open();
                EDU edu = new EDU(conn);
                edu.Log = Console.Out;

                emps = from emp in edu.Employees
                       select emp;
            } // ここでコネクションは閉じられる

            foreach (var emp in emps) // NG
            {
                Console.WriteLine(emp.Name);
            }
        }
    }
}

これを実行すると下のような結果になる。

ハンドルされていない例外: System.InvalidOperationException: ConnectionString プ
ロパティは初期化されていません。
   場所 System.Data.SqlClient.SqlConnection.PermissionDemand()
   場所 System.Data.SqlClient.SqlConnectionFactory.PermissionDemand(DbConnection
 outerConnection)
以下略

これは、

emps = from emp in edu.Employees
       select emp;

この行でSQL文が発行されるんじゃなくて

foreach (var emp in emps) // NG

ここで初めてSQL文が発行されるためです。
この時点では、すでにコネクションが閉じてしまってるので例外になってしまいます。


じゃぁ軽く解決してみます。
empsにアクセスすればSQLが発行されるみたいなので、ToArrayで配列にしちゃいます。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using DLinqSample2.Properties;
using DLinqSample;

namespace DLinqSample2
{
    class Program
    {
        static void Main(string[] args)
        {
            Employees[] emps = null;
            
            using (SqlConnection conn = new SqlConnection(Settings.Default.Edu))
            {
                conn.Open();
                EDU edu = new EDU(conn);
                edu.Log = Console.Out;

                emps = (from emp in edu.Employees
                        select emp).ToArray();
            } // ここでコネクションは閉じられる

            foreach (var emp in emps) // OK
            {
                Console.WriteLine(emp.Name);
            }
        }
    }
}

これを実行すると下のようになります。

SELECT [t0].[ID], [t0].[Name], [t0].[DeptID]
FROM [Employees] AS [t0]
SqlProvider\AttributedMetaModel

ほげほげ
二郎
三郎
四郎
五郎

ばっちり!
ただ、これで安心してるとまたはまっちゃうかもしれません。
例えば、さっきのプログラムをちょっと変えて部署名も出すようにすると…

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using DLinqSample2.Properties;
using DLinqSample;

namespace DLinqSample2
{
    class Program
    {
        static void Main(string[] args)
        {
            Employees[] emps = null;
            
            using (SqlConnection conn = new SqlConnection(Settings.Default.Edu))
            {
                conn.Open();
                EDU edu = new EDU(conn);
                edu.Log = Console.Out;

                emps = (from emp in edu.Employees
                        select emp).ToArray();
            } // ここでコネクションは閉じられる

            foreach (var emp in emps) // OK
            {
                Console.WriteLine(emp.Name);
                Console.WriteLine(emp.Departments.Name); // NG!!
            }
        }
    }
}

部署名表示の部分で例外が飛びます。
理由は、今までとまったく同じです。
ちょっと厄介な感じですね。


これを解決するには…???
こんな手段くらいしか思いつかない…

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using DLinqSample2.Properties;
using DLinqSample;

namespace DLinqSample2
{
    class Program
    {
        static void Main(string[] args)
        {
            Employees[] emps = null;
            
            using (SqlConnection conn = new SqlConnection(Settings.Default.Edu))
            {
                conn.Open();
                EDU edu = new EDU(conn);
                edu.Log = Console.Out;

                emps = (from emp in edu.Employees
                        select emp).ToArray();
                foreach (var emp in emps)
                {
                    // コネクションが生きてるうちにアクセスしておく!
                    Departments dept = emp.Departments; 
                }
            } // ここでコネクションは閉じられる

            foreach (var emp in emps) // OK
            {
                Console.WriteLine(emp.Name);
                Console.WriteLine(emp.Departments.Name);
            }
        }
    }
}

なんかなぁ。


Open Session in Viewみたいなことをしないといけないのかな?
それは嫌だ!!


結局理想論的には、必要なら必要なデータだけとっとけやっていうアプローチになるのかなぁとか思ったり思わなかったり。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using DLinqSample2.Properties;
using DLinqSample;

namespace DLinqSample2
{
    class Program
    {
        static void Main(string[] args)
        {
            EmpInfo[] emps = null;

            using (SqlConnection conn = new SqlConnection(Settings.Default.Edu))
            {
                conn.Open();
                EDU edu = new EDU(conn);
                edu.Log = Console.Out;

                // 必要なもんは必要なだけとっちゃう
                emps = (from emp in edu.Employees
                        select new EmpInfo
                               {
                                   Name = emp.Name,
                                   DeptName = emp.Departments.Name
                               }).ToArray();
            } // ここでコネクションは閉じられる

            foreach (var emp in emps)
            {
                Console.WriteLine(emp.Name);
                Console.WriteLine(emp.DeptName);
            }
        }
    }

    class EmpInfo
    {
        public string Name { get; set; }
        public string DeptName { get; set; }
    }
}