很多年没有在这儿写技术博客了。有些生疏,朋友们见谅。
引子
估计有不少写.Net的朋友也是和我一样,平时主要以WebForm开发位置,遇到WinForm开发的时候,总会遇到一些本来在WebForm下面很熟练的问题,在WinForm下面却麻烦重重了。
我也刚开始写WinForm,时间不长。希望在这个过程中遇到的一些问题和解决办法,可以一一拿出来和朋友们分享。
场景
有如下一个及其简单的场景:
1、主界面(Main)中有一项需要在另外一个Item编辑窗口(ItemEdit)中编辑;
2、用户可以在窗口中对Item中的一些属性就行修改,保存后回到主窗口,这时主窗口在不刷新数据库的情况下,可以显示用户最新的编辑内容;
3、用户也可能编辑部分内容后放弃了继续编辑,此时主窗口的内容应该保持不变。
非AutoMapper的一些解决办法
为实现上述需求,通常,编辑界面的构造函数中,添加一个参数用于传递待编辑对象即可。如
public ItemEdit(Entity entity){}
上述需求如果在WebForm中很容易实现。因为WebForm的界面之间在不使用浏览器脚本的情况下,是相互独立的,没有通信。但对于WinForm,在实现场景需求3的时候,由于Entity是引用类型,如Entity被绑定到控件,用户一旦编辑后,实体将被同步修改,即便用户取消,在主界面中实体也已经被修改了。
一种相对ugly的解决办法是,在ItemEdit中,对象不绑定到控件,而在加载时手动为控件赋值,同时,在用户确定保存的时候,再从控件中依次取值,并依次赋值到Entity中。如
![]()
Load时为控件赋值
private TextBox tbxName = new TextBox();
private void ItemEdit_Load( object sender, EventArgs e)
{
tbxName.Text = _entity.Name;
}
private Button btvSave = new Button();
private void btnSave_Click( object sender, EventArgs e)
{
_entity.Name = tbxName.Text;
Save(_entity);
}
在使用WinForm的时候,更常用的方式是采用控件绑定的方式。如:
![]()
Save时从控件取值
private TextBox tbxName = new TextBox();
private Button btvSave = new Button();
private void ItemEdit_Load( object sender, EventArgs e)
{
tbxName.DataBings.Add( " Text " ,_entity, " Name " );
}
private void btnSave_Click( object sender, EventArgs e)
{
Save(_entity);
}
很明显,如果采用第二种方法,_entity的值将是和控件的中绑定的属性同步的。如果要避免这种情况,我们在页面编辑时,需要使用另外一个对象,以避免编辑时的实体与传入的entity存在引用关系。你也可以使用下面的这种方式,——如果在Entity有几十个属性的时候,你也不觉得麻烦的话,——下面的内容你也不需要在遇到了。
Entity _entityForEdit = new Entity();
_entityForEdit.Name = _entity.Name;
AutoMapper的解决办法
在这里,我们采用AutoMapper来觉得这种问题。为了方便,我采用一个简单的测试用例的方式来说明。
![ContractedBlock.gif](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
using NUnit.Framework;
namespace RQQuery.Client.Test
{
[TestFixture]
public class AutoMapperTest
{
[Test]
public void MainTest()
{
const string name = " Entity From Main. " ;
var entityForSave = new Entity {Name = name};
var codeForSave = entityForSave.GetHashCode();
var itemEditSaved = new ItemEdit(entityForSave);
itemEditSaved.Edit();
itemEditSaved.Save();
// 期望Name值被修改。
Assert.AreEqual( " Entity is changed. " , entityForSave.Name, " 修改不成功 " );
// 期望引用关系不被破坏。
Assert.AreEqual(codeForSave, entityForSave.GetHashCode(), " 引用关系丢失 " );
var entityForCancel = new Entity { Name = name };
var codeForCancel = entityForSave.GetHashCode();
var itemEditCanceled = new ItemEdit(entityForCancel);
itemEditCanceled.Edit();
itemEditCanceled.Cancel();
// 期望Name值没有被修改。
Assert.AreEqual(name, entityForCancel.Name, " 已被修改 " );
// 期望引用关系不被破坏。
Assert.AreEqual(codeForCancel, entityForSave.GetHashCode(), " 引用关系丢失 " );
}
}
public class Entity
{
public string Name { get ; set ; }
}
public class ItemEdit
{
private readonly Entity _entity;
private readonly Entity _entityForEdit;
public ItemEdit(Entity entity)
{
_entity = entity;
Mapper.CreateMap < Entity, Entity > ();
// 使用Mapper.Map<TSource,TDistination>(TEntity entity),获取新的实例。
_entityForEdit = Mapper.Map < Entity, Entity > (_entity);
}
public void Edit()
{
_entityForEdit.Name = " Entity is changed. " ;
}
public void Save()
{
// 使用Mapper.Map<TSource,TDistination>(TEntity entity, TDistination distinationEntity),
// 将从entity中取值,并复制给distinationEntity。
Mapper.Map < Entity,Entity > (_entityForEdit, _entity);
// you can save _entity here.
// ....
}
public void Cancel()
{
// do nothing.
}
}
}
上述MainTest方法,即模拟主界面的操作过程。代码是最好的注释,其余的就不详细解释了。
在使用AutoMapper的时候,通过Mapper.CreateMap方法,创建实体的映射关系。
Mapper.CreateMap<Entity, Entity>();
我们在这里,将Entity自己映射为Entity。如果不声明这个映射,在使用Mapper.Get的时候,我们也可以获得对象,但AutoMapper将总是创建一个新的实例来完成映射。
Mapper.CreateMap第一个参数为源类型,第二个参数为目标类型。在映射时,如果源类型的属性与目标类型中的属性对应,即可完成映射;如果属性不同,可以通过ForMember、Ignore等方法声明对关系进行声明。更多用法的详细说明,可参考文档(我没有找到更详细的,你有的话,可以告诉我。 )。代码是最好的注释,IDE工具智能感知一下,用起来还是比较顺手的。或者google之。
----------------------------------------分隔线后面是精华----------------------------------
重点注意:
A:在对实体映射赋值时,只带source一个参数的Map方法,AutoMapper将创建新的实例作为返回值。
_entityForEdit = Mapper.Map<Entity, Entity>(_entity);
B:在对实体映射赋值时,使用带Distination的Map方法,AutoMapper将source实体的值映射到destination上,并不创建新的实例。此时也有返回值,与destination相同。在这里,我们将_entityForEdit的值直接赋给_entity,以保证引用关系的不被破坏。(如采用A中的方法,则HashCode测试将不通过。)
Mapper.Map<Entity,Entity>(_entityForEdit, _entity);
--EOF--