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

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.70/5 (51投票s)

2013年8月15日

CPOL

5分钟阅读

viewsIcon

227461

downloadIcon

5794

在本文中,我们将探索 C# 中泛型的强大功能。

引言

数据访问层可以是一个软件应用的重要组成部分。应用程序几乎总是需要访问数据,我们应该为此目的设计一个单独的层。现在,我们想使用 C# 中的泛型,并设计一个真正强大的数据访问层,以便在所有应用程序中使用,而无需任何更改。有关 C# 中泛型的更多信息,您可以关注以下链接之一

当然,应该注意的是,所有这些想法都已在 Entity Framework 中以最佳方式实现,但问题在于,Entity Framework 不能与某些数据库管理系统(如 MS Access)相同地使用。另一方面,它旨在展示泛型和编程中的便利性。

背景

阅读本文当前版本后,您可以在 这里 阅读新版本。在新版本中,我解决了一些问题,例如 SQL 注入和多数据库支持。

数据访问层概述

在本文中,我们将介绍我们的数据访问层架构(请参见下图)。从图中可以看到,我们有两个顶级命名空间,ModelDataAccessHandlerModel 是数据库的类视图,DataAccessHandler 是 ADO.NET 的包装器,用于与数据库管理系统建立通信。

模型

我们数据库中的每个表都应该在 Model 中有一个实现的类,具有相同的字段名称和字段数据类型。请注意,每个类都必须继承自 IEntity(请参见下图)。例如,在此示例中,Personnel 是数据库中 tblPersonnel 的类视图,它继承自 IEntity

Model

背景

Context 是使用 单例设计模式 实现的,作为模型容器,使用此容器插入、更新和删除实体会使 Collections 与数据库同步。因此,更改不需要再次更新 Collections,这可以降低数据访问层的成本。IEntity 中的 InsertUpdateDelete 方法是为了考虑此要求而实现的。请注意以下代码

IEntity

public void AddPersonel(Mode.Personnel personnel)
{
    Mode.Context.Instance.Personnel.Insert(personnel);
}

public void UpadtePersonel(Mode.Personnel personnel)
{
    Mode.Context.Instance.Personnel.Update(personnel);
}

public void RemovePersonel(Mode.Personnel personnel)
{
    Mode.Context.Instance.Personnel.Delete(personnel);
}

DataAccessHandler

在此项目中,我们的顶级命名空间是 DataAccessHandler,因此此容器包含 DataAccessAdapterIEntityEntitiesSqlCommand 枚举器和 PrimaryKey 数据结构。

描述 命名空间
IEntity 这是我们实体模型中实体的接口。例如,如果我们想在人员管理系统中将此 DAL 用于我们的系统,personnel 对象是一个目标实体,它应该实现此接口。每个实体都包含一个方法来插入、更新和删除其自身的集合,如果它继承自 IEntity DataAccessHandler
实体 这是实体的集合,这是一个泛型列表,对我们来说非常有用,因此每个实体都有自己的列表。 DataAccessHandler.Collection
PrimaryKey 此属性用于指定每个实体的。主键。它是一个包含两个元素的结构:NameValueName 是主键的名称,Value 是主键的值。 DataAccessHandler.IEntity
DataAccessAdapter 它是用于处理 ADO.NET 对象的包装器,因此我们期望此对象与我们的数据库进行通信。(在目标数据库上执行 insertupdatedeleteselectsql 命令)。此对象用于完成需要 sqlcommand 枚举器的任务。 DataAccessHandler
SqlCommand 这是一个枚举器对象,用于指定我们的 DAL SQL 命令与数据库进行事务处理。 DataAccessHandler
   

DataAccessAdapter 后端方法

Initialize 方法

此方法用于初始化 DataAccessAdapter 连接字符串和连接到数据库的密码。我们只使用此方法一次。

     static void Main(string[] args) 
{
    #region Initialize DataAccessAdapter
    string datasource = @"../../../Testdb.mdb";
    string password = "";
    DataAccessHandler.DataAccessAdapter.Initialize(datasource, password);
    #endregion
}   

DoEntity<T> 方法

此方法是一个泛型方法,有 3 个参数,第一个参数接收 entity,第二个参数接收 sqlCommandInsertUpdateDeleteSelect),最后,第三个参数接收 where 条件,但是,它不是必需的,通常与 UpdateDeleteSelect 命令一起使用。例如,请参见 IEntity 类的 Insert 方法。

    public virtual bool Insert(T entity)
{
    bool result = DataAccessAdapter.DoEntity(entity, SqlCommand.Insert, "");
    if (result)
        Collection.Add(entity);
    return result;
} 

GenerateCommand <T> 方法

此方法是一个泛型方法,与 DoEntity 方法相同,也有 3 个参数。但是,此方法需要决定 sqlCommand,如果 sqlCommand 是 insert,则调用 InsertCommand;如果它是 update 命令,则调用 UpdateCommand,依此类推。

public static string GenerateCommand<T>(SqlCommand sqlCommand,T entity,
   string where = "        ")where T:IEntity<T>
{
    string commandText = string.Empty;
    switch (sqlCommand)
    {
        case SqlCommand.Insert:
            commandText = InsertCommand(entity);
            break;
        case SqlCommand.Update:
            if(where == "")
                commandText = UpdateCommand(entity);
            else if(where != "")
                commandText = UpdateCommand(entity, where);
            break;
        case SqlCommand.Select:
            if (where == "")
                commandText = selectCommand(entity);
            else if (where != "")
                commandText = selectCommand(entity, where);
            break;
        case SqlCommand.Delete:
            if (where == "")
                commandText = deleteCommand(entity);
            else if (where != "")
                commandText = deleteCommand(entity, where);
            break;
        default:
            break;
    }
    return commandText;
} 

InsertCommand

在此方法中,我们应该为实体参数生成 SQL Insert 命令,当数据库中的实体表名具有前缀或后缀(如 tblPersonel)时,将使用 _perfix_suffix。此方法最重要的部分是 GetInfo 函数,其他方法(UpdateDeleteSelect)与此方法类似,因此我们不解释这些方法。

private static string InsertCommand<T>(T entity) where T:Entity<T>
{
    string Insert = "INSERT INTO " + _perfix + ((Entity<T>)entity).EntityName + _suffix;
    string columns = "(";
    string values = "VALUES(";
    Dictionary<string, object> infos = GetInfo(entity);
    //Read Column Names
    foreach (var item in infos)
    {
        if (item.Value != null && item.Value != "" && 
               !entity.PrimaryKey.Exists(p => p.Name == item.Key))
        {
            columns += item.Key + ",";
                values += Formatting(item.Value.ToString()) + ",";
        }
    }
    columns = columns.Remove(columns.Length - 1, 1)+") ";
    values = values.Remove(values.Length - 1, 1) +") ";
    Insert += columns + values;
    return Insert;
} 

GetInfo 方法

此方法使用 .NET 库中的反射来获取 Model 中实体的属性,我们提醒数据库中的每个表都有一个名为 entity 的类在 Model 中,所以当我的意思是获取实体的属性时,是指 Model 中类的属性。在此代码的末尾,您可以看到此示例中 Personnel 实体的属性。

注意:实体的。主键用此属性 [DataObjectFieldAttribute(true, true, false)] 标记。

namespace Mode
{
    public class Personnel:DataAccessHandler.IEntity<Personnel>
    {
        #region Table Fields
        int _id;
        [DisplayName("Identity")]
        [Category("Column")]
        [DataObjectFieldAttribute(true, true, false)]//Primary key attribute 
        public int ID
        {
            get { return _id; }
            set { _id = value; }
        }
        
        string _fName;
        [DisplayName("First Name")]
        [Category("Column")]
        public string FName
        {
            get { return _fName; }
            set { _fName = value; }
        }
 
        string _lName;
        [DisplayName("Last Name")]
        [Category("Column")]
        public string LName
        {
            get { return _lName; }
            set { _lName = value; }
        }
        #endregion
 
        #region Initialize
        public Personnel() { }
        #endregion
    }
}

关于此方法的另一个重要说明是条件。第一个条件检查项是否为属性,第二个条件检查项是否可读、可序列化且可根据访问修饰符访问,最终的条件在此场景中删除 PrimaryKeyEntityName!!为什么?请仔细思考。 Wink | <img src=

public static Dictionary<string, object> GetInfo<T>(T entity) where T : IEntity<T>
{
    try
    {
        Dictionary<string, object> values = new Dictionary<string, object>();
        foreach (var item in ((IEntity<T>)entity).GetType().GetProperties())
        {
            if (item.MemberType == System.Reflection.MemberTypes.Property)
            {
                if (item.CanRead && item.PropertyType.IsSerializable && item.PropertyType.IsPublic)
                {
                    if (item.Name == "PrimaryKey" || item.Name == "EntityName")
                        continue;
                    else if (item.PropertyType.IsEnum)
                        values.Add(item.Name, item.GetValue(entity, null).GetHashCode());
                    else
                        values.Add(item.Name, item.GetValue(entity, null));
                }
            }
        }
        IsSqlInjection(values);//we can improve this method
        return values;
    }
    catch (Exception)
    {

        throw;
    }
}    

IsSqlInjection() 方法是什么?

要回答这个问题,我们必须先回答什么是 SQL 注入攻击,但我更倾向于将您重定向到 这里 以获取更多信息。所以这个方法可以防止它们,尽管我知道这对于这个目的来说并不是一个真正安全的方法,但我们可以改进它,我创建它是为了未来的开发。

internal static void IsSqlInjection(Dictionary<string,object> source)
{
    foreach (var item in source)
    {
        if (item.Value != null)
            if (item.Value.ToString().Contains('@') || item.Value.ToString().Contains('=') || 
                    item.Value.ToString().Contains("'"))
                throw new Exception("It is not secure using");
    }
}

IEntity<T> 接口

IEntity<T> 是模型中实体的泛型接口。模型中的每个实体都可以继承自 IEntity<T>,并且 IEntity<T> 包含一些基本方法,如 InsertUpdateDeleteSelectLoad。接下来,我们将回顾 IEntity<T> 方法的顺序图。

Update 方法顺序图

Insert 方法顺序图

Delete 方法顺序图

希望这对您有所帮助...

转到下一部分

本主题已在下一部分扩展..

历史

  • 2013年8月13日:修订
  • 2013年8月16日:修订
© . All rights reserved.