今天在寫程式時,發現LINQ to SQL在管理物件上的特殊規則,做個筆記。

假設我們有一個Member資料表如下:

有一段程式,其中很取巧的用Member去承接ExecuteQuery的執行結果,前後取得四顆物件,放入一個List中: (在我的實際案例中,是SELECT另一個Table的不同欄位組裝出與Member欄位相同的結果,此處為求簡化,採SELECT '...' AS FieldName的方式模擬)

    protected void Page_Load(object sender, EventArgs e)
    {
        using (PlaygroundDataContext db = 
                            new PlaygroundDataContext())
        {
            List<Member> lst = new List<Member>();
            var m = (from o in db.Members
                     where o.UserId == 1
                     select o).First();
            lst.Add(m);
            //借用Member類別,承接ExecuteQuery的結果
            var n =
                db.ExecuteQuery<Member>(@"
                select 1 as UserId, 'Jeffrey' as UserName, 
                'ZZZ' as Code, getdate() as RegTime UNION
                select 2 as UserId, 'Ninja' as UserName, 
                'XXX' as Code, getdate() as RegTime"
                ).ToArray();
 
            lst.AddRange(n);
 
            var r = (from o in db.Members
                     where o.UserId == 2
                     select o).First();
            lst.Add(r);
 
            //猜看看,結果是什麼?
            foreach (var o in lst)
                Response.Write(
                    string.Format("<li>{0}|{1}|{2}|{3:yyyy/MM/dd HH:mm}" ,
                        o.UserId, o.UserName, o.Code, o.RegTime));
        }
 
        Response.End();
    }

大家猜看看,網頁會傳回什麼結果? X147, Jeffrey, Ninja, Darkthread? 錯!

  • 1|X147|F1|2000/01/01 00:00
  • 1|X147|F1|2000/01/01 00:00
  • 2|Ninja|XXX|2010/08/13 18:58
  • 2|Ninja|XXX|2010/08/13 18:58

由測試結果看來,在一個DataContext中,當Priimary Key相同的物件重複出現時,DataContext只會保留較早出現的那個版本。

若要避免上述物件依Primary Key自動合併及抛棄的狀況,我想到的解法是拆出不同的DataContext:

    protected void Page_Load(object sender, EventArgs e)
    {
        List<Member> lst = new List<Member>();
        using (PlaygroundDataContext db = 
                            new PlaygroundDataContext())
        {
            var m = (from o in db.Members
                     where o.UserId == 1
                     select o).First();
            lst.Add(m);
            var r = (from o in db.Members
                     where o.UserId == 2
                     select o).First();
            lst.Add(r);
        }
        using (PlaygroundDataContext db = 
                            new PlaygroundDataContext())
        {
            //借用Member類別,承接ExecuteQuery的結果
            var n =
                db.ExecuteQuery<Member>(@"
                select 1 as UserId, 'Jeffrey' as UserName, 
                'ZZZ' as Code, getdate() as RegTime UNION
                select 2 as UserId, 'Ninja' as UserName, 
                'XXX' as Code, getdate() as RegTime"
                ).ToArray();
            lst.AddRange(n);
        }
        //猜看看,結果是什麼?
        foreach (var o in lst)
            Response.Write(
                string.Format("<li>{0}|{1}|{2}|{3:yyyy/MM/dd HH:mm}",
                    o.UserId, o.UserName, o.Code, o.RegTime));
        Response.End();
    }

將ExecuteQuery隔離在另一個PlaygroundDataContext後,結果便符合我們的需求囉!

  • 1|X147|F1|2000/01/01 00:00
  • 2|Darkthread|A4|2012/12/21 00:00
  • 1|Jeffrey|ZZZ|2010/08/13 19:05
  • 2|Ninja|XXX|2010/08/13 19:05

Comments

# by xuzicn

當Primary Key相同的物件重複出現時,DataContext只會保留較早出現的那個版本。 这个说法并不成立。我看到这个结论之后非常的惊奇,并且用你的办法试了一下,得到的结果就是预料之中的状况。不知道你的程式码是怎么写的呢?

# by Jeffrey

to xuzicn, 當真? 莫非是某個條件下才會出現此狀況? 事實上我是在實務專案上遇到離奇現象後,才回到這個簡單的LINQ to SQL的Member物件(PK = UserId, in ASP.NET 3.5)上做測試,也驗證了同樣結果(若連兩次巧合導致該文產生錯誤結論,也真算是不幸了 orz)。 不曉得可否提供更多您測試的細節(.NET 3.5 or 4.0?, EF or LINQ to SQL? 如果有程式碼示意的話更好),讓我看看是否忽略了什麼地方?

# by xuzicn

LinQ to SQL, 3.5, sql server 2005 事实上程式码已经被我扔掉了...今天重新试了一下结果正如你所写,很难琢磨清楚。 不过还是要谢谢你,至少目前知道DataContext确实不适合singleton模式

# by xuzicn

DataContext的缓存,其实很烦,只有小型的项目会考虑LinQ但是小型项目上用不上cache 普通的人当然会觉得DataContext和SqlConnection一样是一个非常expensive的,从而使用singleton或者pool,我做到一半之后才发现完全不是这么回事

Post a comment