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






4.70/5 (51投票s)
在本文中,我们将探索 C# 中泛型的强大功能。
引言
数据访问层可以是一个软件应用的重要组成部分。应用程序几乎总是需要访问数据,我们应该为此目的设计一个单独的层。现在,我们想使用 C# 中的泛型,并设计一个真正强大的数据访问层,以便在所有应用程序中使用,而无需任何更改。有关 C# 中泛型的更多信息,您可以关注以下链接之一
- 链接 I: C# 泛型入门
- 链接 II: 泛型入门 (C# 编程指南)
当然,应该注意的是,所有这些想法都已在 Entity Framework 中以最佳方式实现,但问题在于,Entity Framework 不能与某些数据库管理系统(如 MS Access)相同地使用。另一方面,它旨在展示泛型和编程中的便利性。
背景
阅读本文当前版本后,您可以在 这里 阅读新版本。在新版本中,我解决了一些问题,例如 SQL 注入和多数据库支持。
数据访问层概述
在本文中,我们将介绍我们的数据访问层架构(请参见下图)。从图中可以看到,我们有两个顶级命名空间,Model
和 DataAccessHandler
,Model
是数据库的类视图,DataAccessHandler
是 ADO.NET 的包装器,用于与数据库管理系统建立通信。
模型
我们数据库中的每个表都应该在 Model
中有一个实现的类,具有相同的字段名称和字段数据类型。请注意,每个类都必须继承自 IEntity
(请参见下图)。例如,在此示例中,Personnel
是数据库中 tblPersonnel
的类视图,它继承自 IEntity
。
背景
Context
是使用 单例设计模式 实现的,作为模型容器,使用此容器插入、更新和删除实体会使 Collections
与数据库同步。因此,更改不需要再次更新 Collections,这可以降低数据访问层的成本。IEntity
中的 Insert
、Update
和 Delete
方法是为了考虑此要求而实现的。请注意以下代码
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
,因此此容器包含 DataAccessAdapter
、IEntity
、Entities
、SqlCommand
枚举器和 PrimaryKey
数据结构。
类 | 描述 | 命名空间 |
IEntity | 这是我们实体模型中实体的接口。例如,如果我们想在人员管理系统中将此 DAL 用于我们的系统,personnel 对象是一个目标实体,它应该实现此接口。每个实体都包含一个方法来插入、更新和删除其自身的集合,如果它继承自 IEntity 。 | DataAccessHandler |
实体 | 这是实体的集合,这是一个泛型列表,对我们来说非常有用,因此每个实体都有自己的列表。 | DataAccessHandler.Collection |
PrimaryKey | 此属性用于指定每个实体的。主键。它是一个包含两个元素的结构:Name 和 Value 。Name 是主键的名称,Value 是主键的值。 | DataAccessHandler.IEntity |
DataAccessAdapter | 它是用于处理 ADO.NET 对象的包装器,因此我们期望此对象与我们的数据库进行通信。(在目标数据库上执行 insert 、update 、delete 和 select 等 sql 命令)。此对象用于完成需要 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
,第二个参数接收 sqlCommand
(Insert
、Update
、Delete
或 Select
),最后,第三个参数接收 where
条件,但是,它不是必需的,通常与 Update
、Delete
或 Select
命令一起使用。例如,请参见 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
函数,其他方法(Update
、Delete
、Select
)与此方法类似,因此我们不解释这些方法。
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
}
}
关于此方法的另一个重要说明是条件。第一个条件检查项是否为属性,第二个条件检查项是否可读、可序列化且可根据访问修饰符访问,最终的条件在此场景中删除 PrimaryKey
和 EntityName
!!为什么?请仔细思考。
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>
包含一些基本方法,如 Insert
、Update
、Delete
、Select
和 Load
。接下来,我们将回顾 IEntity<T>
方法的顺序图。
Update 方法顺序图
Insert 方法顺序图
Delete 方法顺序图
希望这对您有所帮助...
转到下一部分
本主题已在下一部分扩展..
历史
- 2013年8月13日:修订
- 2013年8月16日:修订