• 构造我自己的ORM


    GPS平台、网站建设、软件开发、系统运维,找森大网络科技!
    http://cnsendnet.taobao.com
    来自森大科技官方博客
    http://www.cnsendblog.com/index.php/?p=495

    通过前面两章的描述,我相信很多朋友都已经明白我了下面将要讨论到的ORM的实现方法了,那就是根据自定义Attribute来定义O/R Mapping规则,然后通过反射来动态获取此规则,动态构造SQL语句。
    由于这个小东西(ORM)出生在深圳,所以我想来想去,她应该有个深圳的名字,所以我就叫她“MiniORM”。不知道各位认为如何?
    MiniORM采用的是ONE_INHERIT_TREE_ONE_CLASS(一个继承树对应于一个表)的结构,虽然这种结构容易导致数据冗余,但是这种结构很简单。另,本MiniORM 仅仅考虑一个表一个PK,一个FK的情况。

    MiniORM结构如下,为了更便于理解和使用,我使用了3个类:
    1、OrmWriter:负责将实体对象(比如前面章节说的Person)插入数据库和修改数据库中对应的记录。
    2、OrmRemover:负责根据实体对象,删除指定的记录;
    3、OrmReader:负责根据实体对象,读取指定的记录;

    上面就是MiniORM的3个主要类。下面我们就详细地根据前面的描述一步步构造她。我们这里还是以前面说的Person为例进行说明。

    通过本系列第一章,我们知道,对象不但存在继承关系,特别在实际的应用中还存在包含关系,比如一个Person包含两个Hand(手)类,包含一个Head(头)类等,我们的Person在数据库中应该有一个ID,为了更加方便使用和讨论,此ID在MiniORM中是一个int以及自动增长类型(ID INDENTITY(1,1))。这些都是我们的MiniORM应该考虑的范围。
    我们对我们的Person做修改:

    1. [DataObjectAttribute("Person")]
    2. public class Person
    3. {
    4. private int _ID;
    5. private string _Name;
    6. private int _Age;
    7. private string _Sex;
    8. private Head _Head;
    9. private Hand _LeftHand;
    10. private Hand _RightHand;
    11.  
    12. public int ID
    13. {
    14. get { return _ID; }
    15. set { _ID = value; }
    16. }
    17.  
    18. public Head Head
    19. {
    20. get { return _Head; }
    21. set { _Head = value; }
    22. }
    23.  
    24. public Hand LeftHand
    25. {
    26. get { return _LeftHand; }
    27. set { _LeftHand = value; }
    28. }
    29.  
    30. public Hand RightHand
    31. {
    32. get { return _RightHand; }
    33. set { _RightHand = value; }
    34. }
    35.  
    36. [DataFieldAttribute("name", "NvarChar")]
    37. public string Name
    38. {
    39. get { return this._Name; }
    40. set { this._Name = value; }
    41. }
    42.  
    43. [DataFieldAttribute("age", "int")]
    44. public int Age
    45. {
    46. get { return this._Age; }
    47. set { this._Age = value; }
    48. }
    49.  
    50. [DataFieldAttribute("sex", "NvarChar")]
    51. public string Sex
    52. {
    53. get { return this._Sex; }
    54. set { this._Sex = value; }
    55. }
    56. }

    你可能又发现了一个问题,就是在我们修改后的Person中,增加了LeftHand,RightHand以及Head,可是这三个都属于类啊,这个怎么能够保存到数据库中呢?并且使用我们前面的DataFieldAttribute是没有办法描述的啊。另外还增加了个ID,又怎么来标志这个是自动增长的int型PK呢?当然了能够到这里你就发现这些问题那是相当的不错了。如果前面就动手的人,估计考虑的还是修改我们的DataFieldAttribute让它能够对这些东西进行区别。比如在DataFieldAttribute中再增加一个属性用于区别哪个是ID属性,哪个是对象类型(比如Hand)属性。这当然是好的,只不过这样做导致我们的代码极其丑陋。最好的办法还是另外增加一个Attribute。当然了,我是为了更加方便的构造SQL语句,我做的不是很好。

    1、怎么表示实体类对应的数据库表的PK和FK?
    为了更方便的实现,MiniORM中标志一个实体类的PK和FK都是在DataObjectAttribute中来做(其实最好的办法还是另外增加个比如PKAttribute和FKAttribute,不过这个留给其它人去做吧)。如下,DataObjectAttribute第一个参数表示对应的数据库表,第二个参数表示PK,第三个参数表示FK:

    1. [DataObjectAttribute("Person", "ID", "")]
    2. public class Person
    3. {
    4. ......
    5. }

    2、怎么标志字段是Indentity(自动增长)?
    在DataFieldAttribute中增加了个属性,用于标志某个字段是否自动增长的字段。这些都是我个人懒做的,其中,第二个参数标志ID是Identity类型

    1. [DataFieldAttribute("ID", true)]
    2. public int ID
    3. {
    4. get { return _ID; }
    5. set { _ID = value; }
    6. }

    3、怎样标志字段是类对象(比如Person中的Hand,当然复杂点的对象,可能包含子对象列表)?
    由于MiniORM提供的是一个类似框架的东西,所以不应该受到实体类的限制,所以对于类对象字段,我们应该能够描述此对象所在的程序集,命名空间,类名,这样我们才可以运行时创建该对象。

    1. public class SubDataObjectAttribute : Attribute
    2. {
    3. private SubDataObjectFieldType _FieldType;
    4. private string _AssemblyName;
    5. private string _NamespaceName;
    6. private string _ClassName;
    7.  
    8. public SubDataObjectAttribute(SubDataObjectFieldType fieldtype, string assemblyname, string namespacename, string classname)
    9. {
    10. this._FieldType = fieldtype;
    11. this._AssemblyName = assemblyname;
    12. this._NamespaceName = namespacename;
    13. this._ClassName = classname;
    14. }
    15.  
    16. /// <summary>
    17. /// 本记录对应的FieldType
    18. /// </summary>
    19. public SubDataObjectFieldType FieldType
    20. {
    21. get { return _FieldType; }
    22. }
    23.  
    24. /// <summary>
    25. /// 本记录对应的AssemblyName
    26. /// </summary>
    27. public string AssemblyName
    28. {
    29. get { return _AssemblyName; }
    30. }
    31.  
    32. /// <summary>
    33. /// 本记录对应的NamespaceName
    34. /// </summary>
    35. public string NamespaceName
    36. {
    37. get { return _NamespaceName; }
    38. }
    39.  
    40. /// <summary>
    41. /// 本记录对应的ClassName
    42. /// </summary>
    43. public string ClassName
    44. {
    45. get { return _ClassName; }
    46. }
    47. }

    其中SubDataObjectFieldType是一个枚举类型,因为我们的子对象可能是单独的对象比如Person.Head也可能是一个列表(List)。所以我增加了这个枚举类型,用于做标志。

    1. public enum SubDataObjectFieldType
    2. {
    3. Object,
    4. /// <summary>
    5. /// 本字段属于List类型,直接遍历
    6. /// </summary>
    7. List,
    8. }

    当然了,这里的子对象列表可能是ArrayList,HashTable等等,你都可以根据自己项目中实际使用到的类型来做相应的修改。

    4、怎么控制某个字段在表中不能重复?
    比如我们要控制Person.Name不能重复,如果你新增的时候发现重复要提示。那我们也通过增加一个Attribute的形式来实现。这个Attribute很简单,没有任何方法和属性。

    1. public class DataFieldNotDoubleAttribute : Attribute
    2. {
    3. }

    5、怎样做事务处理?
    事务处理是每个底层框架都应该考虑到的问题,在.NET中我们有两种方式来进行事务处理,一种是使用COM+,这是最好的方法,不过性能上比较欠缺,另外这东西配置很麻烦,当你数据库安装在另外一太服务器上的时候,往往出现无法使用的问题,我曾经就被这东西折腾够呛,所以我干脆就不用他了,不过还是介绍下语法,通过使用TransactionScope就可以很好的使用com+提供的事务处理,代码相当的简洁,优美,只可惜啊!天使的面孔,魔鬼的心。

    1. public void function1()
    2. {
    3. using (System.Transactions.TransactionScope scope = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Required))
    4. {
    5. function2();
    6. }
    7. }
    8.  
    9. public void function2()
    10. {
    11. using (System.Transactions.TransactionScope scope = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Required))
    12. {
    13. //DoSomething();
    14. }
    15. }

    另外一种方法就是使用SqlTransaction:

    1. using (SqlConnection conn = new SqlConnection(ConnectionStr))
    2. {
    3. conn.Open();
    4. SqlTransaction trans = conn.BeginTransaction();
    5. //DoSomething();
    6. trans.Commit();
    7. }

    不过遗憾的是这种方式不能实现事务嵌套,所以只能通过将trans作为参数进行传递来实现事务处理。

    经过上面一系列的修改后,我们的Person成了什么样子了?

    1. [DataObjectAttribute("Person", "ID", "")]
    2. public class Person
    3. {
    4. private int _ID;
    5. private string _Name;
    6. private int _Age;
    7. private string _Sex;
    8. private Head _Head;
    9. private Hand _LeftHand;
    10. private Hand _RightHand;
    11.  
    12. [DataFieldAttribute("ID", true)]
    13. public int ID
    14. {
    15. get { return _ID; }
    16. set { _ID = value; }
    17. }
    18.  
    19. [SubDataObjectAttribute(SubDataObjectFieldType.Object, "Person", "Person", "Head")]
    20. public Head Head
    21. {
    22. get { return _Head; }
    23. set { _Head = value; }
    24. }
    25.  
    26. [SubDataObjectAttribute(SubDataObjectFieldType.Object, "Person", "Person", "Hand")]
    27. public Hand LeftHand
    28. {
    29. get { return _LeftHand; }
    30. set { _LeftHand = value; }
    31. }
    32.  
    33. [SubDataObjectAttribute(SubDataObjectFieldType.Object, "Person", "Person", "Hand")]
    34. public Hand RightHand
    35. {
    36. get { return _RightHand; }
    37. set { _RightHand = value; }
    38. }
    39.  
    40. [DataFieldAttribute("name", "NvarChar")]
    41. public string Name
    42. {
    43. get { return this._Name; }
    44. set { this._Name = value; }
    45. }
    46.  
    47. [DataFieldAttribute("age", "int")]
    48. public int Age
    49. {
    50. get { return this._Age; }
    51. set { this._Age = value; }
    52. }
    53.  
    54. [DataFieldAttribute("sex", "NvarChar")]
    55. public string Sex
    56. {
    57. get { return this._Sex; }
    58. set { this._Sex = value; }
    59. }
    60. }
    61.  
    62.  

    当然了对于Person这样的实体类,我们完全可以自己写代码自动生成工具来弄,然后再做很小的修改就可以了,这样的工具实现简单,我就不讨论了。
    好了,关于我的MiniORM我就讨论到这里了,其它的请看代码吧。

    ORM虽然是好东西,但是也存在很多方面的不足,首先我们能够做到的是将大部分的数据库操作交个ORM来做。另外少部分还是需要我们自己写SQL的。单大部分的工作的分离可以为我们节约大量的时间(也就是所谓的20/80原则,80%的教给ORM来处理,20%的自己做,当然很好了)。另外通过将这些相同的流程教给ORM来处理,可以避免很多的疏忽导致的失误(比如不小心把某个Insert,Update,Delete语句弄错了什么的)。
    最主要的缺点当然是性能问题,特别是我的MiniORM,全部采用反射来获取映射规则,从而导致性能上更多的下降,不过我们了解方法以后是很容易通过动态生成代码,动态编译的方式来减少这部分的性能损失的。另外某些部分的代码显得有些臃肿,特别是把判断是否Indentity这样的代码放DataFieldAttribute中来处理(这个完全可以象DataFieldNotDoubleAttribute一分开处理的样)等等。

    GPS平台、网站建设、软件开发、系统运维,找森大网络科技!
    http://cnsendnet.taobao.com
    来自森大科技官方博客
    http://www.cnsendblog.com/index.php/?p=495

  • 相关阅读:
    (C#)asp_net调试错误解决方法收集(1)
    asp.net调试技巧
    asp。Net 页面传值
    viewState详解
    Session,ViewState用法
    asp.net中通过form表单submit提交到后台的实例
    C#读写xml文件
    Asp.Net前台调用后台变量
    3. mybatis # 与 $ 的区别
    IDEA 中 使用 git(Git)
  • 原文地址:https://www.cnblogs.com/cnsend/p/12649838.html
Copyright © 2020-2023  润新知