概述
这里有许多的解决方案,来实际对象间数据的复制。最常见的应用场景就是生成DTO对象。让我们假设你有一个数据访问层。在这一层,你通过一些工具,访问了数据库。比如LINQ to SQL、Entity Framework或者其他的ORM框架工具。这些工具能够将查询的结果转换为对象。但是这些对象中,包含了很多的技术细节。它可能基于一些特定的类、特性、属性或其他的特有设置。数据访问层直接抛出这种对象,有时并不是很好的选择。有时,甚至无法做到。 一个常见的方法是使用DTO
假如我们建了下面的表:
假如我们建了下面的表:
CREATE TABLE [dbo].[Customers]( [CustomerID] uniqueidentifier NOT NULL PRIMARY KEY , [ContactName] [nvarchar](30) NULL , [Address] [nvarchar](60) NULL , [Phone] [nvarchar](24) NULL ) |
在数据访问层,我们通过LINQ to SQL访问数据库。 Visual Studio设计器会生成customers表的实体对象:
[Table(Name= "dbo.Customers" )] public partial class Customer : INotifyPropertyChanging, INotifyPropertyChanged { private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty); private string _CustomerID; private string _ContactName; private string _Address; private string _Phone; #region Extensibility Method Definitions ... #endregion public Customer() { OnCreated(); } [Column(Storage= "_CustomerID" , DbType= "NChar(5) NOT NULL" , CanBeNull= false , IsPrimaryKey= true )] public string CustomerID { get { return this ._CustomerID; } set { if (( this ._CustomerID != value)) { this .OnCustomerIDChanging(value); this .SendPropertyChanging(); this ._CustomerID = value; this .SendPropertyChanged( "CustomerID" ); this .OnCustomerIDChanged(); } } } [Column(Storage= "_ContactName" , DbType= "NVarChar(30)" )] public string ContactName { get { return this ._ContactName; } set { if (( this ._ContactName != value)) { this .OnContactNameChanging(value); this .SendPropertyChanging(); this ._ContactName = value; this .SendPropertyChanged( "ContactName" ); this .OnContactNameChanged(); } } } ... public event PropertyChangingEventHandler PropertyChanging; public event PropertyChangedEventHandler PropertyChanged; protected virtual void SendPropertyChanging() { if (( this .PropertyChanging != null )) { this .PropertyChanging( this , emptyChangingEventArgs); } } protected virtual void SendPropertyChanged(String propertyName) { if (( this .PropertyChanged != null )) { this .PropertyChanged( this , new PropertyChangedEventArgs(propertyName)); } } } |
我们会发现,这个对象中,包含了许多的信息,是不需要暴露出去的。为了解决这个问题,我们创建了一个自定义的DTOCustomer类:
public class DTOCustomer { public Guid CustomerId { get ; set ; } public string ContactName { get ; set ; } public string Address { get ; set ; } public string Phone { get ; set ; } } |
现在,数据访问层方法看起来像下面的样子:
public DTOCustomer GetCustomer(Guid customerId) { using (var dc = new DataContext()) { var customer = dc.Customers.Where(c => c.CustomerID == customerId).Single(); return new DTOCustomer // <-- this is our DTO class which is visible from outside { CustomerID = customer.CustomerID, ContactName = customer.ContactName, Address = customer.Address, Phone = customer.Phone }; } } |
这个解决方案有以下问题:
- 这种单调重复的写法,很让人郁闷
- 很容易产生BUG。如果你在Customer 表中新增了一个字段,那么,你需要在DTOCustomer中新增一个属性,同时在映射方法中写入映射代码,如果少写了,就会抛出错误。
这个问题可以使用EmitMapper类库来解决
public DTOCustomer GetCustomer(Guid customerId) { using (var dc = new DataContext()) { var customer = dc.Customers.Where(c => c.CustomerID == customerId).Single(); return ObjectMapperManager.DefaultInstance.GetMapper<Customer, DTOCustomer>().Map(customer); } } |
Emit Mapper是一个非常强大的工具。你能够自定义数据来源,类型转换,要转换为的对象。 Emit Mapper 类库没有使用System.Data命名空间,但是能够定义策略来实现DbDatareader 到Entity的映射。你能够在DbDatareader的字段上打特性,或者映射DTO的部分属性,或者自定义自己的想要的逻辑
能够使用EmitMapper来跟踪对象的更改:
public class A { public string f1 = "f1" ; public int f2 = 2; public bool f3 = true ; } ... var tracker = new ObjectsChangeTracker(); var a = new A(); tracker.RegisterObject(a); a.f2 = 3; string [] changes = tracker.GetChanges(a); Assert.IsTrue( changes[0] == "f2" ); |
能够自动生成新增或者更新的SQL语句
using (DbConnection connection = CreateConnection()) { DBTools.InsertObject( connection, new Customer { CustomerID = Guid.NewGuid(), ContactName = "John Smith" , Address = "Some Street 15" , Phone = "1234567890" }, "Customers" , DbSettings.MSSQL ); } |
你要意识到,Emit Mapper也能作为一个“超级ORM”工具,你能够基于他开发一套适用于你的项目的数据访问层工具类库。有时,他比使用ORM工具更加的高效。