使用過ORM的朋友對這一部分理解起來會非常快,如果沒有請自行補習吧:D.
不說廢話,首先,我們來開發一個簡單的CRM系統,CRM系統第一個信息當然是客戶信息。我們只做個簡單 的客戶信息來了解一下XAF好了。

新建項之後,可以看到如下代碼界面:
using System;
using System.Linq;
using System.Text;
using DevExpress.Xpo;
using DevExpress.ExpressApp;
using System.ComponentModel;
using DevExpress.ExpressApp.DC;
using DevExpress.Data.Filtering;
using DevExpress.Persistent.Base;
using System.Collections.Generic;
using DevExpress.ExpressApp.Model;
using DevExpress.Persistent.BaseImpl;
using DevExpress.Persistent.Validation;
namespace XCRMDemo.Module.BusinessObjects
{
[DefaultClassOptions]
//[ImageName("BO_Contact")]
//[DefaultProperty("DisplayMemberNameForLookupEditorsOfThisType")]
//[DefaultListViewOptions(MasterDetailMode.ListViewOnly, false, NewItemRowPosition.None)]
//[Persistent("DatabaseTableName")]
// Specify more UI options using a declarative approach (https://documentation.devexpress.com/eXpressAppFramework/CustomDocument112701.aspx).
public class 客戶 : BaseObject
{ // Inherit from a different class to provide a custom primary key, concurrency and deletion behavior, etc. (https://documentation.devexpress.com/eXpressAppFramework/CustomDocument113146.aspx).
public 客戶(Session session)
: base(session)
{
}
public override void AfterConstruction()
{
base.AfterConstruction();
// Place your initialization code here (https://documentation.devexpress.com/eXpressAppFramework/CustomDocument112834.aspx).
}
//private string _PersistentProperty;
//[XafDisplayName("My display name"), ToolTip("My hint message")]
//[ModelDefault("EditMask", "(000)-00"), Index(0), VisibleInListView(false)]
//[Persistent("DatabaseColumnName"), RuleRequiredField(DefaultContexts.Save)]
//public string PersistentProperty {
// get { return _PersistentProperty; }
// set { SetPropertyValue("PersistentProperty", ref _PersistentProperty, value); }
//}
//[Action(Caption = "My UI Action", ConfirmationMessage = "Are you sure?", ImageName = "Attention", AutoCommit = true)]
//public void ActionMethod() {
// // Trigger a custom business logic for the current record in the UI (https://documentation.devexpress.com/eXpressAppFramework/CustomDocument112619.aspx).
// this.PersistentProperty = "Paid";
//}
}
}
1.為客戶類填加屬性,填加屬性後將對應著數據庫中的字段:
我將在代碼中依次填加,姓名、禁用、性別、出生日期、手機號碼、地址、年收入、照片,幾個字段。
1 using System;
2 using System.Linq;
3 using System.Text;
4 using DevExpress.Xpo;
5 using DevExpress.ExpressApp;
6 using System.ComponentModel;
7 using DevExpress.ExpressApp.DC;
8 using DevExpress.Data.Filtering;
9 using DevExpress.Persistent.Base;
10 using System.Collections.Generic;
11 using System.Drawing;
12 using DevExpress.ExpressApp.Model;
13 using DevExpress.Persistent.BaseImpl;
14 using DevExpress.Persistent.Validation;
15
16 namespace XCRMDemo.Module.BusinessObjects
17 {
18 [DefaultClassOptions]
19 //[ImageName("BO_Contact")]
20 //[DefaultProperty("DisplayMemberNameForLookupEditorsOfThisType")]
21 //[DefaultListViewOptions(MasterDetailMode.ListViewOnly, false, NewItemRowPosition.None)]
22 //[Persistent("DatabaseTableName")]
23 // Specify more UI options using a declarative approach (https://documentation.devexpress.com/eXpressAppFramework/CustomDocument112701.aspx).
24 public class 客戶 : BaseObject
25 {
26 // Inherit from a different class to provide a custom primary key, concurrency and deletion behavior, etc. (https://documentation.devexpress.com/eXpressAppFramework/CustomDocument113146.aspx).
27 public 客戶(Session session)
28 : base(session)
29 {
30 }
31
32 public override void AfterConstruction()
33 {
34 base.AfterConstruction();
35 // Place your initialization code here (https://documentation.devexpress.com/eXpressAppFramework/CustomDocument112834.aspx).
36 }
37
38 //姓名、禁用、性別、出生日期、手機號碼、地址、年收入、照片
39 private string _姓名;
40
41 public string 姓名
42 {
43 get { return _姓名; }
44 set { SetPropertyValue("姓名", ref _姓名, value); }
45 }
46
47 private bool _禁用;
48
49 public bool 禁用
50 {
51 get { return _禁用; }
52 set { SetPropertyValue("禁用", ref _禁用, value); }
53 }
54
55 private 性別 _性別;
56
57 public 性別 性別
58 {
59 get { return _性別; }
60 set { SetPropertyValue("性別", ref _性別, value); }
61 }
62
63 private DateTime _出生日期;
64
65 public DateTime 出生日期
66 {
67 get { return _出生日期; }
68 set { SetPropertyValue("出生日期", ref _出生日期, value); }
69 }
70
71 private string _手機號碼;
72
73 public string 手機號碼
74 {
75 get { return _手機號碼; }
76 set { SetPropertyValue("手機號碼", ref _手機號碼, value); }
77 }
78
79 private string _地址;
80
81 public string 地址
82 {
83 get { return _地址; }
84 set { SetPropertyValue("地址", ref _地址, value); }
85 }
86
87 private decimal _年收入;
88
89 public decimal 年收入
90 {
91 get { return _年收入; }
92 set { SetPropertyValue("年收入", ref _年收入, value); }
93 }
94
95
96 [Size(SizeAttribute.Unlimited), VisibleInListView(true)]
97 [ImageEditor(ListViewImageEditorMode = ImageEditorMode.PictureEdit,
98 DetailViewImageEditorMode = ImageEditorMode.PictureEdit,
99 ListViewImageEditorCustomHeight = 40)]
100 public byte[] 照片
101 {
102 get { return GetPropertyValue<byte[]>("照片"); }
103 set { SetPropertyValue<byte[]>("照片", value); }
104 }
105 }
106
107 public enum 性別
108 {
109 男,女,未知
110 }
111 }
代碼修改為上述內容後,我們再次運行系統,按下F5.

可以看到,我們新建的業務對象“客戶”已經在菜中顯示了,按下New按鈕後,可以看到詳細界面。

上面就是新建客戶信息的界面了。下面我們來分析一下原理:
在代碼中,我們使用了ORM工具,XPO定義了一個客戶類,XPO運行時,分根據代碼中的屬性創建出數據庫字段,下圖是數據庫中的表情況:

可以看出,xpo自動為我們建立了“客戶”表,字段與“客戶”類中的屬性是一一對應的,但Oid,OptimisticLockField,GCRecord三個字段是我們沒有建立的屬性,卻出現了,其中:
Oid,是GUID類型,主鍵,這是因為我們的代碼中是這樣寫的:
public class 客戶 : BaseObject
Oid是在BaseObject中定義的,所以客戶類會自動建立這個字段。
OptimisticLockField:是XAF為了解決並發沖突而建立的字段。
GCRecord:繼承自BaseObject的類在刪除記錄時只是邏輯刪除,即只是將GCRecord中記錄一個值,而沒有刪除的記錄則為Null.
屬性的寫法:
最簡單的,當我們想在數據庫中定義一個字段時,可以在xpo類中寫一個屬性,當然這種說法很膚淺,但是為了方便理解,剛開始時這樣認為就可以了。
39 private string _姓名;
40
41 public string 姓名
42 {
43 get { return _姓名; }
44 set { SetPropertyValue("姓名", ref _姓名, value); }
45 }
這個屬性和以往開發中的方法沒有什麼大的不同,僅是在set部分調用了SetPropertyValue方法,xpo為我們提供了一系列基類,SetPropertyValue是多數類中都有的,它的功能是可以在有值被設置時,需要得到屬性變化時可以及時的得到通知。
當然,也可以直接使用
public string 姓名{get;set;}
這樣來定義出姓名屬性,但是有些場景時卻會帶來麻煩。
如:
public int 數量{get;set;}
public int 價格{get;set;}
public int 總價{get{return 數量*價格;}}
在數量和價格發生變化時,我們卻看不到總價發生變化,因為控件不知道數量、價格已經變化了。所以我們應該盡量使用SetPropertyValue進行寫set.
可以看到,字符串類型的姓名,在界面上最終顯示成了一個文本框,XAF中內置了很多這樣的控件,與類型做出了對應關系,當我們使用對應的類型時,就會自動使用對應的控件,這裡的控件被叫做編輯器(PropertyEditor)。
接下來,有禁用屬性:
private bool _禁用;
public bool 禁用
{
get { return _禁用; }
set { SetPropertyValue("禁用", ref _禁用, value); }
}
同樣的,在視圖中可以看到一個CheckBox編輯器出現了。
private DateTime _出生日期;
public DateTime 出生日期
{
get { return _出生日期; }
set { SetPropertyValue("出生日期", ref _出生日期, value); }
}
DateTime類型,直接使用CLR類型Datetime,日期型字段將在數據庫中創建。
可以看出,xpo使用clr類型映射到了數據中字段的類型,下表中說明了數據庫表中的字段類型與CLR類型的對應關系:
字段映射
除了自動建立的3個字段外,別的字段都是與代碼有對應關系的映射了,xpo默認支持以下幾種類型的映射:
上面所描述的是都簡單類型,其中枚舉類型、圖像類型、顏色,相對特殊一些,例如枚舉類型:
在代碼中,我們可以看到如下屬性定義
55 private 性別 _性別;
56
57 public 性別 性別
58 {
59 get { return _性別; }
60 set { SetPropertyValue("性別", ref _性別, value); }
61 }
這裡的性別是一個枚舉類型,定義如下:
107 public enum 性別
108 {
109 男,女,未知
110 }
打開詳細視圖,運行效果如下:
可以看出,XAF為我們使用類型進行了推導,自動使用了下拉框,並且取得到了枚舉中有哪些值,顯示在列表中供我們選擇。XAF中的這種自動機制使用得非常多,後續我們將會看到。
圖片的存儲:
[Size(SizeAttribute.Unlimited), VisibleInListView(true)]
[ImageEditor(ListViewImageEditorMode = ImageEditorMode.PictureEdit,
DetailViewImageEditorMode = ImageEditorMode.PictureEdit,
ListViewImageEditorCustomHeight = 40)]
public byte[] 照片
{
get { return GetPropertyValue<byte[]>("照片"); }
set { SetPropertyValue<byte[]>("照片", value); }
}
圖片的存儲稍微有些不一樣,在屬性的get方法中,使用了GetPropertyValue<byte[]>來取值。
並且使用了幾種Attribute,Attribute是為了擴展元數據的描述信息,簡單來說,C#(.net)下面的語言不可能是無止境擴展的,所以提供了這樣一種特殊的類,可以用來修飾程序中的無素,如assembly,class,interface,property,field,method等 等 .
xpo+xaf定義了很多的Attribute用來描述和擴展元數據信息,其中:
Size(SizeAttribute.Unlimited) 的意義是,創建數據庫字段時,長度不限。SizeAttribute.Unlimited的值其實是-1,當然有些場景會用到限制長度。如
[Size(100)] //姓名字段數據庫類型將是nvarchar(100)
public string 姓名{......}
[ImageEditor(ListViewImageEditorMode = ImageEditorMode.PictureEdit, DetailViewImageEditorMode = ImageEditorMode.PictureEdit,ListViewImageEditorCustomHeight = 40)]
這裡設置了圖像所使用的編輯器的參數,列表下面如何顯示,詳細視圖下面如何顯示,列表上顯示時控制高度。
因為本節主要介紹業務對象的創建方法,不擴展討論Attribute的用法,後續章節詳細描述。
下節介紹幾種常見的關系型數據庫節構在ORM中的實現方法。
文章示例項目源碼下載
QQ:4603528 QQ群:50185791