LayIM專案之基礎資料獲取程式碼優化,Dapper取代ADO.NET

丶Pz發表於2017-01-20

前言

  最近在開發LayIM融雲版,也在進行專案重構,現在在看之前的程式碼,簡直不敢直視。不過不知道以後看現在的程式碼是不是也是糟糕的一批。LayIM有個介面,一般接觸過的開發人員都不會生疏,就是init介面。介面返回的資料大概就是醬紫的:

 1 {
 2     "code": 0,
 3     "msg": "",
 4     "data": {
 5         "mine": {
 6             "username": "紙飛機",
 7             "id": "100000",
 8             "status": "online",
 9             "sign": "在深邃的編碼世界,做一枚輕盈的紙飛機",
10             "avatar": "/images/default.jpg"
11         },
12         "friend": [
13             {
14                 "groupname": "前端碼屌",
15                 "id": 1,
16                 "online": 2,
17                 "list": [
18                     {
19                         "username": "賢心",
20                         "id": "100001",
21                         "avatar": "/images/default.jpg",
22                         "sign": "這些都是測試資料,實際使用請嚴格按照該格式返回"
23                     },
24                     {
25                         "username": "Z_子晴",
26                         "id": "108101",
27                         "avatar": "/images/default.jpg",
28                         "sign": "微電商達人"
29                     }
30                 ]
31             },
32             {
33                 "groupname": "網紅",
34                 "id": 2,
35                 "online": 3,
36                 "list": [
37                     {
38                         "username": "羅玉鳳",
39                         "id": "121286",
40                         "avatar": "/images/default.jpg",
41                         "sign": "在自己實力不濟的時候,不要去相信什麼媒體和記者。他們不是善良的人,有時候候他們的採訪對當事人而言就是陷阱"
42                     },
43                     {
44                         "username": "長澤梓Azusa",
45                         "id": "100001222",
46                         "sign": "我是日本女藝人長澤あずさ",
47                         "avatar": "/images/default.jpg"
48                     }
49                 ]
50             }
51         ],
52         "group": [
53             {
54                 "groupname": "前端群",
55                 "id": "101",
56                 "avatar": "/images/default.jpg"
57             },
58             {
59                 "groupname": "Fly社群官方群",
60                 "id": "102",
61                 "avatar": "/images/default.jpg"
62             }
63         ]
64     }
65 }
LayIM init介面資料

  總之,裡面巢狀了很多關係,比如,我的好友分組和好友的關係,還有其他的一些資料,其實,仔細分析一下,也就會各個擊破了。今天的重點不是這個資料,而是關於獲取這段資料程式碼的重構。

之前的程式碼

  之前用的ADO.NET直接讀取的DataSet,然後進行資料處理,主要麻煩的是,還需要手動寫DataTable轉Model的過程,還要處理關係,比較繁瑣,並且需要知道其中的欄位。(當然,dapper也需要對應)。先看一下之前的程式碼,總之這段程式碼就是很古老的一種形式。

 private  BaseListResult ToBaseListResult(DataSet ds)
        {
            if (ds.Tables.Count > 0)
            {
                if (ds.Tables[0].Rows.Count ==0) {
                    return new BaseListResult();
                }
                //當前使用者的資訊
                var rowMine = ds.Tables[0].Rows[0];
                //使用者組資訊
                var rowFriendDetails = ds.Tables[2].Rows.Cast<DataRow>().Select(x => new GroupUserEntity
                {
                    id = x["uid"].ToInt(),
                    avatar = x["avatar"].ToString(),
                    groupid = x["gid"].ToInt(),
                    remarkname = x["remarkname"].ToString(),
                    username = x["nickname"].ToString(),
                    sign = x["sign"].ToString(),
                    //status之前的欄位是為空的,現在我們把他的線上狀態加上,IsOnline方法接收一個userid引數,從Redis快取中讀取該使用者是否線上並返回
                    status = LayIMCache.Instance.IsOnline(x["uid"].ToInt()) ? "online" : "hide"
                }).OrderByDescending(x => x.status);//這裡要根據使用者是否線上這個欄位排序,保證線上使用者都在好友列表最上邊
                //使用者組資訊,執行分組
                var friend = ds.Tables[1].Rows.Cast<DataRow>().Select(x => new FriendGroupEntity
                {
                    id = x["id"].ToInt(),
                    groupname = x["name"].ToString(),
                    online = 0,
                    list = rowFriendDetails.Where(f => f.groupid == x["id"].ToInt())
                });
                //群組資訊
                var group = ds.Tables[3].Rows.Cast<DataRow>().Select(x => new GroupEntity
                {
                    id = x["id"].ToInt(),
                    groupname = x["name"].ToString(),
                    avatar = x["avatar"].ToString(),
                    groupdesc = x["groupdesc"].ToString()
                });
                //使用者皮膚,第一個是預設正在使用的
                List<string> skin = ds.Tables[4].Rows.Cast<DataRow>().Select(x => x[0].ToString()).ToList();

                BaseListResult result = new BaseListResult
                {
                    mine = new UserEntity
                    {
                        id = rowMine["id"].ToInt(),
                        avatar = rowMine["avatar"].ToString(),
                        sign = rowMine["sign"].ToString(),
                        username = rowMine["nickname"].ToString(),
                        status = "online"
                    },
                    friend = friend,
                    group = group,
                    skin = skin
                };
                return result;
            }
            return null;
        }

  下面,我們改用Dapper試試。重構過程先不談,看一下重構後的程式碼處理:

  public BaseListResult Handle(SqlMapper.GridReader reader)
        {
            var result = new BaseListResult();
            //使用者本人資料
            result.mine = reader.ReadFirst<UserEntity>();
            //處理friend邏輯 start
            var friend = reader.Read<FriendGroupEntity>();
            var groupUsers = reader.Read<GroupUserEntity>();
            friend.ToList().ForEach(f =>
            {
                //每一組的人分配給各個組
                f.list = groupUsers?.Where(x => x.groupid == f.id);
            });
            result.friend = friend;
            //處理friend邏輯 end
            //讀取使用者所在群
            result.group = reader.Read<GroupEntity>();
            return result;
        }

  Dapper相較於ADO.NET比起來,就清爽多了,首先,處理GridReader,然後直接呼叫Read<T>方法,直接將表值轉換為Model,不過Model的值需要對應。然後轉換完之後,在進行一步邏輯處理,就是將相應的使用者跟好友分組的id對應上。最後一個返回,這樣看起來就清爽了許多,也不用處理的很麻煩。下文記錄一下我的開發思路。

程式碼思路

  首先要解決一個問題就是,解耦的問題,Dapper中有一個方法就是 QueryMultiple ,他是返回一個GridReader 物件,然後進行處理。那麼這個GridReader物件又必須在連線開著的時候使用,所以,不能直接返回然後關閉物件,所以,可以採用介面的形式,將處理方法提出去,或者,我剛才恰好想到的就是用Func<SqlMapper.GridReader,TResult> 方式來處理結果。我的程式碼如下:

 public static T QueryMultiple<T>(string sql, object param,CommandType commandType = CommandType.Text, IMultipleHandler<T> handler=null)
        {
            using (var connection = getConnection())
            {
                using (var multi = connection.QueryMultiple(sql, param,commandType: commandType))
                {
                    if (handler == null) {
                        return default(T);
                    }
                    return handler.Handle(multi);
                }
            }
        }

  其中的IMultipleHandler 中就一個方法,Handle方法,引數為GridReader。然後返回 T型別的結果。所以上文中,我的呼叫方式很簡單了,就是傳入相應的處理類就可以了。

 public class UserBaseListHandler : IMultipleHandler<BaseListResult>

  其實我剛才突然想到,或許用Func更方便。不用再定義類繼承介面了。此篇,Over,重構程式碼確實是有意思的事情,後續我會將各種體會做總結,如果能和讀者產生共鳴那是最好不過的了。

相關文章