65.9K
CodeProject 正在变化。 阅读更多。
Home

ObjectLounge - 面向对象的数据库框架

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.07/5 (8投票s)

2009年7月20日

LGPL3

10分钟阅读

viewsIcon

46171

downloadIcon

480

“开箱即用” 主机化您的领域模型 + 无需存储或架构 + 领域模型上的事务 + 验证

ObjectLounge Logo

引言

TechNewLogic ObjectLounge 框架是一个开源框架,可帮助您开发业务应用程序。它是一个面向对象领域模型的主机,管理并发事务验证持久性

如果您查看下面的小步教程,您可以了解该框架的工作方式及其功能。

想了解更多?请访问 http://objectlounge.technewlogic.de.

必备组件

您需要 Microsoft SQL Server CE 3.5 SP1 Runtime

背景

如今,人们在构建业务应用程序时会自动使用关系数据库。但是

数据库来自很久很久以前的遥远星系......

那个时候,

  • RAM 非常小,价格昂贵,
  • 计算机速度很慢,
  • 编程是技术性很强的东西,OO 等抽象概念尚未发明。

因此,数据库供应商优化了他们的产品,以实现扩展性、内存充足性等。但在中型“通常”业务应用程序中,这些真的需要吗?我们认为:不,先生。原因如下:

  • 8 GB RAM 只需几美元。
  • 即使是台式电脑也以几 GHz 的速度运行,并拥有多个核心。
  • 软件无处不在,必须快速变化并具有高度灵活性。
  • 中型业务应用程序中的数据量通常小于一台配置良好的机器上的内存量。

面向对象很棒,很酷,很流畅。但是一旦你接触到数据库,它就不再那么酷和流畅了。我们定义关系表,但我们想要分层类。我们编写存储过程、函数和触发器,但我们希望将逻辑封装在具有方法、属性和事件的对象中。关键是:即使使用现代 ORM,我们也要经历很多麻烦才能将应用程序连接到数据库。我们为此获得了很多我们不需要的功能。所以问题是

我们为什么要使用数据库呢?

我们唯一需要的是一个可以持久化实体的存储,以及一个使这方面尽可能透明的框架。但是数据库的“有用”功能(由 ACID 标准定义,主要侧重于并发性)呢?在数据库中,我们可以运行事务,可以回滚它们,并且事务之间具有隔离性。如果我们想摆脱数据库,我们需要一个框架来处理领域模型上的带回滚和隔离的事务——太好了!使用 ObjectLounge,您也可以做到所有这些——无需数据库!

教程

该示例侧重于开发一个独立的(仅客户端)WPF 应用程序,用于管理包含作者、博客和帖子的博客系统(灵感来自 Castle ActiveRecord 的“入门”)。当然,它也可以用于服务中,ObjectLounge 的事务/并发处理将在其中发挥作用。

该示例的目标是让您了解

  • 领域类应该是什么样子。
  • 我们如何使用聚合和组合等抽象。
  • 如何使用领域模型(查询数据、操作数据、事务和持久化)。

此示例未显示的内容

  • 验证
  • 复杂的业务逻辑
  • 并发

设置你的项目

对于此示例,您可以通过执行以下操作开始使用 ObjectLounge 进行开发

  • 在 Visual Studio 2008 中创建一个新的“WPF 应用程序”项目,并将其命名为“GettingStartedDemo”。
  • 添加对程序集 Technewlogic.ObjectLounge.dll 的引用。

创建领域模型

现在,在项目中创建一个名为“DomainModel”的文件夹,并添加以下新类

  • 作者
  • 博客
  • PostCategory
  • Post
  • DomainModelContext

前四个类称为“实体类”,包含我们应用程序的业务逻辑。它们共同构成了示例应用程序的领域模型。DomainModelContext 类保存这四个实体类的列表(只读或读/写)。

最终的领域模型将如下所示

Domain Model

现在,我们将详细查看这些类,并描述它们如何与 ObjectLounge 框架交互。您可以将代码区域复制到您的类中。

Author 类

using System;
using System.ComponentModel;
using Technewlogic.ObjectLounge;

namespace GettingStartedDemo.DomainModel
{
    [Identity("_id")]
    public class Author : INotifyPropertyChanged
    {
        protected Guid _id = Guid.NewGuid();

        private string _name = default(string);
        public virtual string Name
        {
            get { return _name; }
            set
            {
                _name = value;
                OnPropertyChanged("Name");
            }
        }

        private string _password = default(string);
        public virtual string Password
        {
            get { return _password; }
            set
            {
                _password = value;
                OnPropertyChanged("Password");
            }
        }

        private Blog _blog;
        [Composition]
        public virtual Blog Blog
        {
            get { return _blog; }
            set
            {
                _blog = value;
                OnPropertyChanged("Blog");
            }
        }

        #region INotifyPropertyChanged Members

        ...

        #endregion
    }
}
简单 POCO
如您所见,Author 类是一个简单的 POCO(普通旧 CLR 对象)。这意味着如果您想使用 ObjectLounge,您不必实现 ObjectLounge 特定的接口或继承基类。INotifyPropertyChanged 的实现只是为了 WPF 中的数据绑定。
IdentityAttribute - 实体的标识
每个 ObjectLounge 实体类都需要 IdentityAttribute,它指定应用于维护对象标识的字段名称。标识对于 ObjectLounge 引用管理系统也很重要,该系统依赖于实体内部的标识字段(这是框架的持久性方面对框架用户不透明的一点)。
虚拟属性
看看 NamePassword 属性:它们被标记为虚拟!这是强制性的,因为在内部,ObjectLounge 框架通过在运行时继承您的领域类来生成它们的代理(这对于几乎所有子系统都非常重要,例如事务管理、变更跟踪和验证)。
CompositionAttribute - 管理子引用
还有另一个属性:Blog 属性,它是一个指向博客实体的指针。它被标记为 CompositionAttribute。在 ObjectLounge 中,引用(无论是单个引用还是列表引用)通过使用组合和聚合来建模。组合是其子级的“所有者”。这意味着,如果父实体被删除,所有子级也将从存储中删除。只能有一个类对另一个类进行组合。在这种情况下:如果 Author 类定义了对 Blog 类的组合,则其他类也不能对 Blog 类进行组合。

Blog 类

using System;
using System.Collections.Generic;
using System.ComponentModel;
using Technewlogic.ObjectLounge;

namespace GettingStartedDemo.DomainModel
{
    [Identity("_id")]
    public class Blog : INotifyPropertyChanged
    {
        protected Guid _id = Guid.NewGuid();

        private string _name = default(string);
        public virtual string Name
        {
            get { return _name; }
            set
            {
                _name = value;
                OnPropertyChanged("Name");
            }
        }

        [MasterRef]
        public virtual Author Author { get; protected set; }

        [Composition]
        public virtual IList<Post> Posts { get; protected set; }

        #region INotifyPropertyChanged Members

        ...

        #endregion
    }
}
MasterRefAttribute - 指向父级的反向指针
在面向对象的领域模型中,您可以非常方便地遍历对象图。在我们的例子中,我们可以选择一个作者并使用 Blog 属性导航到它的博客。如果我们要反向导航——从 Blog 导航到它的父级 Author 呢?为此,您可以指定一个由 MasterRefAttribute 修饰的反向指针属性。当您更改父级的组合时,框架将自动设置该属性。
CompositionAttribute - 管理子集合
此外,我们还有一个与 Author 类类似的对 Post 类的组合。但在这里,我们不是一个简单的链接,而是一个完整的帖子列表。当我们稍后使用这个类时,框架会将适当的集合分配给 IList 属性。

PostCategory 类

using System;
using System.ComponentModel;
using Technewlogic.ObjectLounge;

namespace GettingStartedDemo.DomainModel
{
    [Identity("_id")]
    public class PostCategory : INotifyPropertyChanged
    {
        protected Guid _id = Guid.NewGuid();

        private string _name = default(string);
        public virtual string Name
        ...
    }
}

非常简单的类 :)

Post 类

using System;
using System.ComponentModel;
using Technewlogic.ObjectLounge;

namespace GettingStartedDemo.DomainModel
{
    [Identity("_id")]
    public class Post : INotifyPropertyChanged
    {
        public Post()
        {
            CreationDate = DateTime.Now;
        }

        protected Guid _id = Guid.NewGuid();

        private string _heading = default(string);
        public virtual string Heading
        ...

        private string _content = default(string);
        public virtual string Content
        ...

        private DateTime _creationDate = default(DateTime);
        public virtual DateTime CreationDate
        ...

        private bool _isPublished = default(bool);
        public virtual bool IsPublished
        ...

        [MasterRef]
        public virtual Blog Blog { get; protected set; }

        [Aggregation]
        public virtual PostCategory PostCategory { get; set; }

        ...
    }
}
AggregationAttribute - 实体的弱引用
正如我们之前所见,PostsBlog 类组成(“拥有”)。但这里还有一个有趣的发现,那就是:Post 需要一个 PostCategory。但并非以它真正是其“所有者”的方式。我们只是想指向一个现有的 PostCategory - 仅此而已。因此 PostCategory 的性质更像是一个主数据。这是通过在 PostCategory 属性上使用 AggregationAttribute 来实现的。

DomainModelContext 类

using System.Collections.Generic;
using Technewlogic.ObjectLounge;

namespace GettingStartedDemo.DomainModel
{
    public class DomainModelContext
    {
        [EntityCollection]
        public IList<Author> Authors
        {
            get; private set;
        }

        [EntityCollection]
        public IEnumerable<Blog> Blogs
        {
            get; private set;
        }

        [EntityCollection]
        public IEnumerable<Post> Posts
        {
            get; private set;
        }

        [EntityCollection]
        public IList<PostCategory> PostCategorys
        {
            get; private set;
        }
    }
}

最后但并非最不重要:我们需要一个类来提供对系统中所有实体的访问,我们称之为“上下文”。

EntityCollectionAttribute
EntityCollectionAttribute 告诉 ObjectLounge 框架我们的领域模型包含哪些实体类。
顶级类
顶级类没有“所有者”,意味着:没有其他类持有对其的组合。因此,我们需要一些地方,通过使用上下文中的 IList<T> 属性,我们可以插入新实体或删除旧实体。
非顶级类
非顶级类只能通过上下文中的 IEnumerable<T> 属性读取。写入访问通过实体父类中的组合提供。

使用领域模型

在下一节中,我们将介绍如何使用我们的领域模型,即修改和查询数据。大部分有趣的操作都在示例应用程序的 MainWindow 类中完成。

启动

using System;
using System.ComponentModel;
using Technewlogic.ObjectLounge;

public partial class MainWindow : Window, INotifyPropertyChanged
{
    public MainWindow()
    {
        InitializeObjectLounge();
        
        ...
    }

    private Engine<DomainModelContext> _engine;
    public DomainModelContext Context { get; private set; }

    private void InitializeObjectLounge()
    {
        // Create a new Context.
        Context = new DomainModelContext();

        // Start the framework (the database will be created
        // automatically if it doesn't exist).
        _engine = EngineFactory.CreateEngine("store.sdf", Context);

        // Check and create the demo user if not present.
        if (!Context.Authors.Any(it => it.Name == "Demo"))
        {
            // Execute an atomic transaction and store the "demo" user.
            _engine.ExecuteTransaction(() =>
            {
                // We must use the factory method of the engine
                // to create new entities.
                var demoAuthor = _engine.CreateInstance<Author>();
                demoAuthor.Name = "Demo";
                demoAuthor.Password = "Demo123";
                Context.Authors.Add(demoAuthor);
            });
        }
    }

    ...
    
}
上下文创建
我们必须为框架提供一个 DomainModelContext 实例。当我们启动引擎时,所有实体集合将自动注入。
先生们,启动引擎!
该框架使用 EngineFactory 启动。您可以提供数据库文件的路径。在这种情况下,ObjectLounge 使用其开箱即用的 SQL-Server CE 后端。或者,您可以提供自己的后端实现。在这种情况下,数据访问完全由您控制。
当框架启动时,会发生很多事情。框架会检查您的领域模型(实体类和上下文)是否符合上述约束。检查成功后,框架会从存储中加载实体并构建提供的上下文。现在,我们可以开始工作了!
执行原子事务
如果数据库是第一次创建,则根本没有数据。因此,我们创建一个“demo”用户,我们可以将其用于登录表单以登录应用程序。
您可以随时使用 LINQ 从上下文中查询数据。如果您想修改属性或添加/删除实体,您必须在事务内部进行。ObjectLounge 中的事务是线程绑定的,这意味着每个线程只能运行一个事务。因此,我们的示例应用程序是一个单线程客户端应用程序,这在这里并不那么重要。
有两种方式可以执行事务
  • 使用引擎的 ExecuteTransaction 方法。提供的委托将被执行并提交,如果发生异常则回滚。
  • 使用引擎的 BeginTransaction 方法来控制事务流(此示例中未使用)。
事务行为
在事务运行期间进行的所有更改在您的实体中是立即可见的,如果您从修改实体的事务中访问它们(正如您对普通对象的期望)。如果您在任何时候决定不想要这些更改,您可以直接回滚更改,就好像什么也没发生过一样(您无需以任何方式重新加载或重建实体)。只有当事务提交时,更改才会实际持久化到数据存储。
创建新的实体实例
为了让 ObjectLounge 框架为您管理实体,您必须让它为您创建新实体。因此,您必须使用引擎的 CreateInstance<T> 工厂方法,而不是使用 new 运算符。这要求实体类中有一个无参数(默认)构造函数。

DataBinding

我们有简单的POCO,所以我们可以将上下文中的实体绑定到UI元素。请注意,ObjectLounge 中的所有集合都实现了 INotifyCollectionChanged,这非常适合数据绑定到 ItemsControlListBox 等。

<Window x:Class="GettingStartedDemo.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:domainModel="clr-namespace:GettingStartedDemo.DomainModel"
    x:Name="mainWindow">

    <ListBox ItemsSource="{Binding ElementName=mainWindow, Path=Context.PostCategorys}">
            
        ...

摘要

我们已经学到了一些在使用 ObjectLounge 时需要注意的重要事项

  • 存在一个由实体类和上下文类组成的领域模型。
  • 上下文类具有用 EntityCollectionAttribute 标记的实体类的列表属性。
  • 实体类是 POCO。
  • 实体之间的关系使用 CompositionAttributeAggregationAttributeMasterRefAttribute 进行建模。
  • ObjectLounge 框架在运行时的管理通过 Engine 完成。
  • 可以使用 EngineFactory 方法创建引擎。
  • 实例使用引擎的 CreateInstance 方法创建。
  • 始终可以读取和查询数据。
  • 数据只能在事务中修改。

历史

2009-07-20: 文章发布。
2009-07-30: Blog 类的列表属性标记为 virtual / MasterRef setter 标记为 protected。

© . All rights reserved.