在架構設計中,利用領域驅動開發時,涉及到do(領域對象)和dto(數據傳輸對象)的相互裝換匹配,這段代碼簡單但是重復頻率太多,寫得我很冒火(我有個職責是wcf SOA包裝),我是個不喜歡重復勞動的懶人,我在網上搜索等到很多實體匹配的框架EmitMapper,AutoMapper等,但是他們都不能滿足dto和do的對象的按規則匹配包裝。最后我只得花了半個小時寫了一個簡單的代碼生成器,完成了我的任務。但是事后總覺得不爽,於是有了寫下這個AgileMapper框架來適應領域開發中的po,do,dto,vo着一些列對象的相互包裝,建立一個按規則包裝的Mapper框架。項目已經完成上傳於CodePlex http://agilemapper.codeplex.com/ ,目前剛成型,希望大家能夠幫助測試,提出bug,或者修復。我不是很清楚開源協議,選擇了一個 協議。大家可以隨便使用和修改應用來滿足各自的需求,但是如果有些bug修復或者好的通用的修改希望大家能夠,提交供我和其他人學習共同進步,但是這不是必須的,你也可以選擇保留。
AgileMapper架構設計類圖:
在AgileMapper中支持多種MappingConfigurator(匹配管理器)都集成至MappingConfiguratorBase(MappingConfiguratorBase中擁有唯一的對象之間對於相等的默認表達式守信,針對於dto轉化為do對象級聯刪除情況),內置了AttributeMappingConfigurator,XMLMappingConfigurator,DataRowMappingConfigurator三種匹配管理器。支持xml書寫,attribute標記規則。由這些管理器根據具體標記標記方式產生一組IMappingRule(匹配規則),內置了5中匹配規則(簡單,集合,表達式,對象,datarow)。
在AgileMapper為我們提供了MappingConfiguratorBase的擴展,IMappingRule的擴展,已經多余Attribute標注的擴展CustomerMappingAttribute,已經xml的配置擴展。
下面我們來使用AgileMapper提供的內置Mapper。
測試預備:
Domain Object:

{
public int ID
{
get;
set;
}
public string Name
{ get; set; }
public Sex Sex
{ get; set; }
public Address Address
{ get; set; }
public ContactWay ContactWay
{ get; set; }
public List< string> CourseIds
{ get; set; }
public List<KeyValuePair> Propertys
{ get; set; }
}
public class KeyValuePair
{
public string Key
{ get; set; }
public string Value
{ get; set; }
}
public enum Sex
{
男, 女
}
public class ContactWay
{
public string Phone
{
get;
set;
}
public string Email
{
get;
set;
}
public string QQ
{
get;
set;
}
}
public class Address
{
public string Country
{
get;
set;
}
public string Province
{ get; set; }
public string Street
{ get; set; }
public string Particular
{ get; set; }
}
Dto:

public class StudenDto
{
public int ID
{
get;
set;
}
public string Name
{ get; set; }
public Sex Sex
{ get; set; }
[Mapping( " Address.Country ")]
public string Country
{
get;
set;
}
[Mapping( " Address.Province ")]
public string Province
{ get; set; }
// [Mapping("Address.Street")]
[IgnoreMapping]
public string Street
{ get; set; }
[ExpressionMapping( " Address.Country +\" 國籍 \"+Address.Province +\" 省 \" ")]
public string Particular
{ get; set; }
[ObjectMappingAttribute]
public ContactWayDto ContactWay
{ get; set; }
[CollectionMapping()]
public List< string> CourseIds
{ get; set; }
[CollectionMapping(EqualExpression= " from.Key==to.Key ",IsDeleteNotInFromItem= true)]
public List<KeyValuePair> Propertys
{ get; set; }
[ExpressionMapping( " Propertys[0].Key ")]
public string FirstPropertyKey
{
get;
set;
}
public class ContactWayDto
{
public string Phone
{
get;
set;
}
public string Email
{
get;
set;
}
public string QQ
{
get;
set;
}
}
public class AddressDto
{
public string Country
{
get;
set;
}
public string Province
{ get; set; }
public string Street
{ get; set; }
public string Particular
{ get; set; }
}
一:Attribute標注:

[TestMethod]
public void AttributeConfig_SimpleMapping_Gen()
{
StudenDo stu = new StudenDo()
{
ID = 1,
Name = " test1 ",
Sex = Sex.女,
Address = new Address()
{
Country = " 中國 ",
Province = " 四川 ",
Street = " 高新區 "
},
CourseIds = new List< string>() { " 1 ", " 2 ", " 3 " },
Propertys = new List<KeyValuePair>() { new KeyValuePair() { Key = " 1 ", Value = " 1 " } },
ContactWay = new ContactWay()
{
Phone = " 1111111111111111 ",
Email = " xxxx@12f ",
QQ = " 7889789999889 "
}
};
var mapper = ObjectMapperManager.Default.GetMapper<StudenDto, StudenDo>();
var dt1 = DateTime.Now;
var stuDto = mapper.Warp(stu);
var sp = DateTime.Now - dt1;
dt1 = DateTime.Now;
stuDto = mapper.Warp(stu);
var sp1 = DateTime.Now - dt1;
Assert.AreEqual(stuDto.ID, stu.ID);
Assert.AreEqual(stuDto.Name, stu.Name);
Assert.AreEqual(stuDto.Sex, stu.Sex);
Assert.AreEqual(stuDto.Country, stu.Address.Country);
Assert.AreEqual(stuDto.Province, stu.Address.Province);
Assert.AreEqual(stuDto.Street, null); // Ignore
// object
// Assert.AreEqual(stuDto.ContactWay,null);
Assert.AreEqual(stuDto.ContactWay.QQ, stu.ContactWay.QQ);
Assert.AreEqual(stuDto.ContactWay.Email, stu.ContactWay.Email);
// expression
Assert.AreEqual(stuDto.Particular, string.Format( " {0} 國籍 {1} 省 ", stu.Address.Country, stu.Address.Province));
Assert.AreEqual(stuDto.FirstPropertyKey, stu.Propertys[ 0].Key);
// collection
Assert.AreEqual(stuDto.CourseIds[ 0], stu.CourseIds[ 0]);
Assert.AreEqual(stuDto.CourseIds.Count, stu.CourseIds.Count);
Assert.AreEqual(stuDto.Propertys[ 0].Key, stu.Propertys[ 0].Key);
Assert.AreEqual(stuDto.Propertys[ 0].Value, stu.Propertys[ 0].Value);
Assert.AreEqual(stuDto.Propertys.Count, stu.Propertys.Count);
// Warp 2
var stuDo = new StudenDo();
mapper.Warp(stuDto, stuDo);
Assert.AreEqual(stuDo.ID, stuDto.ID);
Assert.AreEqual(stuDo.Name, stuDto.Name);
Assert.AreEqual(stuDo.Sex, stuDto.Sex);
Assert.AreEqual(stuDo.Address.Country, stuDto.Country);
Assert.AreEqual(stuDo.Address.Province, stuDto.Province);
// Assert.AreEqual(stuDo.Address.Street, null); // Ignore
// object
Assert.AreEqual(stuDo.ContactWay.QQ, stuDto.ContactWay.QQ);
Assert.AreEqual(stuDo.ContactWay.Email, stuDto.ContactWay.Email);
// collection
Assert.AreEqual(stuDo.CourseIds.Count, stuDto.CourseIds.Count);
Assert.AreEqual(stuDo.CourseIds[ 0], stuDto.CourseIds[ 0]);
Assert.AreEqual(stuDo.Propertys.Count, stuDto.Propertys.Count);
Assert.AreEqual(stuDo.Propertys[ 0].Key, stuDto.Propertys[ 0].Key);
Assert.AreEqual(stuDo.Propertys[ 0].Value, stuDto.Propertys[ 0].Value);
}
[TestMethod]
public void AttributeConfig_SimpleMapping()
{
StudenDo stu = new StudenDo()
{
ID = 1,
Name = " test1 ",
Sex = Sex.女,
Address = new Address()
{
Country = " 中國 ",
Province = " 四川 ",
Street = " 高新區 "
},
CourseIds = new List< string>() { " 1 ", " 2 ", " 3 " },
Propertys = new List<KeyValuePair>() { new KeyValuePair() { Key = " 1 ", Value = " 1 " } },
ContactWay = new ContactWay()
{
Phone = " 1111111111111111 ",
Email = " xxxx@12f ",
QQ = " 7889789999889 "
}
};
var mapper = ObjectMapperManager.Default.GetMapper();
var stuDto = mapper.Warp( typeof(StudenDto), stu) as StudenDto;
Assert.AreEqual(stuDto.ID, stu.ID);
Assert.AreEqual(stuDto.Name, stu.Name);
Assert.AreEqual(stuDto.Sex, stu.Sex);
Assert.AreEqual(stuDto.Country, stu.Address.Country);
Assert.AreEqual(stuDto.Province, stu.Address.Province);
Assert.AreEqual(stuDto.Street, null); // Ignore
// object
Assert.AreEqual(stuDto.ContactWay.QQ, stu.ContactWay.QQ);
Assert.AreEqual(stuDto.ContactWay.Email, stu.ContactWay.Email);
// expression
Assert.AreEqual(stuDto.Particular, string.Format( " {0} 國籍 {1} 省 ", stu.Address.Country, stu.Address.Province));
// collection
Assert.AreEqual(stuDto.CourseIds[ 0], stu.CourseIds[ 0]);
Assert.AreEqual(stuDto.CourseIds.Count, stu.CourseIds.Count);
}
二:xml配置標注規則:

< AgileMapper >
< Extensions >
< Extension Name ="SimpleMappingRule" Type ="Green.AgileMapper.SimpleMappingRule,Green.AgileMapper" ></ Extension >
< Extension Name ="ObjectMappingRule" Type ="Green.AgileMapper.ObjectMappingRule,Green.AgileMapper" ></ Extension >
< Extension Name ="CollectionMappingRule" Type ="Green.AgileMapper.CollectionMappingRule,Green.AgileMapper" ></ Extension >
< Extension Name ="ExpressionMappingRule" Type ="Green.AgileMapper.ExpressionMappingRule,Green.AgileMapper" ></ Extension >
</ Extensions >
< Mappings >
< Mapping FromType ="AgileMapper.Test.StudenDto,AgileMapper.Test" >
< SimpleMappingRule FromPoperty ="Country" ToPoperty ="Address.Country" ></ SimpleMappingRule >
< SimpleMappingRule FromPoperty ="Province" ToPoperty ="Address.Province" ></ SimpleMappingRule >
< ObjectMappingRule FromPoperty ="ContactWay" ToPoperty ="ContactWay" ></ ObjectMappingRule >
< CollectionMappingRule FromPoperty ="CourseIds" ToPoperty ="CourseIds" ></ CollectionMappingRule >
< CollectionMappingRule FromPoperty ="Propertys" ToPoperty ="Propertys" EqualExpression ="from.Key==to.Key" IsDeleteNotInFromItem ="true" ></ CollectionMappingRule >
< ExpressionMappingRule FromPoperty ="Particular" Expression ="Address.Country +Address.Province" ></ ExpressionMappingRule >
< ExpressionMappingRule FromPoperty ="FirstPropertyKey" Expression ="Propertys[0].Key" ></ ExpressionMappingRule >
< Ignores >
< Ignore Name ="Street" ></ Ignore >
</ Ignores >
</ Mapping >
</ Mappings >
</ AgileMapper >
測試代碼:

[TestMethod]
public void XMlConfig_SimpleMapping_Gen()
{
StudenDo stu = new StudenDo()
{
ID = 1,
Name = " test1 ",
Sex = Sex.女,
Address = new Address()
{
Country = " 中國 ",
Province = " 四川 ",
Street = " 高新區 "
},
CourseIds = new List< string>() { " 1 ", " 2 ", " 3 " },
Propertys = new List<KeyValuePair>() { new KeyValuePair() { Key = " 1 ", Value = " 1 " } },
ContactWay = new ContactWay()
{
Phone = " 1111111111111111 ",
Email = " xxxx@12f ",
QQ = " 7889789999889 "
}
};
var mapper = ObjectMapperManager.Default.GetMapper<StudenDto, StudenDo>( new XMLMappingConfigurator( @" E:\Project\OpenSource\AgileMapper\AgileMappper.Test\XMLConfigurator\AgileMapper.xml "));
var stuDto = mapper.Warp(stu);
Assert.AreEqual(stuDto.ID, stu.ID);
Assert.AreEqual(stuDto.Name, stu.Name);
Assert.AreEqual(stuDto.Sex, stu.Sex);
Assert.AreEqual(stuDto.Country, stu.Address.Country);
Assert.AreEqual(stuDto.Province, stu.Address.Province);
Assert.AreEqual(stuDto.Street, null); // Ignore
// object
// Assert.AreEqual(stuDto.ContactWay,null);
Assert.AreEqual(stuDto.ContactWay.QQ, stu.ContactWay.QQ);
Assert.AreEqual(stuDto.ContactWay.Email, stu.ContactWay.Email);
// expression
Assert.AreEqual(stuDto.Particular.Replace( " ", ""), string.Format( " {0}{1} ", stu.Address.Country, stu.Address.Province));
Assert.AreEqual(stuDto.FirstPropertyKey, stu.Propertys[ 0].Key);
// collection
Assert.AreEqual(stuDto.CourseIds[ 0], stu.CourseIds[ 0]);
Assert.AreEqual(stuDto.CourseIds.Count, stu.CourseIds.Count);
Assert.AreEqual(stuDto.Propertys[ 0].Key, stu.Propertys[ 0].Key);
Assert.AreEqual(stuDto.Propertys[ 0].Value, stu.Propertys[ 0].Value);
Assert.AreEqual(stuDto.Propertys.Count, stu.Propertys.Count);
// Warp 2
var stuDo = new StudenDo();
mapper.Warp(stuDto, stuDo);
Assert.AreEqual(stuDo.ID, stuDto.ID);
Assert.AreEqual(stuDo.Name, stuDto.Name);
Assert.AreEqual(stuDo.Sex, stuDto.Sex);
Assert.AreEqual(stuDo.Address.Country, stuDto.Country);
Assert.AreEqual(stuDo.Address.Province, stuDto.Province);
// Assert.AreEqual(stuDo.Address.Street, null); // Ignore
// object
Assert.AreEqual(stuDo.ContactWay.QQ, stuDto.ContactWay.QQ);
Assert.AreEqual(stuDo.ContactWay.Email, stuDto.ContactWay.Email);
// collection
Assert.AreEqual(stuDo.CourseIds.Count, stuDto.CourseIds.Count);
Assert.AreEqual(stuDo.CourseIds[ 0], stuDto.CourseIds[ 0]);
Assert.AreEqual(stuDo.Propertys.Count, stuDto.Propertys.Count);
Assert.AreEqual(stuDo.Propertys[ 0].Key, stuDto.Propertys[ 0].Key);
Assert.AreEqual(stuDo.Propertys[ 0].Value, stuDto.Propertys[ 0].Value);
}
三:DataRow的測試:
測試預備StudentModelForDataRow:

{
public int ID
{ get; set; }
public string Name
{ get; set; }
}
測試代碼:

public void DataRowConfig_SameTable_DataRowCloneMapping()
{
DataTable dt = new DataTable();
dt.Columns.AddRange( new DataColumn[] {
new DataColumn( " ID ", typeof( int)),
new DataColumn( " Name ", typeof( string))
});
var row = dt.NewRow();
row[ 0] = 1;
row[ 1] = " Green ";
dt.Rows.Add(row);
var rowClone = dt.NewRow();
var mapper = ObjectMapperManager.Default.GetMapper( new DataRowMappingConfigurator());
mapper.Warp( typeof(DataRow), row, rowClone);
Assert.AreEqual(row[ 0], rowClone[ 0]);
Assert.AreEqual(row[ 1], rowClone[ 1]);
}
[TestMethod]
public void DataRowConfig_UnSameTable_MutipleRule_DataRowCloneMapping()
{
DataTable dt = new DataTable();
dt.Columns.AddRange( new DataColumn[] {
new DataColumn( " ID ", typeof( int)),
new DataColumn( " Name ", typeof( string))
});
DataTable dt2 = new DataTable();
dt2.Columns.AddRange( new DataColumn[] {
new DataColumn( " ID ", typeof( int)),
new DataColumn( " Name ", typeof( string)),
new DataColumn( " Sex ", typeof( string))
});
var row = dt2.NewRow();
row[ 0] = 1;
row[ 1] = " Green ";
row[ 2] = " Nan ";
dt2.Rows.Add(row);
var rowClone = dt.NewRow();
var mapper = ObjectMapperManager.Default.GetMapper( new DataRowMappingConfigurator());
mapper.Warp(row, rowClone);
Assert.AreEqual(row[ 0], rowClone[ 0]);
Assert.AreEqual(row[ 1], rowClone[ 1]);
}
[TestMethod]
public void DataRowConfig_UnSameTable_Not_MutipleRule_DataRowCloneMapping()
{
DataTable dt = new DataTable();
dt.Columns.AddRange( new DataColumn[] {
new DataColumn( " ID ", typeof( int)),
new DataColumn( " Name ", typeof( string))
});
DataTable dt2 = new DataTable();
dt2.Columns.AddRange( new DataColumn[] {
new DataColumn( " ID ", typeof( int)),
new DataColumn( " Name ", typeof( string)),
new DataColumn( " Sex ", typeof( string))
});
var row = dt.NewRow();
row[ 0] = 1;
row[ 1] = " Green ";
dt.Rows.Add(row);
var rowClone = dt2.NewRow();
var mapper = ObjectMapperManager.Default.GetMapper( new DataRowMappingConfigurator());
mapper.Warp(row, rowClone);
Assert.AreEqual(row[ 0], rowClone[ 0]);
Assert.AreEqual(row[ 1], rowClone[ 1]);
}
[TestMethod]
public void DataRowConfig_To_Object_CloneMapping()
{
DataTable dt = new DataTable();
dt.Columns.AddRange( new DataColumn[] {
new DataColumn( " ID ", typeof( int)),
new DataColumn( " Name ", typeof( string))
});
var row = dt.NewRow();
row[ 0] = 1;
row[ 1] = " Green ";
dt.Rows.Add(row);
StudentModelForDataRow model = new StudentModelForDataRow();
var mapper = ObjectMapperManager.Default.GetMapper( new DataRowMappingConfigurator());
mapper.Warp(row, model);
Assert.AreEqual(model.ID, row[ 0]);
Assert.AreEqual(model.Name, row[ 1]);
}
DataRow匹配針對相同的表結構和不同表結構,以及實體類和DataRow之間的轉化。
單元測試結果:
對於xml配置的架構還沒做,以及基於T4模板的按照規則代碼生成模板還在進一步開發中,敬請期待。
今天就寫在這里了,歡迎大家的指正和修改,希望你的修改如果更好能通知我,給我好的建議和探討,謝謝。