In the previous project development, we often encountered a problem: for example, in the architecture design of the project outside, we use MVC and entityframework to build a Web application. For example, we use common multi-layer architecture, such as presentation layer, Businesslogic layer, dataaccess layer, etc., each layer is relatively independent and the responsibility is clear. For example, we define viewmodel in the presentation layer, and the dbcontext part of the dataaccess layer is generated entityframework from Storagemodel, or Datamodel. Then we need to pass this data to ViewModel after we fetch it from the database from the DataAccess layer, and finally to the previous user, and of course the fields (attributes) defined between the two model may differ, which we will discuss later.
Let's take a look at how to solve this kind of problem first. The simplest and most clumsy way to do this is to assign a property to an object, such as this:
varViewModels =NewList<employeeviewmodel>(); List<EmployeeStorageModel> Storagemodels =NewList<employeestoragemodel>(); if(Storagemodels.count >0) {Employeeviewmodel ViewModel=NULL; foreach(varStoragemodelinchstoragemodels) {Viewmodel.number=Storagemodel.name; Viewmodel.name=Storagemodel.name; Viewmodel.hiredate=storagemodel.hiredate; Viewmodel.job=Storagemodel.job; Viewmodel.department=storagemodel.department; Viewmodel.salary=storagemodel.salary; //.... } }
If the properties of an object are more than one, then we have to write a long list of assignment statements, which is not a clever way to show. The purpose of saying so much is to throw a question. What about the solution? There are many solutions to this problem, such as using automapper and so on, before I remember one of our colleagues wrote an extension method that uses JSON serialization and deserialization to implement mapping between object data members, but what I'm going to take out today is to use mappings to solve this problem.
The features I want to implement are the following, or the problem I want to solve:
1. Implement mapping of two different object data members, including attributes and public-readable segments;
2. Map data members with the same name and data type to another object;
Here I'll give you the core code, which will explain the business logic and some attention issues in detail later on:
First, define a attribute, which is mainly used to identify the alias of the data member of the class, and I'll explain why.
//alias of data member[AttributeUsage (attributetargets.property|Attributetargets.field, AllowMultiple=false, inherited=true)] Public classDataMemberAliasAttribute:System.Attribute {Private ReadOnly string_alias; PublicDatamemberaliasattribute (stringalias) {_alias=alias; } Public stringAlias {Get { return_alias; } } }
Second, define a class that contains a common static method mapping, which is an extension method, which will explain in detail why you should define it this way.
Private StaticT mapping<t> ( This ObjectSourcewhereR |class{Type T=typeof(T); if(Source = =NULL) { return default(T); } T Target=(T) t.assembly.createinstance (t.fullname); #regionMapping Propertiespropertyinfo[] Targetprops=t.getproperties (); if(Targetprops! =NULL&& targetprops.length >0) { stringTargetpropname;//Target Property namePropertyInfo Sourceprop; ObjectSourcepropvalue; foreach(PropertyInfo Targetpropinchtargetprops) { //The alias of the data member is preferred, and if no alias is used, the property name Object[] Targetpropaliasattrs = Targetprop.getcustomattributes (typeof(Datamemberaliasattribute),true); if(Targetpropaliasattrs! =NULL&& targetpropaliasattrs.length >0) Targetpropname= ((Datamemberaliasattribute) targetpropaliasattrs[0]). Alias; ElseTargetpropname=Targetprop.name; //Retrieving Source PropertiesSourceprop =source. GetType (). GetProperty (Targetpropname); if(Sourceprop! =NULL&& Sourceprop.canread &&targetprop.canread) {Sourcepropvalue= Sourceprop.getvalue (Source,NULL); //when property types are consistent, populate property values directly if(Targetprop.propertytype = =sourceprop.propertytype) Targetprop.setvalue (target, Sourcepropvalue,NULL); } } } #endregion #regionMapping fieldsfieldinfo[] Targetfields=T.getfields (); if(targetfields!=NULL&&targetFields.Length>0) { stringTargetfieldname; FieldInfo SourceField; foreach(FieldInfo Targetfieldinchtargetfields) { if(!targetfield.isinitonly &&!)targetfield.isliteral) {//fields can be assigned values Object[] Targetfieldattrs = Targetfield.getcustomattributes (typeof(Datamemberaliasattribute),true); if(Targetfieldattrs! =NULL&& targetfieldattrs.length >0) Targetfieldname= ((Datamemberaliasattribute) targetfieldattrs[0]). Alias; ElseTargetfieldname=Targetfield.name; SourceField=source. GetType (). GetField (Targetfieldname); if(sourcefield!=NULL) { //data type with simultaneous mapping values if(Targetfield.fieldtype = =sourcefield.fieldtype) Targetfield.setvalue (target, Sourcefield.getvalue (source)); } } } } #endregion returnTarget; } Public StaticTOut mapping<tout,tin> ( ThisTIn Source)whereTIn:class whereTOut:class { returnSource. Mapping<tout>(); } }
Thirdly, I can then use this syntax to map data members between two objects:
Employeeviewmodel ViewModel = Storagemodel.mapping<employeeviewmodel, employeestoragemodel> ();
OK, the code is all posted. So let me first introduce you to the first question: Why define the Datamemberaliasattribute attribute class. People do one thing often to solve a certain kind of problem, similarly, the reason is to adapt to the two objects (classes) contain the names of the data members may be different. Take the above example, in the above Employeestoragemodel may or contains a property, called Hire_date, indicating the employee's employment date, However, there may be a property called HireDate in the Employeeviewmodel class that corresponds to it. This is not enough if we only use the name of the property itself, and this often happens because our storagemodel is usually entityframework auto-generated, and his name is usually the same as the name of the field in the database table. It all depends on the design habits of the database Table Designer, but ViewModel is likely to be designed by a. NET programmer, who is likely to name the properties according to Microsoft's proposed property naming method, for example, with uppercase letters for each word. So here I define this attribute class, which is used to represent the "alias" of a data member, using the following method:
Public classEmployeeviewmodel { Public Const stringPhoneNumber =""; Public ReadOnly stringEmailAddress; [Datamemberalias ("ID")] Public intId {Get;Set; } [Datamemberalias ("Employee_number")] Public stringEmployeeNumber {Get;Set; } [Datamemberalias ("Employee_Name")] Public stringEmployeeName {Get;Set; } [Datamemberalias ("hire_date")] PublicDateTime HireDate {Get;Set; } [Datamemberalias ("Salary")] Public DoubleSalary {Get;Set; } [Datamemberalias ("Job")] Public stringJob {Get;Set; } [Datamemberalias ("Department")] PublicDepartment Department {Get; Set; } Public intStatus {Get;Set; } }
This will solve the problem raised above. Note If you set an alias for a data member in the object class, the alias is preferred when mapping, and if no alias is set for the data member, the name of the data member itself is matched. Also pay attention to the following details:
1, object B's properties need to be writable, object A's properties need to be readable, otherwise this data member will be ignored;
2, the field of object B needs to be assignable, that is, cannot take the readonly or the const modifier, because in this case, cannot assign the value to the field, can only ignore them;
3. Only public properties and fields are mapped (usually properties are common, and fields with the publicly modifier have some characteristics of attributes)
4, the data type of the same data member of object A and object B must be the same;
5. If the data type of the data member in object A and object B is the same and is a reference type, then the pass is a reference and should be noted in use, see example code for the Department property
[Datamemberalias ("Department")] Public Department Department { get; Set ; }
Public class Department { publicintgetset;} Public string Get Set ; } Public string Get Set ; } }
Now let's take a look at this extension method of mapping, first this method is a generic method, which contains two types of parameters tin and tout,tout as the data type of the method return value, and the data type of object B, the tin is the data type of the parameter, that is, the data type of object A. Both types of parameters must be specified as class types when instantiated, because of the design of the application scenario. Because this is an extension method, you can use the following syntax to make a call to a method:
Employeeviewmodel ViewModel = Storagemodel.mapping<employeeviewmodel, employeestoragemodel> ();
Originally wanted to talk about the business logic of the mapping method, but because of the relationship between time, here is no longer elaborate, the code has comments. If you are interested, you can add me: happy_chopper
--------------------------------------------------------------------------------------------------------------- ---------------------End
. NET uses reflection to implement automatic mapping of data members in two objects