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






4.55/5 (36投票s)
在实现业务的数据访问层 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 之前,先看一下这个版本。
在这个版本中,我们有三个主要目标
- 一个数据访问层,具有分离的模型、上下文以及连接不同 DBMS 的模型
- 使用命令设计模式来执行实体和 ADO.NET 命令
- 防止 SQL 注入等典型威胁
链接 III: 命令设计模式
| 命名空间 | 描述 | 
| DataAccessHandler | 这是模型、上下文和数据库之间的一个中间层。不同的提供程序可以建立不同数据库之间的不同连接,另一方面, IEntity和IContext位于此层,并且模型实体要使用此层,应该继承自抽象模型和上下文(IEntity,IContext)。 | 
| 
 | 这个命名空间介绍了人力资源模型,用于将 HRDatabase(人力资源数据库)与数据访问处理程序连接起来。 | 
| AccModel | 这个命名空间介绍了会计模型,用于将  | 

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

图 1:命令设计模式
图 1 从右侧显示了实体命令,从左侧显示了 ADO.NET 命令。您可以认为实体命令需要 Entity 来执行,实际上这意味着 UpdateCommand、InsertCommand 或 DeleteCommand 必须知道哪个实体应该从哪个上下文(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 的职责
- 在模型中初始化实体连接信息
- 将模型的提供程序引入数据访问处理程序(IEnginCommands)
- 处理数据库连接冗余
- 提供从实体获取模型和从模型获取实体的能力(例如,从 HRModel如果我有Personnel实体和Position实体,我可以通过Personnel实体访问Position的列表,反之亦然)这可以使处理模型实体更加方便。

图 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


