应该有很多人像我一样,对LINQ的依赖已经到达"LINQ orDie!"(不LINQ,吾宁死)的地步,到了需要存取DB的场合,打死也不想再走ADO.NET + DataTable、DataRow的回头路。不过,在专案引用EntityFramework或其他ORM解决方案( NHibernation、SubSonic...),固然严谨扎实,却也多出额外工作--要依照Schema在专案定义Entity物件、数据库变更时要记得同步更新Entity定义,遇到多TABLE JOIN查询得另外宣告自订类别承接查询结果(我还为此写过潜盾机 )。对于要求严谨精准的中大型系统,这类准备工作属无法避免的代价,但在一些力求快速轻巧的开发情境(例如:转档工具、范例程式、单纯但大量的报表需求...),所有对数据库的存取(Table、View、Stored Procedure)需预先定义,还必须随时保持与数据库一致,引用EF之类的架构便显笨重。

前阵子提到用TVP传WHERE IN参数的做法,在FB专页得到网友Lane Kuo的回馈(在此致谢),得知好物一枚-- Dapper (英文原义是短小精悍,用过即知Dapper不负其名),一个精简小巧的.NET ORM工具,不需在专案里新增DB Table、View或Stored Procuedure定义,只要取得IDbConnection(SqlConnection、OracleConnection、MySqlConnection...都适用),就能立即享受接近EF、LINQ to SQL等ORM架构的便利,最重要的是能用LINQ把玩资料,这才是上流社会的程式写法呀!

不啰嗦,打开NuGet就能找到它:

试用之后,感动到直起鸡皮疙瘩,这就是我一直在寻找的,好吃又不黏牙的DB LINQ解决方案~

以下整理Dapper的特色:

第一点绝对要大推! 之前在公司推广LINQ/EF,最常被挑战的罩门-- "过去不管再复杂的SELECT FROM WHERE,丢给ADO.NET就能拿到DataTable;弄了LINQ/ EF之后,不定义Entity或自订类别就收不到结果。干! 你知道我的系统里有多少个花式SELECT吗?"

我一直觉得这是由传统ADO.NET开发迈向LINQ/EF世界的最大阻碍,也动过脑筋想让ExecuteStoreQuery <T>的T接受dynamic,而Dapper实现了!

 

排版显示纯文字
            using (var cn = new SqlConnection(cnStr))

            {

                //1) 不需要定义POCO对象,直接SELECT结果转成.NET对象集合!(酷)

                // 注意: 结果为IEnumerable<dynamic>,会丧失强型别优势

                //2) 可宣告及传入具名参数

                var list = cn.Query(

              "SELECT * FROM Products WHERE CategoryID=@catg" , new { catg = 2 });

                foreach (var item in list)

                {

                    Console.WriteLine( "{0}.{1}({2})" ,

                        item.ProductID, item.ProductName, item.QuantityPerUnit);

                }

            }

 

另外,Dapper在SQL语法里可使用具名参数(如@catg),不像ExecuteStoreQuery只能用{0}、{1},可读性较佳。

执行结果:

3.Aniseed Syrup(12 - 550 ml bottles)
4.Chef Anton's Cajun Seasoning(48 - 6 oz jars)
5.Chef Anton's Gumbo Mix(36 boxes)
6.Grandma's Boysenberry Spread(12 - 8 oz jars)
8.Northwoods Cranberry Sauce(12 - 12 oz jars)
15.Genen Shouyu(24 - 250 ml bottles)
44.Gula Malacca(20 - 2 kg bags)
61.Sirop d'erable(24 - 500 ml bottles)
63.Vegie-spread(15 - 625 g jars)
65.Louisiana Fiery Hot Pepper Sauce(32 - 8 oz bottles)
66.Louisiana Hot Spiced Okra(24 - 8 oz jars)
77.Original Frankfurter grune Sose(12 boxes)

使用dynamic物件固然方便,但会丧失强型别在编译时期的防错优势,因此Dapper当然也支援将查询结果应对到自订类别。此外,以下范例一并示范Dapper能直接将参数阵列展开成WHERE col IN (@arg1, @arg2, @arg3)的特异功能,相当方便。

排版显示纯文字
        public  class SimpProduct
        {
            public  int ProductID { get; set; }

            public  string ProductName { get; set; }

        } 

        private  static  void Test()
        {
            using (var cn = new SqlConnection(cnStr))
            {
                //1) 将SELECT结果转成指定的型别(属性与栏位名称要一致)

                //2) 直接传数字阵列作为WHERE IN比对参数

                // =>自动转成WHERE col in (@arg1,@arg2,@arg3)

                var list = cn.Query<SimpProduct>(

                    "SELECT * FROM Products WHERE CategoryID IN @catgs" ,

                    new { catgs = new  int [] { 1, 4 } });

                foreach (var item in list)
                {

                    Console.WriteLine( "{0}.{1}" , item.ProductID, item.ProductName);

                }

            }

        }

 

执行结果:

1.Chai
2.Chang
11.Queso Cabrales
12.Queso Manchego La Pastora
24.Guarana Fantastica
31.Gorgonzola Telino
32.Mascarpone Fabioli
33.Geitost
34.Sasquatch Ale
35.Steeleye Stout
38.Cote de Blaye
39.Chartreuse verte
43.Ipoh Coffee
59.Raclette Courdavault
60.Camembert Pierrot
67.Laughing Lumberjack Lager
69.Gudbrandsdalsost
70.Outback Lager
71.Flotemysost
72.Mozzarella di Giovanni
75.Rhonbrau Klosterbier
76.Lakkalikoori

除了查询,Dapper提供.Execute()执行SQL资料更新,最特别的是它可以一次传进多组参数,用不同参数重复执行同一SQL操作,批次作业时格外有用。

排版显示纯文字
            using (var cn = new SqlConnection(cnStr))
            {

                //1) 可执行SQL资料更新指令,支持参数化

                //2) 以动态数组方式提供多组参数,可重复执行同一SQL指令

                cn.Execute( @"INSERT INTO Region VALUES (@id, @desc)" ,

                    new [] {

                        new { id = 5, desc = "Taiwan" },

                        new { id = 6, desc = "Mars" }

                    });

            }

 

Dapper还可以在命令中一次包含多组SELECT,透过QueryMultiple()后再以Read()或Read<T>分别取出查询结果。

排版显示纯文字
        public  class SimpCust
        {

            public  string ContactName { get; set; }

            public  string ContactTitle { get; set; }

        }

        private  static  void Test4()
        {

            using (var cn = new SqlConnection(cnStr))

            {

                //一次执行多组查询,分别取回结果

                var multi = cn.QueryMultiple( @"

SELECT * FROM Customers WHERE CustomerId = @id

SELECT * FROM Orders WHERE CustomerId = @id

", new { id = "ALFKI" });

                var cust = multi.Read<SimpCust>().First();

                Console.WriteLine( "{0} / {1}" , cust.ContactName, cust.ContactTitle);

                var ords = multi.Read(); //取回IEnumerable<dynamic>

                Console.WriteLine( "Orders Count = {0}" , ords.Count());

            }

        }

 

 

执行结果:

Maria Anders / Sales Representative
Orders Count = 6

至于StoredProcedure,一样可以透过Dapper Query()查询及使用Execute()执行,直接取回SELECT结果或使用Output参数都难不倒它。

排版显示纯文字
            using (var cn = new SqlConnection(cnStr))
            {
                //呼叫StoredProcedure查询资料

                var res = 

                    cn.Query( "dbo.CustOrderHist" , new { CustomerID = "ALFKI" },

                             commandType: CommandType.StoredProcedure);

                foreach (var item in res)
                {

                    Console.WriteLine( "{0} = {1}" , item.ProductName, item.Total);

                }

                //取回ReturnValue及Output参数
/*

CREATE PROCEDURE AddOne

    @n INT, @r INT OUPUT

AS 

BEGIN 

SET @r = ​​@n + 1

RETURN 1024

END

*/

                var p = new DynamicParameters();

                p.Add( "@n" , 1);

                p.Add( "@r" , dbType: DbType.Int32, direction: ParameterDirection.Output);

                p.Add( "@rtn" , dbType: DbType.Int32,direction: ParameterDirection.ReturnValue);

                cn.Execute( "dbo.AddOne" , p,commandType: CommandType.StoredProcedure);

                Console.WriteLine( "@r = {0}, return = {1}" ,p.Get< int >( "@r" ), p.Get< int >( "@rtn" ));

            }

        }

 

 

执行结果:

Aniseed Syrup = 6
Chartreuse verte = 21
Escargots de Bourgogne = 40
Flotemysost = 20
Grandma's Boysenberry Spread = 16
Lakkalikoori = 15
Original Frankfurter grune Sose = 2
Raclette Courdavault = 15
Rossle Sauerkraut = 17
Spegesild = 2
Vegie-spread = 20
@r = ​​2, return = 1024

除了调用应用的便利性,Dapper很强调效能,在一些实测中明显胜过EF及其他ORM架构。

【结论】

Dapper的轻巧犀利令人惊艳,赞叹之余颇有相见恨晚之感,不过现在知道也不算迟。未来中大型项目我想仍会维持预先定义Entity、Model、ViewModel,力求严谨分明的原则,但在一些需要巷战抢滩近身肉博的场合,Dapper将会是我的好伙伴!

转载自:http://blog.darkthread.net/post-2014-05-15-dapper.aspx

posted on 2016-05-28 12:00  踏歌&而行  阅读(565)  评论(0编辑  收藏  举报