• .NET ORM 的 “SOD蜜”--零基础入门篇


    PDF.NET SOD框架不仅仅是一个ORM,但是它的ORM功能是独具特色的,我在博客中已经多次介绍,但都是原理性的,可能不少初学的朋友还是觉得复杂,其实,SOD的ORM是很简单的。下面我们就采用流行的 Code First的方式,一步步来了解下。

    一、准备工作

    1.1,添加SOD包引用

    首先建立一个控制台项目(支持.NET2.0的项目),并使用程序包管理器添加PDF.NET SOD的程序引用:

    PM> Install-Package PDF.NET

    更多详细使用信息说明,请参考nuget 网站说明 https://www.nuget.org/packages/PDF.NET/

    1.2,配置数据连接

    新建一个控制台项目,添加一个应用程序配置文件,增加一个数据库连接配置:

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
        <connectionStrings>
            <add name="local" connectionString="Data Source=.;Initial Catalog=LocalDB;Integrated Security=True" providerName="SqlServer" />
        </connectionStrings>
    </configuration>

    上面的连接字符串要求你本地已经安装SqlServer,框架兼容2000以上所有版本,有一个数据库名字是 LocalDB。当然你也可以修改成你实际的连接字符串。

    之后,我们的查询示例,都将采用这个连接配置。

    注意:最新版本的SOD框架,如果使用的是SqlServer,并且连接字符串指定了数据库名字但实际上没有这个数据库,框架可以自动创建数据库,此功能需要SOD的Code First功能支持,请参考下面“1.5 ,创建数据库表”的功能。

    SOD框架最基本的配置,仅需要这一个地方,这比起EF来说要简单。

    如果是SqlServer +EF Code First方式的连接配置,SOD框架也可以使用它这个连接字符串的。

    1.3,定义实体类

    我们将使用最常见的用户登录来举例,这里需要一个用户实体类,假设它的定义是下面这个样子的:

    public class User : EntityBase
        {
            public User()
            {
                TableName="Tb_User";
                IdentityName = "UserID";
                PrimaryKeys.Add("UserID");
            }
    
            public int ID
            {
                get { return getProperty<int>("UserID"); }
                set { setProperty("UserID", value); }
            }
    
            public string Name
            {
                get { return getProperty<string>("Name"); }
                set { setProperty("Name", value, 50); }
            }
    
            public string Pwd
            {
                get { return getProperty<string>("Pwd"); }
                set { setProperty("Pwd", value, 50); }
            }
    
            public DateTime RegistedDate
            {
                get { return getProperty<DateTime>("RegistedDate"); }
                set { setProperty("RegistedDate", value); }
            }
    
        }
    View Code

    在上面的定义中,构造函数内指明了 IdentityName = "UserID";这表示当前实体类对应的表有一个叫做 UserID的自增列,每当插入实体类后,可以通过该自增列对应的属性获取到新插入的自增ID的值。

    另外,我们发现每个实体类的属性中,都是这样的写法:

        public int ID
            {
                get { return getProperty<int>("UserID"); }
                set { setProperty("UserID", value); }
            }

    属性内 getProperty ,setProperty 方法内的第一个参数,就是属性对应的字段名字。 所以,这样的属性,SOD框架称为“持久化属性”。

    可以看到,SOD实体类还是比较简单的,它没有使用特性来申明数据库信息,这意味着你可以在运行时修改实体类影射的主键,自增字段,表名称等数据库元数据,并且不需要反射,这些特性构成了SOD框架简单而强大的基础。

    1.4,添加查询对象的数据上下文

    在项目中添加一个 LocalDbContext.cs 文件,文件中添加如下代码,以便检查表 Tb_User 是否存在,如果不存在,则自动创建一个:

    复制代码
    /// <summary>
     /// 用来测试的本地SqlServer 数据库上下文类
     /// </summary>
      public class LocalDbContext : DbContext
      {
          public LocalDbContext()
              : base("local") 
          {
              //local 是连接字符串名字
          }
    
          #region 父类抽象方法的实现
    
          protected override bool CheckAllTableExists()
          {
              //创建用户表
              CheckTableExists<User>();
              return true;
          }
    
          #endregion
       }
    复制代码

    本例中使用SqlServer 作为查询示例,程序会自动使用 SqlServerDbContext 密封类来进行内部协作处理,但这取决于你的AdoHelper实例类型,如果是Oracle,则内部使用的是OracleDbContext 密封类。

    如果你不需要ORM的 Code First功能,这个 DbContext 实现类是不需要写的。

    1.5,创建数据库表

    OK,基本准备就绪,现在Main 方法里面可以写下面的代码开始工作了:

    LocalDbContext context = new LocalDbContext();//自动创建表

    准备工作全部完成,运行这一句代码,之后查看SqlServer管理工具,发现表 Tb_User 已经创建了。

    二、ORM之增,删,改

    SOD框架的ORM功能跟通常的ORM框架不同,SOD框架的实体类上并没有数据查询和持久化的方法,所以SOD的实体类是“非常纯粹的”实体类,你可以把它看作是一个数据容器,或者用来当作DTO,ViewModel使用,有关这个话题更详细的阐述,请看这篇文章:《DataSet的灵活,实体类的方便,DTO的效率:SOD框架的数据容器,打造最适合DDD的ORM框架》。

    在进行真正的数据查询之前,得先有数据,所以我们先测试数据的增删改。

    2.1,删除数据

    首先删除之前的测试数据,可以采用OQL进行批量数据删除:

    复制代码
    int count = 0;
    
    //删除 测试数据-----------------------------------------------------
    User user = new User();
    OQL deleteQ = OQL.From(user)
                    .Delete()
                    .Where(cmp => cmp.Comparer(user.ID, ">", 0)) //为了安全,不带Where条件是不会全部删除数据的
                 .END;
    count= EntityQuery<User>.ExecuteOql(deleteQ, context.CurrentDataBase);
    复制代码

    这里将用户ID 大于0的数据全部删除了,框架内置了数据安全机制,不会随意删除全部数据,所以为了清除全部数据,采用了上面的方法。

    2.2,增加数据

    方式1,采用DbContext 的Add 方法:

                count = 0;
                User zhang_san = new User() { Name = "zhang san", Pwd = "123" };
                count += context.Add<User>(zhang_san);//采用 DbContext 方式插入数据

    方式2,采用OQL:

                User li_si = new User() { Name = "li si", Pwd = "123" };
                OQL insertQ= OQL.From(li_si)
                                .Insert(li_si.Name);//仅仅插入用户名,不插入密码

    OQL还需EntityQuery 对象配合,才可以执行,继续看下面的代码:

     AdoHelper db = MyDB.GetDBHelperByConnectionName("local");
     EntityQuery<User> userQuery = new EntityQuery<User>(db);
     count += userQuery.ExecuteOql(insertQ);

    下面是结果图:

    方式3,直接EntityQuery 插入实体类:

     User zhang_yeye = new User() { Name = "zhang yeye", Pwd = "456" };
     count += EntityQuery<User>.Instance.Insert(zhang_yeye);//采用泛型 EntityQuery 方式插入数据

    2.3,修改数据:

    方式1,采用 DbContext 方式更新数据

     li_si.Pwd = "123123";
     count = context.Update<User>(li_si);//采用 DbContext 方式更新数据

    方式2,采用OQL方式更新指定的数据

     li_si.Pwd = "123456";
    OQL updateQ= OQL.From(li_si)
                    .Update(li_si.Pwd) //仅仅更新密码
                 .END;
    count += EntityQuery<User>.Instance.ExecuteOql(updateQ);//采用OQL方式更新指定的数据

    方式3,采用泛型 EntityQuery 方式修改数据

      li_si.Pwd = "888888";
      count += EntityQuery<User>.Instance.Update(li_si);//采用泛型 EntityQuery 方式修改数据

     三、ORM之数据查询

    前面增删改数据完成了,现在有了测试数据,我们可以来进行ORM的数据查询测试了,这里使用用户登录的例子来测试,框架提供了6种数据查询方式。

     3.1,最简单的方法

    假设前端直接传递了一个 User 实体类对象,中间设置了用户名和密码,现在有一个登录方法使用该对象,该方法详细内容如下所示:

    复制代码
           /// <summary>
            /// 使用用户对象来登录,OQL最简单最常见的使用方式
            /// </summary>
            /// <param name="user"></param>
            /// <returns></returns>
            public bool Login(User user)
            {
                OQL q = OQL.From(user)
                    .Select()
                    .Where(user.Name, user.Pwd) //以用户名和密码来验证登录
                .END;
    
                User dbUser =q.ToEntity<User>();//ToEntity,OQL扩展方法 
                return dbUser != null; //查询到用户实体类,表示登录成功
            }
    复制代码

    这里我们使用了SOD框架的ORM查询语言--OQL,它的结构非常类似于SQL,你可以认为OQL就是对象化的SQL语句。

    OQL表达式采用 .From.....END 的语法,对象的链式方法调用,只要敲点出来的就是正确的,这样没有SQL基础的同学也可以很快掌握该查询语法,也能马上作数据开发。

    注意:在本例中,使用了OQL的扩展方法,因此需要引用下面的名字空间:

    using PWMIS.Core.Extensions;

    如果不使用扩展方法,可以采用泛型EntityQuery 的方法,请看下面的示例。

    3.2,使用 OQLCompare 对象的 EqualValue 相等比较方式

    复制代码
            /// <summary>
            /// 使用用户对象来登录,但是使用 OQLCompare 对象的 EqualValue 相等比较方式 
            /// </summary>
            /// <param name="user"></param>
            /// <returns></returns>
            public bool Login1(User user)
            {
                OQL q = OQL.From(user)
                     .Select(user.ID) //仅查询一个属性字段 ID
                     .Where(cmp => cmp.EqualValue(user.Name) & cmp.EqualValue(user.Pwd))
                  .END;
    
                User dbUser = EntityQuery<User>.QueryObject(q);
                return dbUser != null; //查询到用户实体类,表示登录成功
            }
    复制代码

    跟例1一样,这里也要求user 对象的Name和Pwd属性必须事先有值。本例没有使用OQL的扩展方法。

    3.3, EntityQuery 泛型查询方法

    本例只是对例子1做了下改进,重点在于登录方法的参数不是用户对象,而是名字和密码参数。

    复制代码
           /// <summary>
            /// 使用用户名密码参数来登录,采用 EntityQuery 泛型查询方法
            /// </summary>
            /// <param name="name"></param>
            /// <param name="pwd"></param>
            /// <returns></returns>
            public bool Login2(string name, string pwd)
            {
                User user = new User()
                {
                    Name = name,
                    Pwd = pwd
                };
    
                OQL q = OQL.From(user)
                    .Select(user.ID)
                    .Where(user.Name, user.Pwd)
                .END;
                User dbUser = EntityQuery<User>.QueryObject(q);
    
                return dbUser != null; //查询到用户实体类,表示登录成功
            }
    复制代码

    3.4,使用OQLConditon 对象为查询条件

    复制代码
           /// <summary>
            /// 使用用户名密码参数来登录,使用早期的实例化OQL对象的方式,并使用OQLConditon 对象为查询条件
            /// </summary>
            /// <param name="name"></param>
            /// <param name="pwd"></param>
            /// <returns></returns>
            public bool Login3(string name, string pwd)
            {
                User user = new User();
                OQL q = new OQL(user);
                q.Select(user.ID).Where(q.Condition.AND(user.Name, "=", name).AND(user.Pwd, "=", pwd));
    
                User dbUser = EntityQuery<User>.QueryObject(q);
                return dbUser != null; //查询到用户实体类,表示登录成功
            }
    复制代码

    这是OQL早期的条件查询方式,缺点是没法构造复杂的查询条件。

     3.5,操作符重载

    OQLCompare 的操作符重载可以简化比较条件,如下所示:

    复制代码
           /// <summary>
            /// 使用用户名密码参数来登录,并使用操作符重载的查询条件比较方式
            /// </summary>
            /// <param name="name"></param>
            /// <param name="pwd"></param>
            /// <returns></returns>
            public bool Login4(string name, string pwd)
            {
                User user = new User();
    
                OQL q = OQL.From(user)
                      .Select()
                      .Where( cmp => cmp.Property(user.Name) == name 
                                   & cmp.Property(user.Pwd)  == pwd  )
                   .END;
    
                User dbUser = EntityQuery<User>.QueryObject(q);
                return dbUser != null; //查询到用户实体类,表示登录成功
            }
    复制代码

     3.6,使用泛型OQL查询(GOQL)

    使用泛型OQL查询(GOQL),对于单实体类查询最简单的使用方式,缺点是不能进行“连表查询”,即多个实体类联合查询。

    复制代码
            /// <summary>
            /// 使用用户名密码参数来登录,使用泛型OQL查询(GOQL),对于单实体类查询最简单的使用方式。
            /// </summary>
            /// <param name="name"></param>
            /// <param name="pwd"></param>
            /// <returns></returns>
            public bool Login5(string name, string pwd)
            {
                User dbUser = OQL.From<User>()
                     .Select()
                     .Where((cmp, user) => cmp.Property(user.Name) == name 
                                         & cmp.Property(user.Pwd)  == pwd  )
                .END
                .ToObject();
    
                return dbUser != null; //查询到用户实体类,表示登录成功
            }
    复制代码

    3.7,使用实体类主键来查询

    SOD实体类的“主键”字段是可以修改的,这样你可以随时修改它,就像实体类本来的主键一样,用它来填充数据,本例就是判断是否填充成功当前实体类来判断用户是否可以登录。

    复制代码
           /// <summary>
            /// 使用用户名密码参数来登录,但是根据实体类的主键来填充实体类并判断是否成功。
            /// </summary>
            /// <param name="name"></param>
            /// <param name="pwd"></param>
            /// <returns></returns>
            public bool Login6(string name, string pwd)
            {
                User user = new User();
                user.PrimaryKeys.Clear();
                user.PrimaryKeys.Add("Name");
                user.PrimaryKeys.Add("Pwd");
    
                user.Name = name;
                user.Pwd = pwd;
                bool result= EntityQuery<User>.Fill(user);//静态方法,使用默认的连接对象
                return result;
            }
    复制代码

    3.8,查询多条数据

    前面的例子都只是查询一条数据,如果需要查询多条数据也很简单,参见下面的例子,如何查询所有姓 zhang 的用户:

    复制代码
            /// <summary>
            /// 模糊查询用户,返回用户列表,使用OQLCompare 委托
            /// </summary>
            /// <param name="likeName">要匹配的用户名</param>
            /// <returns>用户列表</returns>
            public List<User> FuzzyQueryUser(string likeName)
            {
                User user = new User();
                OQL q = OQL.From(user)
                  .Select()
                  .Where(cmp => cmp.Comparer(user.Name, "like", likeName+"%") )
                .END;
    
                List<User> users = EntityQuery<User>.QueryList(q);
                return users;
            }
    复制代码

    前端调用:

    //查询列表
    var users=service.FuzzyQueryUser("zhang");
    Console.WriteLine("模糊查询姓 张 的用户,数量:{0}",users.Count );

    所以,查询多条数据,仅需要使用泛型 EntityQuery对象的QueryList 方法即可。同样,框架也为你提供了OQL对象扩展方法来直接查询列表数据。

    3.9,实体类联合查询

    这里不再举例,我的博客文章也多次说明过,请参考下面这个系列文章:

    ORM查询语言(OQL)简介--高级篇(续):庐山真貌 深蓝医生 2013-07-30 16:54 阅读:4497 评论:41  
     
    ORM查询语言(OQL)简介--高级篇:脱胎换骨 深蓝医生 2013-07-26 17:26 阅读:3274 评论:28  
     
    ORM查询语言(OQL)简介--实例篇 深蓝医生 2013-04-01 14:56 阅读:5108 评论:16  
     
    ORM查询语言(OQL)简介--概念篇 深蓝医生 2012-10-06 00:58 阅读:4657 评论:25  
     

    四、SOD框架的设计原则

     SOD框架的整个设计都采用了“条条道路通罗马”的原则,即多模式解决方案,对数据的开发不管是SQL-MAP ,ORM,Data Control 哪种模式都可以“殊途同归”,对于OQL,这个也得到了充分的体现,比如上面的用户登录功能,使用了7 种方式来实现,实际上还有3中查询方式本篇文章没有做成说明;而对于实体类的增,删,改,分别又提供了DbContext,OQL,泛型EntityQuery 等多种方式。

    所以,SOD框架的使用非常灵活,你可以根据你的偏好,习惯,环境,来灵活使用,而且也容易扩展,因此,相对于EF这样的ORM框架来,SOD框架的ORM功能没有任何束缚,它自由,灵活,而且轻量,容易扩展,但不妨碍它的强大,比如对于分表分库的查询,数据的批量更新插入修改,数据库锁的直接支持等这些“企业级”数据开发需求的支持。

    五、相关资源

    本篇文章的源码,已经上传在了框架的开源项目 pwmis.codeplex.com 网站上,可以通过下面的地址查看完整的源码:

    http://pwmis.codeplex.com/SourceControl/latest#SOD/Test/SampleORMTest/Program.cs

    源码下载,可以在下面的地址查看:

    http://pwmis.codeplex.com/releases/view/117822 Example     简单ORM示例程序--新手必读

    有关框架更详细而完整的入门指引,请参考下面这篇文章:

    PDF.NET SOD 开源框架红包派送活动 && 新手快速入门指引

    更多完整而详细的信息,请看框架官网地址:

    http://www.pwmis.com/sqlmap

    框架已经完全开源,参看这篇文章:

    一年之计在于春,2015开篇:PDF.NET SOD Ver 5.1完全开源

    另外,网友 广州-四糸奈 在他的"惊鸿哥的港湾"写了一篇基础入门介绍文章《PDF.NET最初等入门》,推荐大家看看。

    六、最新源码资源地址

    CodePlex online : http://pwmis.codeplex.com/SourceControl/latest

    CodePlex SVN : https://pwmis.codeplex.com/svn

    GitHub      : https://github.com/znlgis/PDF.NET-SOD

    OSChina     : http://git.oschina.net/dxzyx/SOD

  • 相关阅读:
    Vue2020
    Vue2020
    Vue v-model双向数据绑定 的实现
    TCP 粘包
    黑幕模板
    STL总结与例子
    中缀表达式转后缀表达式
    php 转换数组里的时间戳
    gorm踩过的坑
    WxJava使用lettuce客户端的redis实现微信access_token等接口重复利用
  • 原文地址:https://www.cnblogs.com/zhangxiaolei521/p/5646758.html
Copyright © 2020-2023  润新知