かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

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