Wednesday, February 9, 2011

[C#] LINQ to SQL Select Random Row

在SQL上,可以使用order by NewID()的方式去做到選取隨機資料的功能,但是到了LINQ時似乎是沒那麼方便,保哥有介紹了一篇如何讓 LINQ 支援亂數排序功能,但是會需要在DB上建View跟Function,個人比較懶惰,所以想找找有沒有不動DB就可以做到的方式,後來試了半天還真的有辦法。

Solution 1

首先,來看這個看起來最簡潔的方式。
var Result = DB.Country_IPs.AsEnumerable().OrderBy(p => Guid.NewGuid()).Take(1);

這個效果等同於

select top 1 * from [dbo].[Country_IP] order by NewID()

我沒仔細研究內部是怎麼運作的,不過看起來是將資料都讀進程式後才作亂數排序,所以實際跑起來的速度甚至比select all的速度還慢上一點,在我的電腦上對約有11萬筆資料的table進行操作,約要800~900ms的時間,所以建議要使用這方法最好是對資料量小的資料表進行操作,不然會拖慢不少速度。



Solution 2

另一個方法則是要對Data Model做點手腳,在你的DataContext中,加上下面這段

[System.Data.Linq.Mapping.Function(Name = "NEWID", IsComposable = true)]
        public System.Guid Random()
        { 
            throw new System.NotImplementedException();
        }

如果不知道去那加的話,可以在Solution Explorer裡,把你的dbml點開,然後去裡面的cs加。

LINQ_Random_01

加完之後長的這個樣子

LINQ_Random_02

然後接下來的實用就很簡單了,只要orderby我們剛剛建的function就好。

var Result = (from p in DB.Country_IPs orderby DB.Random() select p).Take(1);

第二個方法的速度比第一個快很多,與使用SqlDataAdapter去跑的速度相比,只慢了大約100ms,個人覺得是相當不錯的選擇,可以參考看看。

LINQ_Random_03


the other test

by the way,我後來試了一下保哥的作法,原本想說從DB做應該會更快點,沒想到速度比第一種方式還慢,蠻出乎我意料的。

LINQ_Random_04

保哥看到別生氣啊XDDDDD


test code
最後附上落落長的測試code,歡迎大家提出指正與改進之處:)

LINQDemoDataContext DB = new LINQDemoDataContext();
            string queryString = "select top 1 * from [dbo].[Country_IP] order by NewID()";
            int start, end;
            DataSet dt = new DataSet();
            DB.Log = Console.Out;
            Console.WriteLine("LINQ to SQL");

            start = System.Environment.TickCount;
            //var Result = DB.Country_IPs.AsEnumerable().OrderBy(p => Guid.NewGuid()).Take(1);
            //var Result = (from p in DB.Country_IPs orderby DB.Random() select p).Take(1);
            var Result = (from p in DB.Country_IPs orderby DB.GetNewId() select p).Take(1);
            end = System.Environment.TickCount;

            foreach (var item in Result)
            {
                Console.WriteLine(item.IPStart + "\t" + item.IPEnd + "\t" + item.NumStart + "\t" + item.NumEnd + "\t" + item.Short + "\t" + item.Long);
            }

            Console.WriteLine("Run Time:" + (end - start));
            Console.WriteLine();

            Console.WriteLine("SqlDataAdapter");
            Console.WriteLine(queryString);

            start = System.Environment.TickCount;
            using (SqlConnection connection = new SqlConnection(connectionString))
            {
                SqlDataAdapter adapter = new SqlDataAdapter();
                adapter.SelectCommand = new SqlCommand(queryString, connection);
                adapter.Fill(dt);
            }
            end = System.Environment.TickCount;

            for (int i = 0; i < dt.Tables.Count; i++)
            {
                Console.WriteLine("Table " + i.ToString() + " [" + dt.Tables[i].TableName + "]");
                for (int j = 0; j < dt.Tables[i].Columns.Count; j++)
                {
                    Console.Write(dt.Tables[i].Columns[j].ColumnName + "\t");
                }
                Console.WriteLine();
                for (int j = 0; j < dt.Tables[i].Rows.Count; j++)
                {
                    for (int k = 0; k < dt.Tables[i].Columns.Count; k++)
                    {
                        Console.Write(dt.Tables[i].Rows[j][k].ToString() + "\t");
                    }
                    Console.WriteLine();
                }
            }

            Console.WriteLine("Run Time:" + (end - start));

            Console.ReadLine();

No comments:

Post a Comment