一,爲什麼表達式的參數需要替換?
在使用領域模型編程時,我們的領域模型經常和數據模型是不一樣的。領域模型最爲貼近業務,數據模型反應的是數據庫表。這二者的不一致經常給我們帶來代碼的複雜化。在模型的轉換上,我們有 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 了。