信不信由你:
谁都可以写出机器能执行的代码,但不是谁都写得出其他程序员也看得懂的代码.(流醒鱼的感慨)
任何一个工具都很难被使用者真正掌握,工具越强大,配置越复杂--复杂性从code变成config
序:
信不信由你:
谁都可以写出机器能执行的代码,但不是谁都写得出其他程序员也看得懂的代码.(流醒鱼的感慨)
任何一个工具都很难被使用者真正掌握,工具越强大,配置越复杂,复杂性从code变成config
有时自己觉得真的不年轻了,怎么也多少有些唠叨了?
一)问题描述
最近一直在尝试几个ORM框架,我感觉他们都不错的,只要你真正掌握,对付一般小Mis系统应该都不成问题.
几乎每个工具都有现成的例子,但是这些例子往往过于简单.请看Hibernate的例子:
Public class Blog
{
...
public IList Posts;
}
Public class Post
{
...
public Blog owner;
}
这个例子本来也没有什么问题,只是很多人依葫芦画瓢就搞起了商业系统,看到他们的一些代码,实在是能预感到维护阶段的一幕幕。
二)避免问题的一点经验
经过自己看别人的程序感触,我把这些要点记下来,大约如下:
1) 逻辑变化和扩展要对实体类关闭频繁修改
比如有个User类,那我们做人事系统时可能有TimeCard;而我们做项目管理系统时又可能有Task等
2) 实体和逻辑类必须在完全没有数据库支持的情况下测试通过,证明自己的清白
当数据库操作一上来后,会发生大量异常,千万不要把宝贵的时间放在联调上,分布式跟踪代价“高,实在是高”!
所以我通常用下面的图来提醒自己:
业务实体、业务逻辑
---------------实体关系------------
数据操作
可以看到实体关系处在一个灰色地带,放到那里可以根据具体情况决定
三)一个较为清晰的实例
为了突出思路和要点,下面将采用Teddy的ORM框架来解释,其他的类似,主要调整一下数据访问层。
3.1)业务背景
每个应用都重复着组、用户的这些逻辑,用这个例子大家一定不会陌生
规则1:一个组可以再包含下面的组,但是系统只有一个最顶层的组
规则2:一个组可能有多个用户(也可能没有)
规则3:一个用户至少一个组
下面的例子主要针对规则2。
3.2)代码演示
首先定义数据实体
注意,vUsersOfGroup是一个视图,只是改进效率,原则上不应该出现在实体类中
为了能够表达规则2,建立下面的实体关系类
using System;
using System.Data;
using Ilungasoft.Framework.Common;
namespace Teddy.ORM.Test
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](/Images/OutliningIndicators/ContractedBlock.gif)
{
public abstract class Entity
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
public interface Group : IEntity
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
int ID
{ get; set; }
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
string Title
{ get; set; }
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
string Description
{ get; set; }
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
System.DateTime CreateTime
{ get; set; }
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
int ParentID
{ get; set; }
}
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
public interface User : IEntity
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
int ID
{ get; set; }
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
string Name
{ get; set; }
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
bool Gender
{ get; set; }
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
double Salary
{ get; set; }
}
public interface vUsersOfGroup : IEntity
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
int ID
{ get; set; }
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
string Name
{ get; set; }
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
bool Gender
{ get; set; }
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
double Salary
{ get; set; }
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
int GroupID
{ get; set; }
}
}
}
![](/Images/OutliningIndicators/None.gif)
public class MemberShip
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
public Entity.Group Group;
public Entity.User Administrator;
public Entity.User[] Members;
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
public MemberShip()
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
}
public MemberShip(Entity.Group g,Entity.User u,Entity.User[] members)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
Group = g;
Administrator = u;
Members = members;
![](/Images/OutliningIndicators/InBlock.gif)
}
}
关系数据的操作类也比较简单
using System;
using System.Data;
![](/Images/OutliningIndicators/None.gif)
using Ilungasoft.Framework.Common;
using Ilungasoft.Framework.Data.Facade;
using Teddy.ORM;
namespace Teddy.ORM.Test
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](/Images/OutliningIndicators/ContractedBlock.gif)
{
public class MemberShipLoader
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
private Gateway _gw;
![](/Images/OutliningIndicators/InBlock.gif)
public MemberShipLoader(Gateway gw)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
_gw = gw;
}
// todo 如果框架中使用反射自动做这类事就好了!
public Entity.User clone(Entity.vUsersOfGroup vUser)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
Entity.User u = EntityFactory<Entity.User>.CreateObject();
u.ID = vUser.ID;
u.Name = vUser.Name;
u.Salary = vUser.Salary;
u.Gender = vUser.Gender;
return u;
}
public MemberShip getMemberShip(Entity.Group group)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
Entity.vUsersOfGroup[] temp_users = _gw.Select<Entity.vUsersOfGroup>("GroupID=" + group.ID);
Entity.User[] us = new Entity.User[temp_users.Length];
int i = 0;
foreach (Entity.vUsersOfGroup t in temp_users)
us[i++] = clone(t);
return new MemberShip(group, us[0], us);
![](/Images/OutliningIndicators/InBlock.gif)
}
![](/Images/OutliningIndicators/InBlock.gif)
}
}
![](/Images/OutliningIndicators/None.gif)
最后就是证明自己的清白了:
using System;
using System.Data;
using System.Collections;
using NUnit.Framework;
using Ilungasoft.Framework.Common;
using Ilungasoft.Framework.Data;
using Ilungasoft.Framework.Data.Facade;
using Ilungasoft.Framework.Data.MsAccess;
using Teddy.ORM;
[TestFixture]
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
public class Tester
{
[SetUp]
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public void Init()
{
}
[TearDown]
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public void Finish()
{
}
[Test]
public void testSimpleDataAccess()
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
![](/Images/OutliningIndicators/InBlock.gif)
string connstr = @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=E:\SORM\App_Data\TestRelation.mdb";
Database db = new Database(new AccessDbProvider(connstr));
Gateway gw = new Gateway(db);
MemberShipLoader msl = new MemberShipLoader(gw);
![](/Images/OutliningIndicators/InBlock.gif)
Entity.Group g = EntityFactory<Entity.Group>.CreateObject();
g.ID = 1; g.Description ="Demo 公司";
MemberShip ms = msl.getMemberShip(g);
Console.WriteLine("Group:" + ms.Group.Description);
Console.WriteLine("Administrator:"+ms.Administrator.Name);
Console.WriteLine("====Members======");
Assert.AreEqual(3, ms.Members.Length);
foreach (Entity.User u in ms.Members) Console.WriteLine(u.Name);
}
}
我机器上的结果如下:
Group:Demo 公司
Administrator:Alex
====Members======
Alex
cxiong
yxiuquan
四)小结
4.1 先梳理出清晰的概念,然后再编码,选择合适的工具。
4.2 因为时间和篇幅,我并没有按照自己的要求在没有数据联入时进行单元测试,因为这个例子太过于简单,所以万事不要绝对。
4.3 Teddy的框架如果能够方便吧Entity导出到XML今后就更加实用,不知何时能看到。
4.4 Teddy的框架能加上MySQL和Oracle的DbProvider就会更加实用了。
Alex 4-6