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

自同步数据访问层 - 第二部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.55/5 (36投票s)

2013年11月25日

CPOL

6分钟阅读

viewsIcon

106015

downloadIcon

3289

在实现业务的数据访问层 SQL(命令/事务)同步数据库实体结构/模板的软件中,这是最耗时的事情,因为当您的实体结构发生更改(如数据类型、名称等)时,您需要重新实现。

引言

数据访问层不仅是软件开发挑战中最重要的一部分,也是软件系统的关键瓶颈,并且它还有增加开销和产生许多副作用的巨大潜力。然而,我们有几种方法/设计模式来实现这一层,但我们总会有一些可怕的缺陷会增加维护成本。

在实现业务的数据访问层 SQL(命令/事务)同步数据库实体结构/模板的软件中,这是最耗时的事情,因为当您的实体结构发生更改(如数据类型、名称等)时,您需要重新实现。

最重要的标准是保证您的 DBMS 事务的可靠性,但什么是可靠的事务?这是一个好问题。

ACID(原子性一致性隔离性持久性)是一组属性,保证数据库事务被可靠地处理。在数据库的上下文中,对数据的单个逻辑操作称为事务。Jim Gray 在 20 世纪 70 年代末定义了这些可靠事务系统的属性。

在实现业务的数据访问层 SQL 命令/翻译同步数据库实体结构/模板的软件中,这是最耗费精力的事情,每当您更改实体结构(如数据类型、名称等)时,您都需要重写 SQL 命令。
在本文中,我将通过使用泛型、反射器和设计模式来实现一个灵活且自同步的数据访问层,这样我们就无需在开发/更新数据库中的实体之后/之前重写任何 SQL 命令。如您所知,数据库中的每个实体都是一个简单的表,每个实体实例都是该表中的一行。

Microsoft Entity Framework 引入了一些类似的解决方案,但不幸的是 EF 不支持某些 DBMS,例如 Microsoft Access,并且它不是开源解决方案。还有一个更新,在这种情况下,您可以实现自己的搜索算法或任何其他类型的特殊处理,以便在多个数据库中访问数据,例如最优搜索算法。此外,您可以用于科学目的,例如评估新方法、解决方案或数据访问算法以及冗余项。

链接 I:C# 泛型入门 链接 II:泛型简介 (C# 编程指南) 链接 III:用于 ADO.net 的 Sqlite
注意:请从上面的链接 III 下载与您的目标机器兼容的 sqlite .net 4 x82 或 64 版本,并在您的 DataAccessHandler 项目中添加引用

背景

在上一篇文章“泛型数据访问层”中,我讨论了 .NET 中的泛型类型及其优点,但我的这个版本并不那么充分,而且这个版本也是这个想法的临时类型。但我建议您在开始自同步 DAL 之前,先看一下这个版本。

在这个版本中,我们有三个主要目标

  1. 一个数据访问层,具有分离的模型、上下文以及连接不同 DBMS 的模型
  2. 使用命令设计模式来执行实体和 ADO.NET 命令
  3. 防止 SQL 注入等典型威胁

链接 III: 命令设计模式

命名空间 描述
DataAccessHandler 这是模型、上下文和数据库之间的一个中间层。不同的提供程序可以建立不同数据库之间的不同连接,另一方面,IEntityIContext 位于此层,并且模型实体要使用此层,应该继承自抽象模型和上下文(IEntity, IContext)。

HRModel

这个命名空间介绍了人力资源模型,用于将 HRDatabase(人力资源数据库)与数据访问处理程序连接起来。
AccModel

这个命名空间介绍了会计模型,用于将 AccDatabase(会计数据库)与数据连接起来。
Access Handler

数据访问处理程序

DataAccessHandler 有两个重要的子命名空间(CommandCollection)。Command 层是所有命令的容器,为了实现这部分,我使用了命令模式、工厂模式和泛型数据结构。Command 命名空间展示了这一点,而 Collection 是一个泛型类型的命名空间,根据下图,您可以更详细地了解 DataAccessHandler

图 1:命令设计模式

图 1 从右侧显示了实体命令,从左侧显示了 ADO.NET 命令。您可以认为实体命令需要 Entity 来执行,实际上这意味着 UpdateCommandInsertCommandDeleteCommand 必须知道哪个实体应该从哪个上下文(Context)更新、插入或删除,因此每个实体必须了解自己的上下文。这就是为什么我们有 IEntity 作为接口的原因,在上一版本中,每个 Entity 都不了解自己的上下文。图 2 说明了这一点。

图 2:IEntity 的架构

根据图 3,您可以看到 IEntity<T>IContext 的关系,以获取每个 IEntity 的上下文,以及 ConnectionInfo 类来了解数据库连接信息,因此每个实体命令将了解自己的数据库连接,并且在运行时通过 IContext 进行冗余连接。

图 3:IEntity<T>IContext 关系

IEntity<T> 包含 IEntityCommand<T> 以便使用 UpdateCommand<T>InsertCommand<T> 等。

上下文的职责

现在我们想了解 IContext 的活动和职责。IContext 在支持各种数据库方面起着至关重要的作用,因为在 IContext 中,我们可以使用 ConnectionInfo 类定义各种提供程序和连接。为了连接不同的数据库,需要不同的提供程序,并且为了实现冗余业务,我使用了 Redundancy 结构来保存多个 ConnectionInfo 类。请注意,这是一个递归属性。

下面的列表是 IContext 的职责

  1. 在模型中初始化实体连接信息
  2. 将模型的提供程序引入数据访问处理程序(IEnginCommands
  3. 处理数据库连接冗余
  4. 提供从实体获取模型和从模型获取实体的能力(例如,从 HRModel 如果我有 Personnel 实体和 Position 实体,我可以通过 Personnel 实体访问 Position 的列表,反之亦然)这可以使处理模型实体更加方便。

IContext Overview

图 4:IContext 的全景图

实体命令序列图

图 5:实体命令序列图

Invoker 是一个命令工厂,我们可以创建我们的命令。在此示例中,IEntity<T> 将请求发送到 Invoker,Invoker 返回请求的命令,例如 InsertCommand<T>IEntity<T>,最后 IEntity<T> 调用命令的 Execute() 方法。其他命令也遵循相同的场景。

 public virtual object Insert(T entity)
{
    iEntityCommand = Invoker.Instanc.GetCommand(entity, EntityCommandType.Insert);
    TransactionResult<T> result = iEntityCommand.Execute(entity);
    if (result.Commit)
       Collection.Add(entity);
    return result.Entity.PrimaryKey.Value;
} 

初始化上下文

Context,我们拥有模型和数据库连接信息,因此在使用 GDA(通用数据访问)之前,我们应该初始化我们的上下文。此初始化包括创建 ConnectionInfo 数据结构并使用它来初始化 Context 的数据库连接信息。

static DataAccessHandler.ConnectionInfo HROledbConnectionInfo = new DataAccessHandler.ConnectionInfo()
{
    DataSourceProvider = DataAccessHandler.Provider.Oledb,
    Password = "",
    Username = "",
    DataSource = @"C:\Users\Homay\Desktop\GenericDataAccess\GenericDataAccessII\Database\HRDatabase.mdb",
    IsValid = true
};

static DataAccessHandler.ConnectionInfo AccOledbConnectionInfo = new DataAccessHandler.ConnectionInfo()
{
    DataSourceProvider = DataAccessHandler.Provider.Oledb,
    Password = "",
    Username = "",
    DataSource = @"C:\Users\Homay\Desktop\GenericDataAccess\GenericDataAccessII\Database\AccDatabase.mdb",
    IsValid = true
};


static void Main(string[] args)
{
    AccModel.Context.Instanc.Initialize(AccOledbConnectionInfo);
    HRModel.Context.Instanc.Initialize(HROledbConnectionInfo);
}  

插入新记录

从这里我将向您展示如何向上下文添加新记录。

AccModel.Account newAccount = new AccModel.Account() 
{ 
    Name = "AccTest" ,
    Description = "for test",
};
AccModel.Context.Instanc.Account.Insert(newAccount); 

更新记录

从这里我将向您展示如何更新上下文中的记录。

AccModel.Account account = AccModel.Context.Instanc.Account.Collection.Select("Name", "AccTest").FirstOrDefault();
account.Name = "JustTest";
AccModel.Context.Instanc.Account.Update(account); 

删除记录

从这里我将向您展示如何从上下文中删除记录。

AccModel.Account account = AccModel.Context.Instanc.Account.Collection.Select("Name", "JustTest").FirstOrDefault();
AccModel.Context.Instanc.Account.Delete(account); 

SQL 注入

现在我将讨论 SQL 注入并解释我如何解决这个问题,SQL 注入是一种代码注入技术,用于攻击数据驱动的应用程序。您可以在 wiki 上阅读有关它的信息。我通过将参数传递给命令来解决这个问题。下面的代码说明了这一点。

public class InsertCommand<T> : IEntityCommand<T> where T : IEntity<T>
{
    public InsertCommand(T entity, EntityCommandType entityCommandType)
        : base(entity, entityCommandType)
    {
    }

    public override TransactionResult<T> Execute(T entity)
    {
        TransactionResult<T> result = new TransactionResult<T>() { Commit = false, Entity = null };
        string Insert = "INSERT INTO " + ((IEntity<T>)entity).EntityName;
        string columns = "(";
        string values = "VALUES(";
        Dictionary<string, object> parameters = new Dictionary<string, object>();
        Dictionary<string, object> infos = GetInfo(entity);
       
        foreach (var item in infos) // create parameters
        {
            if (item.Value != null && item.Value.ToString() != "")
            {
                if (entity.PrimaryKeyReadOnly && item.Key == entity.PrimaryKey.Name)
                    continue;
                columns += "[" + item.Key + "],";
                values += "@" + item.Key.ToString() + ",";
                parameters.Add("@"+item.Key, item.Value);
            }
        }
        columns = columns.Remove(columns.Length - 1, 1) + ") ";
        values = values.Remove(values.Length - 1, 1) + ") ";
        Insert += columns + values;
        IEnginCommand executeNoneQuery = Invoker.Instanc.GetCommand(EnginCommandType.ExecuteNoneQuery);
        ((ExecuteNoneQuery)executeNoneQuery).Execute(entity.Context.ConnectionInfo, Insert, parameters);
        entity.InitializePrimaryKeyValue(new PrimaryKey(){Value= 
          ((ExecuteNoneQuery)executeNoneQuery).Identity,Name = entity.PrimaryKey.Name});
        result.Commit = true;
        result.Entity = entity;
        return result;
    }

    public override EntityCommandType Type
    {
        get { return EntityCommandType.Insert; }
    }
} 

转到上一部分

历史

  • 更新日期:2013/25/11。
  • 更新日期:2014/21/12
© . All rights reserved.