11/1 更新:全面支持实体属性级联更新,详见下面的案例的Entity Usage UnitTests代码。
NBearV3新增/修改功能列表:
1、完全重新设计的ORM实现,支持实体继承,实体间复杂关联(一对一、一对多、多对多)及透明的级联插入、更新、删除,LazyLoad等。
2、提供用于整个开发过程的更易使用的代码生成工具,支持:实体设计代码、实体代码、实体配置文件和数据库创建脚本生成。
3、精简优化了底层数据访问代码,进行了更细致的单线程/多线程性能测试。
4、实体及关联关系可以使用任意标准的.Net Framework支持的语言,使用interface、Attribute、接口继承等语言的自然元素作为实体设计元数据,并使用VS.NET2005的类设计器进行设计。
5、自动生成的实体类是标准的class,避免了V2中基于Emit生成代码的性能损失和可能的内存泄露,集成用于强类型查询的查询代码到每个实体类,并支持标准的各种系统序列化(XML,Binary,WebService SOAP)。生成的实体类代码不
11/1 更新:全面支持实体属性级联更新,详见下面的案例的Entity Usage UnitTests代码。
今天发布了NBear的全新版本V3的Preview。
感兴趣的朋友可以从http://sf.net/projects/nbear下载最新源码。
NBearV3相对于V2作了巨大升级和改进。因此不兼容于NBearV2。
之所以目前称为Preview版是因为新版本的源码中除了包含基于NBearV3重写的SimpleGuestbookV1.1之外,还没有任何相关使用文档,并且,实体生成工具仅支持C#,还不支持直接生成VB.NET代码。我会在近期不断补充,并于年内发布V3的正式版。
NBearV3新增/修改功能列表:
1、完全重新设计的ORM实现,支持实体继承,实体间复杂关联(一对一、一对多、多对多)及透明的级联插入、更新、删除,LazyLoad等。
2、提供用于整个开发过程的更易使用的代码生成工具,支持:实体设计代码、实体代码、实体配置文件和数据库创建脚本生成。
3、精简优化了底层数据访问代码,进行了更细致的单线程/多线程性能测试。
4、实体及关联关系可以使用任意标准的.Net Framework支持的语言,使用interface、Attribute、接口继承等语言的自然元素作为实体设计元数据,并使用VS.NET2005的类设计器进行设计。
5、自动生成的实体类是标准的class,避免了V2中基于Emit生成代码的性能损失和可能的内存泄露,集成用于强类型查询的查询代码到每个实体类,并支持标准的各种系统序列化(XML,Binary,WebService SOAP)。生成的实体类代码不依赖于实体设计元数据。
6、对于ServiceFactory部分,增加了SerializationManager类,用于增加自定义类型序列化实现,从而使得service能够支持任意类型的自定义参数和返回值。
7、重新优化设计的Gateway类,新增PageSelector强类型数据分页器,强/弱类型数据访问,批处理数据访问更简洁高效。
8、NBear.Web模块中,删除了UrlRewrite模块。因为,这样的模块市面上太多了,有些也很强大,因此NBear没必要内置一个轻量级实现了。
-
ORM案例演示
这里演示的案例代码包含于源代码中的NBear.Test.CaseTests程序集,演示了一组包含继承关系、复杂关联关系、复合数据类型、枚举类型的实体。
所有的实体关系图如下:
以上的实体关系图是标准的VS2005 IDE的类设计器对于实际的实体设计代码的直观反映。其中User、AgentUser、LocalUser为一组有继承关系的实体。UserProfile为和所有User一对一关联的Profile信息。Group为和所有User多对多关联的Group,UserGroup为关联实体,除了包含用于关联的字段之外,还可以包含多个Weight属性。Domain为和AgentUser和LocalUser关联的Domain,它也是一个多对多关系,关联实体为AgentUserDomain。同时,关联实体支持被关联的实体是多主键的情形。另外,User.Name为一个包含FirstName和LastName字段的struct,这个复合类型映射到一个名为Name的string字段,如何序列化和反序列化这个struct可自定义,而User.Status为一个枚举类型,映射到一个int类型的Status数据字段。
完整的实体设计代码如下:
Entity Design Code
1using System;
2using System.Collections.Generic;
3using System.Text;
4
5using NBear.Common.Design;
6using NBear.Test.CaseTests.shared;
7
8namespace NBear.Test.CaseTests.design
9{
10 public interface User : Entity
11 {
12 [CompoundUnit, SqlType("ntext")]
13 UserName Name
14 {
15 get;
16 set;
17 }
18
19 UserStatus Status
20 {
21 get;
22 set;
23 }
24
25 [PrimaryKey]
26 Guid ID
27 {
28 get;
29 set;
30 }
31
32 [Query(LazyLoad=false, Where="{UserID}=@ID"), Contained]
33 UserProfile Profile
34 {
35 get;
36 set;
37 }
38
39 [Query(LazyLoad = true, RelationType = typeof(UserGroup), Where = "{IsPublic}=1")]
40 Group[] Groups
41 {
42 get;
43 }
44 }
45
46 public interface Group : Entity
47 {
48 [PrimaryKey]
49 Guid ID
50 {
51 get;
52 set;
53 }
54
55 string Name
56 {
57 get;
58 set;
59 }
60
61 bool IsPublic
62 {
63 get;
64 set;
65 }
66 }
67
68 public interface AgentUser : User
69 {
70 string LoginName
71 {
72 get;
73 set;
74 }
75
76 [Query(LazyLoad = true, RelationType = typeof(AgentUserDomain))]
77 Domain[] Domains
78 {
79 get;
80 }
81 }
82
83 public interface LocalUser : AgentUser
84 {
85 string Password
86 {
87 get;
88 set;
89 }
90 }
91
92 public interface UserProfile : Entity
93 {
94 [PrimaryKey]
95 Guid UserID
96 {
97 get;
98 set;
99 }
100
101 string ContentXml
102 {
103 get;
104 set;
105 }
106 }
107
108 [Relation]
109 public interface UserGroup : Entity
110 {
111 [RelationKey(typeof(User), "ID")]
112 Guid UserID
113 {
114 get;
115 set;
116 }
117
118 [RelationKey(typeof(Group), "ID")]
119 Guid GroupID
120 {
121 get;
122 set;
123 }
124
125 int Weight
126 {
127 get;
128 set;
129 }
130 }
131
132 [Relation]
133 public interface AgentUserDomain : Entity
134 {
135 [RelationKey(typeof(AgentUser), "ID")]
136 Guid AgentUserID
137 {
138 get;
139 set;
140 }
141
142 [RelationKey(typeof(Domain), "ID")]
143 Guid DomainID
144 {
145 get;
146 set;
147 }
148 }
149
150 public interface Domain : Entity
151 {
152 [PrimaryKey]
153 Guid ID
154 {
155 get;
156 set;
157 }
158
159 string Name
160 {
161 get;
162 set;
163 }
164
165 string Desc
166 {
167 get;
168 set;
169 }
170 }
171}
注意,所有的设计实体都使用接口表示,并使用必要的Attribute进行修饰。关于如何使用这些Attribute,近期我会有独立的文档描述,这里大家可以直观感受一下。每个属性的SqlType属性并不是必须的,如果不指定,则代码生成工具将会根据属性的.Net类型使用默认值,特别是对于数值类型,字符串类型建议自定义长度,否则默认为nvarchar(127)。
注意,这里列出的是设计实体代码,所有最终的实际实体(和用于设计的这些代码没有任何依赖来关系)、相关配置信息和数据库生成脚本都能够基于以上设计代码由NBear提供的工具自动生成。生成的具体的代码,我就不演示了。下面简单列举用于操作这些实体的测试代码,包括CRUD和Transaction(11/1更新支持实体属性级联更新,保留更改前的测试代码为黄色,更改后的代码用正常颜色表示)。
以下是支持属性级联更新前的测试代码:
Entity Usage UnitTests
1using System;
2using System.Data.Common;
3using System.Text;
4using System.Transactions;
5using System.Collections.Generic;
6using Microsoft.VisualStudio.TestTools.UnitTesting;
7
8using Entities;
9using NBear.Common;
10using NBear.Data;
11
12using NBear.Test.CaseTests.shared;
13
14namespace NBear.Test.CaseTests
15{
16 [TestClass]
17 public class CaseTest
18 {
19 Additional test attributes#region Additional test attributes
20 //
21 // You can use the following additional attributes as you write your tests:
22 //
23 // Use ClassInitialize to run code before running the first test in the class
24 // [ClassInitialize()]
25 // public static void MyClassInitialize(TestContext testContext) { }
26 //
27 // Use ClassCleanup to run code after all tests in a class have run
28 // [ClassCleanup()]
29 // public static void MyClassCleanup() { }
30 //
31 // Use TestInitialize to run code before running each test
32
33 private Gateway gateway = null;
34
35 [TestInitialize()]
36 public void MyTestInitialize()
37 {
38 gateway = new Gateway("CaseTests");
39 gateway.RegisterSqlLogger(new LogHandler(Console.Write));
40 }
41
42 // Use TestCleanup to run code after each test has run
43 // [TestCleanup()]
44 // public void MyTestCleanup() { }
45 //
46 #endregion
47
48 [TestMethod]
49 public void TestCreate()
50 {
51 LocalUser newLocalUser = new LocalUser();
52 newLocalUser.ID = Guid.NewGuid();
53 newLocalUser.LoginName = newLocalUser.ID.ToString();
54 UserName name = new UserName();
55 name.FirstName = "first name of local user";
56 name.LastName = "last name of local user";
57 newLocalUser.Name = name;
58 newLocalUser.Password = "password";
59 newLocalUser.Status = UserStatus.Normal;
60
61 gateway.Create<LocalUser>(newLocalUser);
62 }
63
64 [TestMethod]
65 public void TestFind()
66 {
67 AgentUser[] users = gateway.FindArray<AgentUser>(WhereClip.All, OrderByClip.Default);
68 }
69
70 [TestMethod]
71 public void TestUpdate()
72 {
73 LocalUser user = gateway.FindArray<LocalUser>(WhereClip.All, LocalUser._.Password.Desc)[0];
74 user.Password = "12345";
75 UserName newName = new UserName();
76 newName.FirstName = "12345";
77 user.Name = newName;
78 gateway.Update<LocalUser>(user);
79 user = gateway.Find<LocalUser>(user.ID);
80 Assert.AreEqual(user.Password, "12345");
81 Assert.AreEqual(user.Name, newName);
82 }
83
84 [TestMethod]
85 public void TestDelete()
86 {
87 LocalUser newLocalUser = new LocalUser();
88 newLocalUser.ID = Guid.NewGuid();
89 newLocalUser.LoginName = newLocalUser.ID.ToString();
90 UserName name = new UserName();
91 name.FirstName = "first name of local user";
92 name.LastName = "last name of local user";
93 newLocalUser.Name = name;
94 newLocalUser.Password = "password";
95 newLocalUser.Status = UserStatus.Normal;
96
97 gateway.Create<LocalUser>(newLocalUser);
98
99 Guid id = newLocalUser.ID;
100 AgentUser user = gateway.Find<AgentUser>(id);
101 gateway.Delete<AgentUser>(user);
102 Assert.IsNull(gateway.Find<User>(id));
103 Assert.IsNull(gateway.Find<AgentUser>(id));
104 Assert.IsNull(gateway.Find<LocalUser>(id));
105 }
106
107 private Guid CreateSampleData(DbTransaction tran)
108 {
109 //create local user
110 LocalUser newLocalUser = new LocalUser();
111 newLocalUser.ID = Guid.NewGuid();
112 newLocalUser.LoginName = newLocalUser.ID.ToString();
113 UserName name = new UserName();
114 name.FirstName = "first name of local user";
115 name.LastName = "last name of local user";
116 newLocalUser.Name = name;
117 newLocalUser.Password = "password";
118 newLocalUser.Status = UserStatus.Normal;
119
120 gateway.Create<LocalUser>(newLocalUser, tran);
121
122 //create user profile
123 UserProfile newUserProfile = new UserProfile();
124 newUserProfile.ContentXml = "sample content xml";
125 newUserProfile.UserID = newLocalUser.ID;
126
127 gateway.Create<UserProfile>(newUserProfile, tran);
128
129 //create group
130 Group newGroup = new Group();
131 newGroup.ID = Guid.NewGuid();
132 newGroup.IsPublic = true;
133 newGroup.Name = newGroup.ID.ToString();
134
135 gateway.Create<Group>(newGroup, tran);
136
137 //create domain
138 Domain newDomain = new Domain();
139 newDomain.Desc = "sample domain desc";
140 newDomain.ID = Guid.NewGuid();
141 newDomain.Name = "sample domain name";
142
143 gateway.Create<Domain>(newDomain, tran);
144
145 //create user group
146 UserGroup newUserGroup = new UserGroup();
147 newUserGroup.UserID = newLocalUser.ID;
148 newUserGroup.Weight = 10;
149 newUserGroup.GroupID = newGroup.ID;
150
151 gateway.Create<UserGroup>(newUserGroup);
152
153 //create agent user domain
154 AgentUserDomain newAgentUserDomain = new AgentUserDomain();
155 newAgentUserDomain.AgentUserID = newLocalUser.ID;
156 newAgentUserDomain.DomainID = newDomain.ID;
157
158 gateway.Create<AgentUserDomain>(newAgentUserDomain);
159
160 return newLocalUser.ID;
161 }
162
163 [TestMethod]
164 public void TestAsp20Transaction()
165 {
166 Guid id = default(Guid);
167 using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
168 {
169 id = CreateSampleData(null);
170
171 scope.Complete();
172 }
173
174 AgentUser user = gateway.Find<AgentUser>(id);
175 Assert.AreNotEqual(user.Domains[0], null);
176 Assert.AreNotEqual(user.Groups[0], null);
177 Assert.AreNotEqual(user.Profile, null);
178
179 gateway.Delete<User>(user);
180 Assert.IsNull(gateway.Find<LocalUser>(id));
181 Assert.IsNull(gateway.Find<UserProfile>(id));
182 }
183
184 [TestMethod]
185 public void TestAsp11Transaction()
186 {
187 Guid id = default(Guid);
188 DbTransaction tran = gateway.BeginTransaction();
189 try
190 {
191 id = CreateSampleData(tran);
192
193 tran.Commit();
194 }
195 catch
196 {
197 tran.Rollback();
198 }
199 finally
200 {
201 gateway.CloseTransaction(tran);
202 }
203
204 AgentUser user = gateway.Find<AgentUser>(id);
205 Assert.AreNotEqual(user.Domains[0], null);
206 Assert.AreNotEqual(user.Groups[0], null);
207 Assert.AreNotEqual(user.Profile, null);
208
209 gateway.Delete<User>(user);
210 Assert.IsNull(gateway.Find<LocalUser>(id));
211 Assert.IsNull(gateway.Find<UserProfile>(id));
212 }
213 }
214}
以下是11/1支持级联更新后的代码:
1using System;
2using System.Data.Common;
3using System.Text;
4using System.Transactions;
5using System.Collections.Generic;
6using Microsoft.VisualStudio.TestTools.UnitTesting;
7
8using Entities;
9using NBear.Common;
10using NBear.Data;
11
12using NBear.Test.CaseTests.shared;
13
14namespace NBear.Test.CaseTests
15{
16 [TestClass]
17 public class CaseTest
18 {
19 Additional test attributes#region Additional test attributes
20 //
21 // You can use the following additional attributes as you write your tests:
22 //
23 // Use ClassInitialize to run code before running the first test in the class
24 // [ClassInitialize()]
25 // public static void MyClassInitialize(TestContext testContext) { }
26 //
27 // Use ClassCleanup to run code after all tests in a class have run
28 // [ClassCleanup()]
29 // public static void MyClassCleanup() { }
30 //
31 // Use TestInitialize to run code before running each test
32
33 private Gateway gateway = null;
34
35 [TestInitialize()]
36 public void MyTestInitialize()
37 {
38 gateway = new Gateway("CaseTests");
39 gateway.RegisterSqlLogger(new LogHandler(Console.Write));
40 }
41
42 // Use TestCleanup to run code after each test has run
43 // [TestCleanup()]
44 // public void MyTestCleanup() { }
45 //
46 #endregion
47
48 [TestMethod]
49 public void TestCreate()
50 {
51 LocalUser newLocalUser = new LocalUser();
52 newLocalUser.ID = Guid.NewGuid();
53 newLocalUser.LoginName = newLocalUser.ID.ToString();
54 UserName name = new UserName();
55 name.FirstName = "first name of local user";
56 name.LastName = "last name of local user";
57 newLocalUser.Name = name;
58 newLocalUser.Password = "password";
59 newLocalUser.Status = UserStatus.Normal;
60
61 gateway.Save<LocalUser>(newLocalUser);
62 }
63
64 [TestMethod]
65 public void TestFind()
66 {
67 AgentUser[] users = gateway.FindArray<AgentUser>(WhereClip.All, OrderByClip.Default);
68 }
69
70 [TestMethod]
71 public void TestUpdate()
72 {
73 LocalUser user = gateway.FindArray<LocalUser>(WhereClip.All, LocalUser._.Password.Desc)[0];
74 user.Password = "12345";
75 UserName newName = new UserName();
76 newName.FirstName = "12345";
77 user.Name = newName;
78 gateway.Save<LocalUser>(user);
79 user = gateway.Find<LocalUser>(user.ID);
80 Assert.AreEqual(user.Password, "12345");
81 Assert.AreEqual(user.Name, newName);
82 }
83
84 [TestMethod]
85 public void TestDelete()
86 {
87 LocalUser newLocalUser = new LocalUser();
88 newLocalUser.ID = Guid.NewGuid();
89 newLocalUser.LoginName = newLocalUser.ID.ToString();
90 UserName name = new UserName();
91 name.FirstName = "first name of local user";
92 name.LastName = "last name of local user";
93 newLocalUser.Name = name;
94 newLocalUser.Password = "password";
95 newLocalUser.Status = UserStatus.Normal;
96
97 gateway.Save<LocalUser>(newLocalUser);
98
99 Guid id = newLocalUser.ID;
100 AgentUser user = gateway.Find<AgentUser>(id);
101 gateway.Delete<AgentUser>(user);
102 Assert.IsNull(gateway.Find<User>(id));
103 Assert.IsNull(gateway.Find<AgentUser>(id));
104 Assert.IsNull(gateway.Find<LocalUser>(id));
105 }
106
107 private Guid CreateSampleData(DbTransaction tran)
108 {
109 //create local user
110 LocalUser newLocalUser = new LocalUser();
111 newLocalUser.ID = Guid.NewGuid();
112 newLocalUser.LoginName = newLocalUser.ID.ToString();
113 UserName name = new UserName();
114 name.FirstName = "first name of local user";
115 name.LastName = "last name of local user";
116 newLocalUser.Name = name;
117 newLocalUser.Password = "password";
118 newLocalUser.Status = UserStatus.Normal;
119
120 //create user profile
121 UserProfile newUserProfile = new UserProfile();
122 newUserProfile.ContentXml = "sample content xml";
123 newUserProfile.UserID = newLocalUser.ID;
124
125 newLocalUser.Profile = newUserProfile;
126
127 //create group
128 Group newGroup = new Group();
129 newGroup.ID = Guid.NewGuid();
130 newGroup.IsPublic = true;
131 newGroup.Name = newGroup.ID.ToString();
132
133 newLocalUser.Groups = LocalUser.AddArrayItem<Group>(newLocalUser.Groups, newGroup);
134
135 //create domain
136 Domain newDomain = new Domain();
137 newDomain.Desc = "sample domain desc";
138 newDomain.ID = Guid.NewGuid();
139 newDomain.Name = "sample domain name";
140
141 newLocalUser.Domains = LocalUser.AddArrayItem<Domain>(newLocalUser.Domains, newDomain);
142
143 gateway.Save<LocalUser>(newLocalUser, tran);
144
145 return newLocalUser.ID;
146 }
147
148 [TestMethod]
149 public void TestAsp20Transaction()
150 {
151 Guid id = default(Guid);
152 using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
153 {
154 id = CreateSampleData(null);
155
156 scope.Complete();
157 }
158
159 AgentUser user = gateway.Find<AgentUser>(id);
160 Assert.AreNotEqual(user.Domains[0], null);
161 Assert.AreNotEqual(user.Groups[0], null);
162 Assert.AreNotEqual(user.Profile, null);
163
164 user.Status = UserStatus.Deleted;
165 user.Profile.ContentXml = "modified";
166
167 gateway.Save<AgentUser>(user);
168 AgentUser anotherThisUser = gateway.Find<AgentUser>(user.ID);
169 Assert.AreEqual(anotherThisUser.Status, user.Status);
170 Assert.AreEqual(anotherThisUser.Profile.ContentXml, user.Profile.ContentXml);
171
172 gateway.Delete<User>(user);
173 Assert.IsNull(gateway.Find<LocalUser>(id));
174 Assert.IsNull(gateway.Find<UserProfile>(id));
175 }
176
177 [TestMethod]
178 public void TestAsp11Transaction()
179 {
180 Guid id = default(Guid);
181 DbTransaction tran = gateway.BeginTransaction();
182 try
183 {
184 id = CreateSampleData(tran);
185
186 tran.Commit();
187 }
188 catch
189 {
190 tran.Rollback();
191 }
192 finally
193 {
194 gateway.CloseTransaction(tran);
195 }
196
197 AgentUser user = gateway.Find<AgentUser>(id);
198 Assert.AreNotEqual(user.Domains[0], null);
199 Assert.AreNotEqual(user.Groups[0], null);
200 Assert.AreNotEqual(user.Profile, null);
201
202 user.Status = UserStatus.Deleted;
203 user.Profile.ContentXml = "modified";
204
205 gateway.Save<AgentUser>(user);
206 AgentUser anotherThisUser = gateway.Find<AgentUser>(user.ID);
207 Assert.AreEqual(anotherThisUser.Status, user.Status);
208 Assert.AreEqual(anotherThisUser.Profile.ContentXml, user.Profile.ContentXml);
209
210 gateway.Delete<User>(user);
211 Assert.IsNull(gateway.Find<LocalUser>(id));
212 Assert.IsNull(gateway.Find<UserProfile>(id));
213 }
214 }
215}
注意比较CreateSampleData()和TestXXXTransaction()方法的代码。可以看到,支持属性级联更新后,操作实体及关联属性的代码极大简化了!
//本文结束