使用 ExpressionMove 對錶達式參數進行替換

一,爲什麼表達式的參數需要替換?
    在使用領域模型編程時,我們的領域模型經常和數據模型是不一樣的。領域模型最爲貼近業務,數據模型反應的是數據庫表。這二者的不一致經常給我們帶來代碼的複雜化。在模型的轉換上,我們有 AutoMapper 這樣的工具進行轉換。 在查詢時,領域模型的查詢表達式是不能直接給數據模型進行查詢的,我也沒有找到有誰造過這樣的輪子。
    比如,我們現在有領域模型 Model1 代碼如下:

    public class Model1
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public DateTime CreateTime { get; set; }
        public string[] Values { get; set; }//["a", "b", "c"]
        public int[] IntValues { get; set; }//[1, 2, 3]
    }

    有數據模型 Model4 代碼如下:

    public class Model4
    {
        public int id { get; set; }
        public string Name { get; set; }
        public string values { get; set; }//",a,b,c,"
        public string intvalues { get; set; }//",1,2,3,"
    }

    我在領域模型裏寫了一個查詢表達式 Expression<Func<Model1, bool>> x => x.Name == "產品1" 如果想在數據庫裏查詢,這個表達式是不行的。因爲數據的模型是 Model4 ,即使字段名一樣,也查不了。
    通常的解決辦法是不使用表達式,使用一個類來做爲查詢的模型。

    public class QueryModel
    {
        public int Id { get; set; }

        public string Key { get; set; }
    }

    然後在數據模型所在項目(一般是基礎設施層)進行表達式或SQL語句的拼接。

        public List<Model4> Query(QueryModel queryModel)
        {
            Expression<Func<Model4, bool>> expression = null;
            if (queryModel.Id > 0)
                expression = x => x.id == queryModel.Id;
            if (!string.IsNullOrEmpty(queryModel.Key))
                expression = expression.And(x => x.Name.Contains(queryModel.Key));
            return Where(expression);
        }

    然而,這樣的壞處也是顯而易見的,增加了複雜度,而且在添加一個查詢字段、或一個字段的查詢方式時,都需要對查詢模型進行修改。比如上邊的例子中如果想增加個按 [不等於某個Id] 進行查詢的需求,那 QueryModel 要增加字段, 查詢方法裏需要再拼接一段。
    這時如果能有一個工具對錶達式進行轉換的話,那就方便太多了。然後 ExpressionMove 就誕生了。

二,ExpressionMove 使用方法。
    項目地址 : https://github.com/zl33842901/ExpressionMove
    Nuget 關鍵字: xLiAd.ExpressionMove
    具體使用方法可參見項目裏的 UnitTest,在兩個模型完全一樣時(或者說用於查詢的字段完全一樣時),只需要用你的表達式 .BuildMover().MoveTo<Model2>() 就能實現轉換了。如下:

    public class Model2
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public DateTime CreateTime { get; set; }
    }


    Expression<Func<Model1, bool>> expression = x => x.Id == 5 && x.Name == "a" && x.CreateTime < DateTime.Now;
    Expression<Func<Model2, bool>> result = expression.BuildMover().MoveTo<Model2>();

    當模型的一些字段類型一樣,名稱不一樣時,可以使用配置類進行配置,然後在使用時進行轉換。

    public class Model3
    {
        public int id { get; set; }
        public string MyName { get; set; }
        public DateTime CreateTime { get; set; }
    }

    private MoverTypeConfiguration Configuration { get; } = new MoverTypeConfiguration(x =>
    {
        x.CreateMap<Model1, Model3>()
        .FieldMap(x => x.Id, x => x.id)
        .FieldMap(x => x.Name, x => x.MyName);
    });

    Expression<Func<Model1, bool>> expression = x => x.Id == 5 && x.Name == "a" && x.CreateTime < DateTime.Now;
    var provider = Configuration.Build();
    var result = provider.Load(expression).MoveTo<Model3>();

    這種情況也可以使用字段的名稱特性標記來實現。

    當領域模型是數組,數據模型是逗號分隔的字符串時(SQLServer裏常用,PG裏不必了),ExpressionMove 現在只支持 Contains 方法。

    private MoverTypeConfiguration Configuration { get; } = new MoverTypeConfiguration(x =>
    {
        x.CreateMap<Model1, Model4>()
        .FieldMap(x => x.Id, x => x.id)
        .FieldMap(x => x.IntValues, x => x.intvalues)
        .FieldMap(x => x.Values, x => x.values);
    });


    Expression<Func<Model1, bool>> expression = x => x.Id == 5 && x.IntValues.Contains(6) && x.Values.Contains("b");
    var provider = Configuration.Build();
    var result = provider.Load(expression).MoveTo<Model4>();

    當然這種實現並不是很完善,在需要存取數組的場景下,推薦使用 PG(PostgreSql),我的另一個組件 xLiAd.DapperEx 有專門爲PG寫的倉儲,可以支持數組字段的操作。
 
    這樣基本能覆蓋90%以上的場景了,再也不用寫 QueryModel 了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章