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数据字段。
完整的实体设计代码如下:
1
using System;2
using System.Collections.Generic;3
using System.Text;4

5
using NBear.Common.Design;6
using NBear.Test.CaseTests.shared;7

8
namespace NBear.Test.CaseTests.design9


{10
public interface User : Entity11

{12
[CompoundUnit, SqlType("ntext")]13
UserName Name14

{15
get;16
set;17
}18

19
UserStatus Status20

{21
get;22
set;23
}24

25
[PrimaryKey]26
Guid ID27

{28
get;29
set;30
}31

32
[Query(LazyLoad=false, Where="{UserID}=@ID"), Contained]33
UserProfile Profile34

{35
get;36
set;37
}38

39
[Query(LazyLoad = true, RelationType = typeof(UserGroup), Where = "{IsPublic}=1")]40
Group[] Groups41

{42
get;43
}44
}45

46
public interface Group : Entity47

{48
[PrimaryKey]49
Guid ID50

{51
get;52
set;53
}54

55
string Name56

{57
get;58
set;59
}60

61
bool IsPublic62

{63
get;64
set;65
}66
}67

68
public interface AgentUser : User69

{70
string LoginName71

{72
get;73
set;74
}75

76
[Query(LazyLoad = true, RelationType = typeof(AgentUserDomain))]77
Domain[] Domains78

{79
get;80
}81
}82

83
public interface LocalUser : AgentUser84

{85
string Password86

{87
get;88
set;89
}90
}91

92
public interface UserProfile : Entity93

{94
[PrimaryKey]95
Guid UserID96

{97
get;98
set;99
}100

101
string ContentXml102

{103
get;104
set;105
}106
}107

108
[Relation]109
public interface UserGroup : Entity110

{111
[RelationKey(typeof(User), "ID")]112
Guid UserID113

{114
get;115
set;116
}117

118
[RelationKey(typeof(Group), "ID")]119
Guid GroupID120

{121
get;122
set;123
}124

125
int Weight126

{127
get;128
set;129
}130
}131

132
[Relation]133
public interface AgentUserDomain : Entity134

{135
[RelationKey(typeof(AgentUser), "ID")]136
Guid AgentUserID137

{138
get;139
set;140
}141

142
[RelationKey(typeof(Domain), "ID")]143
Guid DomainID144

{145
get;146
set;147
}148
}149

150
public interface Domain : Entity151

{152
[PrimaryKey]153
Guid ID154

{155
get;156
set;157
}158

159
string Name160

{161
get;162
set;163
}164

165
string Desc166

{167
get;168
set;169
}170
}171
}
注意,所有的设计实体都使用接口表示,并使用必要的Attribute进行修饰。关于如何使用这些Attribute,近期我会有独立的文档描述,这里大家可以直观感受一下。每个属性的SqlType属性并不是必须的,如果不指定,则代码生成工具将会根据属性的.Net类型使用默认值,特别是对于数值类型,字符串类型建议自定义长度,否则默认为nvarchar(127)。
注意,这里列出的是设计实体代码,所有最终的实际实体(和用于设计的这些代码没有任何依赖来关系)、相关配置信息和数据库生成脚本都能够基于以上设计代码由NBear提供的工具自动生成。生成的具体的代码,我就不演示了。下面简单列举用于操作这些实体的测试代码,包括CRUD和Transaction(11/1更新支持实体属性级联更新,保留更改前的测试代码为黄色,更改后的代码用正常颜色表示)。
以下是支持属性级联更新前的测试代码:
1
using System;2
using System.Data.Common;3
using System.Text;4
using System.Transactions;5
using System.Collections.Generic;6
using Microsoft.VisualStudio.TestTools.UnitTesting;7

8
using Entities;9
using NBear.Common;10
using NBear.Data;11

12
using NBear.Test.CaseTests.shared;13

14
namespace NBear.Test.CaseTests15


{16
[TestClass]17
public class CaseTest18

{19

Additional test attributes#region Additional test attributes20
//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 class24
// [ClassInitialize()]25
// public static void MyClassInitialize(TestContext testContext) { }26
//27
// Use ClassCleanup to run code after all tests in a class have run28
// [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 run43
// [TestCleanup()]44
// public void MyTestCleanup() { }45
//46
#endregion47

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 user110
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 profile123
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 group130
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 domain138
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 group146
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 domain154
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
try190

{191
id = CreateSampleData(tran);192

193
tran.Commit();194
}195
catch196

{197
tran.Rollback();198
}199
finally200

{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支持级联更新后的代码:
using
System;2
using
System.Data.Common;3
using
System.Text;4
using
System.Transactions;5
using
System.Collections.Generic;6
using
Microsoft.VisualStudio.TestTools.UnitTesting;7

8
using
Entities;9
using
NBear.Common;10
using
NBear.Data;11

12
using
NBear.Test.CaseTests.shared;13

14
namespace
NBear.Test.CaseTests15

{16
[TestClass]17
public class CaseTest18

{19

Additional test attributes#region Additional test attributes20
//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 class24
// [ClassInitialize()]25
// public static void MyClassInitialize(TestContext testContext) { }26
//27
// Use ClassCleanup to run code after all tests in a class have run28
// [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 run43
// [TestCleanup()]44
// public void MyTestCleanup() { }45
//46
#endregion47

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 user110
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 profile121
UserProfile newUserProfile = new UserProfile();122
newUserProfile.ContentXml = "sample content xml";123
newUserProfile.UserID = newLocalUser.ID;124

125
newLocalUser.Profile = newUserProfile;126

127
//create group128
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 domain136
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
try183

{184
id = CreateSampleData(tran);185

186
tran.Commit();187
}188
catch189

{190
tran.Rollback();191
}192
finally193

{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()方法的代码。可以看到,支持属性级联更新后,操作实体及关联属性的代码极大简化了!
//本文结束

NBear V3全新版本发布,支持实体属性级联更新、复杂关联及透明的增删改查操作。提供代码生成工具,简化开发流程。

被折叠的 条评论
为什么被折叠?



