The formation of Entity Framework object Framework-Summary of several experiences in Entity Framework Development, entityframework
Some time ago, I made some research on the Entity Framework, and then developed a series of learning processes to explain the relevant technologies of the Entity Framework in a step-by-step manner, during this period, every time you encounter some new problems, you need to dive into the research. This article continues with the previous topic introduction, focusing on summarizing some aspects of the Entity Framework from a holistic perspective, and hopes to learn and exchange with you on these practical issues.
My learning and research on the entire Entity Framework is the end of a phase when my Winform framework is successfully upgraded to this Entity Framework. There are many things in this phase, starting from the development of the WebAPI platform for online ticketing of passenger transportation, the in-depth research on Microsoft's physical framework, and the topic learning and sharing of "Metronic-based Bootstrap development framework Experience summary, they are all mixed up, with multiple topics interspersed with some essays. They also hope to record and summarize their learning processes, so they don't have to wait until they are all forgotten.
1. Type constraints of primary keys in the object framework
In the process of building the entire object framework, we generally use abstract encapsulation to process many basic data processing functions such as addition, deletion, modification, query, and paging, as shown below.
/// <Summary> /// update object attributes to the database /// </summary> /// <param name = "t"> specified object </param> /// <param name = "key"> primary key value </param> /// <returns> returns <c> true </c> If execution is successful, otherwise, the value is <c> false </c> </returns> bool Update (T t, object key ); /// <summary> // update object attributes to the database (asynchronous) /// </summary> /// <param name = "t"> value of the primary key of the specified object </param> /// <param name = "key"> </param> // <returns> returns <c> true </c> If execution succeeds, otherwise, it is set to <c> false </c> </returns> Task <bool> UpdateAsync (T T, object key); // <summary> // Based on the ID of the specified object, delete the specified object from the database /// </summary> /// <param name = "id"> ID of the object </param> /// <returns> returns the result of successful execution. <c> true </c>, otherwise, the value is <c> false </c>. </Returns> bool Delete (object id); // <summary> // Delete a specified object (asynchronous) from the database based on the specified object ID) /// </summary> /// <param name = "id"> Object ID </param> /// <returns> returns <c> true </ c>, otherwise, the value is <c> false </c>. </Returns> Task <bool> DeleteAsync (object id); // <summary> // query the database, returns the object of the specified ID /// </summary> /// <param name = "id"> ID primary key value </param> /// <returns> specified object, otherwise, Null </returns> T FindByID (object id) is returned; // <summary> // query the database, and the object with the specified ID is returned (asynchronous) /// </summary> /// <param name = "id"> ID primary key value </param> /// <returns> returns the specified object, otherwise, Null </returns> Task <T> FindByIDAsync (object id) is returned );
The foreign key above is defined as the object type in a unified way, because we want to consider general primary key types.
In fact, there may be many types of foreign keys in the table, such as common character types, int types, and long types. If we update, search, and delete records of the integer type, an error may occur:
The argument types 'Edm.Int32' and 'Edm.String' are incompatible for this operation.
These errors are caused by primary key Type Mismatch. When operating these interfaces, we must pass in the corresponding types to them for normal processing.
I was trying to convert the data type to the correct type internally, but I didn't find a good solution to identify and process it. Therefore, the best solution is, that is, when we call these interfaces with primary keys of the object type, we can pass in the correct type.
RoleInfo info = CallerFactory<IRoleService>.Instance.FindByID(currentID.ToInt32()); if (info != null) { info = SetRoleInfo(info); CallerFactory<IRoleService>.Instance.Update(info, info.ID); RefreshTreeView(); }
Or the following code:
/// <Summary> // delete a page control /// </summary> private void winGridViewPager1_OnDeleteSelected (object sender, EventArgs e) {if (MessageDxUtil. showYesNoAndTips ("are you sure you want to delete the selected record? ") = DialogResult. no) {return;} int [] rowSelected = this. winGridViewPager1.GridView1. getSelectedRows (); foreach (int iRow in rowSelected) {string ID = this. winGridViewPager1.GridView1. getRowCellDisplayText (iRow, "ID"); CallerFactory <IDistrictService>. instance. delete (ID. toInt64 ();} BindData ();}
2. Processing of recursive functions
In many cases, recursive functions are used to extract the contents of the entire list. This is one of the common knowledge points in our development.
However, when processing LINQ, the processing of its recursive functions is somewhat different from our common practice.
For example, if we want to obtain a tree-like mechanism list and if we specify a node ID of the starting mechanism, we need to recursively retrieve the set of all layers below. The general practice is as follows.
/// <Summary> /// Based on the node ID of the specified organization, obtain the list of all organizations below /// </summary> /// <param name = "parentId"> ID of the specified organization node </param> /// <returns> </ returns> public List <OUInfo> GetAllOUsByParent (int parentId) {List <OUInfo> list = new List <OUInfo> (); string SQL = string. format ("Select * From {0} Where Deleted <> 1 Order By PID, Name", tableName); DataTable dt = SqlTable (SQL); string sort = string. format ("{0} {1}", GetSafeFileName (SortField), isDescending? "DESC": "ASC"); DataRow [] dataRows = dt. select (string. format ("PID = {0}", parentId), sort); for (int I = 0; I <dataRows. length; I ++) {string id = dataRows [I] ["ID"]. toString (); list. addRange (GetOU (id, dt);} return list;} private List <OUInfo> GetOU (string id, DataTable dt) {List <OUInfo> list = new List <OUInfo> (); OUInfo ouInfo = this. findByID (id); list. add (ouInfo); string sort = string. forma T ("{0} {1}", GetSafeFileName (sortField), isDescending? "DESC": "ASC"); DataRow [] dChildRows = dt. select (string. format ("PID = {0}", id), sort); for (int I = 0; I <dChildRows. length; I ++) {string childId = dChildRows [I] ["ID"]. toString (); List <OUInfo> childList = GetOU (childId, dt); list. addRange (childList);} return list ;}
The general idea here is to get all the qualified sets to the able set, and then retrieve them in the set, that is, recursively obtain the content in the set.
The above is a conventional practice. We can see that the amount of code is still too large. If you use LINQ, this is not necessary and cannot be done in this way.
After the Entity Framework is used, we mainly use LINQ to perform some set operations. Although these LINQ operations are a bit difficult, they are easy to learn and process.
At the data access layer, the processing functions are the same as those described above. The following shows the code for the LINQ operation.
/// <Summary> /// Based on the node ID of the specified organization, obtain the list of all organizations below /// </summary> /// <param name = "parentId"> ID of the specified organization node </param> /// <returns> </ returns> public IList <Ou> GetAllOUsByParent (int parentId) {// recursively obtain the specified PID and all the following OU var queries = this. getQueryable (). where (s => s. PID = parentId ). where (s =>! S. deleted. hasValue | s. deleted = 0 ). orderBy (s => s. PID ). orderBy (s => s. name); return query. toList (). concat (query. toList (). selectcenters (t => GetAllOUsByParent (t. ID ))). toList ();}
Basically, we can see that the two lines of code are exactly the same.
However, not all LINQ recursive functions can be very simplified. For some recursive functions, we still need to use conventional methods for processing.
/// <Summary> /// obtain the list of organizations in the tree structure /// </summary> public IList <OuNodeInfo> GetTree () {IList <OuNodeInfo> returnList = new List <OuNodeInfo> (); IList <Ou> list = this. getQueryable (). where (p => p. PID =-1 ). orderBy (s => s. PID ). orderBy (s => s. name ). toList (); if (list! = Null) {foreach (Ou info in list. where (s => s. PID =-1) {OuNodeInfo nodeInfo = GetNode (info); returnList. add (nodeInfo) ;}} return returnList ;}
However, we have already provided a great deal of convenience to our users.
3. Error Handling for date field type conversion
When we make some tables, there will usually be a date type, such as our birthday, create, edit date, etc. Generally, our database may use the datetime type, if the content of the date type is in the following range:
"0001-01-01 to 9999-12-31)
We may get the following error:
The conversion from datetime2 data type to datetime data type generates a value out of the range
Generally, the error message "data type conversion produces a value out of range" is because the data size and range exceed the target to be converted. Let's first look at the specific differences between the datetime2 and datetime data types.
Official MSDN description of datetime2: defines a date that combines the 24-hour time. You can regard datetime2 as an extension of the existing datetime type. It has a larger data range, a higher decimal precision by default, and optional User-Defined precision.
Note that the date range of datetime2 is "0001-01-01 to 9999-12-31" (from January 1, January 1 to January 31, December 31, 9999 AD ). The datetime date range is: "January 1,". The date range here is the cause of the "Conversion from datetime2 data type to datetime data type produces a value out of range" error !!!
In c #, if the attribute of the object class is not assigned a value, the default value is generally used. For example, if the default value of the int type is 0 and the default value of the string type is null, what about the default value of DateTime? Since the default value of DateTime is "0001-01-01", when entity framework performs database operations, when the data is imported, the original datetime data field is automatically converted to the datetime2 type (because the time of 0001-01-01 is beyond the minimum date range of datetime in the database ), then, perform database operations. The problem arises. Although EF has automatically converted the data to the datetime2 type, the field in the table in the database is of the datetime type! Therefore, if you add data of the datetime2 type to a datetime field in the database, an error is reported and the conversion is out of range.
The solution is as follows:
Solution to this problem:
For example, in the object framework, I initialize the date type field of the User table, which ensures that the default value is normal when I store data.
/// <Summary> /// system user information, data Object // </summary> public class User {// <summary> // default constructor (the attribute to be initialized must be processed here) /// </summary> public User () {this. ID = 0; // a value out of the range is generated from the datetime2 data type to the datetime data type conversion // to avoid this problem, you can initialize the Date Field DateTime defaultDate = Convert. toDateTime ("1900-1-1"); this. birthday = defaultDate; this. lastLoginTime = defaultDate; this. lastPasswordTime = defaultDate; this. currentLoginTime = defaultDate; this. editTime = DateTime. now; this. createTime = DateTime. now ;}
Sometimes, even though this is done, you may have set unreasonable values for this date field on the interface, or you may have problems. In this case, we can determine that if it is smaller than a certain value, we will give it a default value.
4. interface processing of the object framework
To adjust this part of the interface, we still try to maintain the Winform interface style of the Enterprise Library, that is, the mixed or normal Winform interface effect. However, here we use a hybrid framework for integration testing. Therefore, the calling and processing of all aspects of the physical framework are basically consistent.
However, because the Entity classes in the object framework avoid coupling, we introduced the concept of DTO and used the AutoMapper component to map Entity and DTO, for details, refer to the Entity Framework formation journey-Data Transmission Model DTO and Entity model Entity separation and union.
.
Therefore, we operate on the DTO object type on the interface. To avoid more changes, the class name like *** Info is still used as the name of the DTO object, and *** represents the table name object.
In the interface presentation layer of the hybrid framework, the processing of their data objects is basically the same as that of the original code.
/// <Summary> /// save data in the new State /// </summary> /// <returns> </returns> public override bool SaveAddNew (){UserInfoInfo = tempInfo; // existing local variables must be used, because some information may be appended with SetInfo (info); info. creator = Portal. gc. userInfo. fullName; info. creator_ID = Portal. gc. userInfo. ID. toString (); info. createTime = DateTime. now; try {# region New DataBool succeed = CallerFactory <IUserService>. Instance. Insert (info );If (succeed) {// you can add other join operations return true;} # endregion} catch (Exception ex) {LogTextHelper. error (ex); MessageDxUtil. showError (ex. message);} return false ;}
However, we need to describe the ing between them at the WCF Service layer to facilitate internal conversion.
In the query of the interface layer of the object framework, we do not use some SQL conditions. We use a safer DTO-based LINQ expression for encapsulation, the last object that is passed to the backend is a LINQ object (non-traditional Object LINQ, which will cause errors in distributed processing ).
The encapsulation of query conditions is as follows:
/// <Summary> /// construct a query statement based on the query conditions /// </summary> private ExpressionNode GetConditionSql () {Expression <Func <UserInfo, bool> expression = p => true; if (! String.IsNullOrEmpty(this.txt HandNo. Text) {expression = expression. And (x => x.HandNo.Equals(this.txt HandNo. Text);} if (! String.IsNullOrEmpty(this.txt Name. text) {expression = expression. and (x => x.Name.Contains(this.txt Name. text ));}................................... ...... // Add the company ID if (Portal. gc. userInRole (RoleInfo. companyAdminName) {expression = expression. and (x => x. company_ID = Portal. gc. userInfo. company_ID);} // if the condition is obtained by clicking the node, use the tree list. Otherwise, use the if (treeCondition! = Null) {expression = treeCondition;} // if not selected, only normal user if (! This. chkIncludeDelete. Checked) {expression = expression. And (x => x. Deleted = 0);} return expression. ToExpressionNode ();}
The processing of paging queries is still similar to the original style, but the Where condition here is the ExpressionNode object, as shown in the code,
ExpressionNode where = GetConditionSql (); PagerInfo = this. winGridViewPager1.PagerInfo; IList <UserInfo> list = CallerFactory <IUserService>. instance. findWithPager (where, ref PagerInfo); this. winGridViewPager1.DataSource = new WHC. pager. winControl. sortableBindingList <UserInfo> (list); this. winGridViewPager1.PrintTitle = "system user information report ";
Finally, let's take a look at the structure and Interface Effects of the entire object framework.
The interface effect is as follows:
The code structure is as follows:
The architecture design is as follows: