XPO 使用笔记

原文链接:https://www.zybuluo.com/wang9563/note/666191

XPO 的使用类似 EntityFramework,提供 Model First 和 Code First 两种开发模式,本笔记将以 Code First 的开发方式进行记录:

  • XPObject 的创建、保存
  • XPObject 在数据库中的存储
  • 使用 GridView 进行数据绑定展示
  • XPObject 一对一、一对多等关系的存储
XPO 作为一款 ORM 框架,最基本的就是 M 即 Model,而数据库层不去了解亦可,只需要我们设计好 Model 类,并继承 XPO 提供的基类便可使用了 XPO 提供的数据存储以及数据绑定等功能。

1. 定义一个普通的 Model 类,比如订单

public class Order
{
    public string No;

    public DateTime Time;

    public string Note; 
}

2. 继承 XPObject 并定义构造函数

public class Order : XPObject
{
    /// <summary>
    /// 默认构造函数
    /// </summary>
    public Order() { }
    
    /// <summary>
    /// 传入 XPO Session 的构造函数
    /// </summary>
    public Order(Session session) : base(session) { }

    //一些字段、属性的定义
}

Session 是父类 XPObject 的只读属性,所以只能通过构造函数传入。调用 XPObject 的 Save 方法即可保存数据到数据库,所以 Session 是必须的,如果它未通过构造函数传入基类,Save 方法调用时则会是使用 DevExpress.Xpo.Session.DefaultSession 进行保存到数据的操作,若该属性也未配置或配置错误将会引发报错。

  • XPO 实现了三个父类 XPBaseObject XPCustomObject XPObject,可以根据自己的需要选择继承不同的父类,或者继承父类再实现一个通用的 Parent Model,以后可以共用该实现的 Model 可以直接继承 Parent Model 而无需重新实现类似的方法。

XPBaseObject:提供基础的数据存储、删除等功能
XPCustomObject:继承于 XPBaseObject,但调用删除会执行软删除,软删除标记字段是 GCRecord
XPObject:继承于 XPCustomObject,增加了主键 OID , 一般我们使用时都会继承于此类

3. 保存数据到数据库

//初始化 Session
var session = new Session
{
    //此处我使用 SQLite 进行数据存储,直接构建 DBConnection 给 Session 即可实现数据库的增删改查
    Connection = new SQLiteConnection(Config.DbConn),

    //或者注释上一行代码,直接传入数据库连接串亦可
    ConnectionString = Config.DbConn
};

//构造 Order 对象,并传入之前构造好的 Session
var order = new Order(session)
{
    No = "ORD001",
    Time = DateTime.Now,
    Note = "这是第一张单据"
};

//调用保存方法
order.Save();

通过执行上述代码,Order 对象已成功存储到数据库中了,可以通过 Navicat 打开 SQLite 数据库查看 XPO 到底存储了怎样的数据。

发现首次保存后多了两张表 Order XPObjectType

Order表
XPObjectType表

Order表 的表名与对象 Order 名字一致,存储的数据也与调用 Save 方法的对象一致,因为 Order 类继承于 XPObject 所以有 OID 作为主键,OptimisticLockField 字段以支持乐观锁,GCRecord 字段以支持软删除等。

XPObjectType表 则记录了数据库表与程序集中的类的对应关系

4. 数据绑定显示

这里仅列举通过 DevExpress 提供的 WebForm 控件来进行数据展示

<%-- 数据源控件,需要后台绑定 Session 或默认使用 DefaultSession --%>
<dx:XpoDataSource ID="Orders" runat="server" DefaultSorting="Oid" TypeName="WebTest.Models.Order">
</dx:XpoDataSource>

<%-- 显示控件,绑定数据源以及设置显示字段列的信息 --%>
<dx:ASPxGridView ID="gvTest" runat="server" DataSourceID="Orders" AutoGenerateColumns="False" KeyFieldName="Oid" Width="705px">
    <Columns>
        <dx:GridViewCommandColumn ShowDeleteButton="True" ShowEditButton="True" ShowNewButtonInHeader="True" VisibleIndex="0">
        </dx:GridViewCommandColumn>
        <dx:GridViewDataTextColumn FieldName="No" VisibleIndex="2">
        </dx:GridViewDataTextColumn>
        <dx:GridViewDataDateColumn FieldName="Time" VisibleIndex="3">
        </dx:GridViewDataDateColumn>
        <dx:GridViewDataTextColumn FieldName="Note" VisibleIndex="4">
        </dx:GridViewDataTextColumn>
    </Columns>
</dx:ASPxGridView>

显示效果如下

GridVie显示效果

可以通过设置 Caption 来修改显示字段的标题名

<dx:GridViewDataTextColumn FieldName="No" VisibleIndex="2" Caption="单号">
</dx:GridViewDataTextColumn>

或者在 Order 类的代码中,在对应字段上加上 DisplayNameAttribute 亦可实现同样效果

[DisplayName("单号")]
public string No;

若上述两项均有设置,则按照控件上设置的 Caption 显示

最终GridVie显示效果

通过以下代码可进行数据的筛选、过滤

//构建过滤条件
CriteriaOperator op = CriteriaOperator.Parse(string.Format("StartsWith(Scope,'{0}')", ScopeHelper.CurrentScope()));

if (!string.IsNullOrEmpty(txtName.Text))
    op = CriteriaOperator.And(op, CriteriaOperator.Parse($"Name like '%{txtName.Text}%'"));
if (!string.IsNullOrEmpty(txtNumber.Text))
    op = CriteriaOperator.And(op, CriteriaOperator.Parse($"Number like '%{txtNumber.Text}%'"));
if (!string.IsNullOrEmpty(txtCatalog.Text))
    op = CriteriaOperator.And(op, CriteriaOperator.Parse($"Catalog.Name like '%{txtCatalog.Text}%'"));
if (!string.IsNullOrEmpty(txtBrand.Text))
    op = CriteriaOperator.And(op, CriteriaOperator.Parse($"Brand.Name like '%{txtBrand.Text}%'"));

//设置数据源的过滤条件
XpoDs.Criteria = op.ToString();

疑虑: CriteriaOperator.Parse($"Name like '%{txtName.Text}%'") 这种写法是否存在 SQL 注入漏洞?
答:不会,CriteriaOperator.Parse 有特定的语法解析,SQL 注入为不合法的 SQL,所以无法进行执行,但会返回报错如下图所示:

SQL注入报错

这样可能导致一些特定的数据无法查询,所以建议使用如下写法

CriteriaOperator.Parse($"Name like ?", $"%{txtName.Text}%")

5. 含关联关系对象的存储

以下将列出 Model 类的定义,使用控件显示数据还请查看该控件的相关文档

  • 一对一
    public class Order : XPObject
    {
        public Order(Session session) : base(session) { }

        [DisplayName("单号")]
        public string No;

        [DisplayName("日期")]
        public DateTime Time;

        [DisplayName("备注")]
        public string Note;


        public Emp SalesMan;
    }

    public class Emp : XPObject
    {
        public Emp(Session session) : base(session) { }

        [Indexed(Unique = true)]
        [DisplayName("编号")]
        public string Number;

        [DisplayName("名称")]
        public string Name;
    }
  • 一对多
    public class Order : XPObject
    {
        public Order(Session session) : base(session) { }

        [DisplayName("单号")]
        public string No;

        [DisplayName("日期")]
        public DateTime Time;

        [DisplayName("备注")]
        public string Note;


        [Association("OrderSalesMan", typeof(Emp))]
        public XPCollection SalesMans => GetCollection("SalesMans");
    }

    public class Emp : XPObject
    {
        public Emp(Session session) : base(session) { }

        [Indexed(Unique = true)]
        [DisplayName("编号")]
        public string Number;

        [DisplayName("名称")]
        public string Name;

        [Association("OrderSalesMan")]
        public Order Order;
    }

以上内容是我测试以及参考文件 http://blog.csdn.net/yizhiduxiu11/article/details/9204447 所编写的,如有理解不透彻的地方欢迎指正。


作者 @王俊杰
2016 年 07月 07日

标签: none

添加新评论