再次处理数据库
4.81/5 (20投票s)
使用 ADO.net
资源
- Geniuses Code 数据框架(带 DataReader 映射)(视频).
- 如何使用 Geniuses Code 数据框架 多个数据库 (视频) .
- 阿拉伯语文章 第一部分 (开始使用 Geniuses Code) .
- 阿拉伯语文章 第二部分 (验证) .
- 阿拉伯语文章 第三部分 (模型和控制器生成器) .
- 在 Youtube 上。
引言
当您完成项目数据库的设计后,您就开始直接编写 C# 代码来处理它。如果您是一位优秀的开发者,您会知道,您将使用 N 层结构来设计您的应用程序,即 Model(代表表)和 Controller(代表该表的功能)。
然后,通常您需要处理数据库相关的内容:Connection、Command、Reader、Adapter、Data Set、Data Table 等等,最终你会得到类似这样的代码:
public List<Compnies> Get()
{
List<Compnies> compnies = new List<Compnies>();
using (var connection = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["ConnectionString"].ToString()))
{
if(connection.State == System.Data.ConnectionState.Closed)
{
connection.Open();
}
using(var command = new SqlCommand("EOM_Companies_Get", connection))
{
command.CommandType = System.Data.CommandType.StoredProcedure;
using (var reader = command.ExecuteReader())
{
while(reader.Read())
{
compnies.Add(new Compnies()
{
ID = int.Parse(reader["ID"].ToString()),
NameEn = reader["NameEn"].ToString(),
Active = bool.Parse(reader["Active"].ToString())
});
}
}
}
}
return compnies;
}
那么上面的代码有什么问题呢?它能正常工作,并且能完全实现您想要的一切。代码的问题在于,它会花费您大量的时间去做一些与应用程序需求完全无关的事情。
您必须始终处理数据库对象,如 Command、Connection 和 Data Reader,以便获取数据,然后开始处理这些数据。这在维护方面非常困难,极其冗长且重复。此外,它还针对特定的数据库,例如上面示例中的 SQL Server。将来,如果您想在 Oracle 上运行您的应用程序,您将需要更改所有内容才能做到这一点。
背景
现在我相信我达到了我想谈论的重点,我的想法非常简单:
我想使用常规的 ADO.net,但无需处理上述所有步骤。
我大约在一年前开始构建一个 DLL 扩展,用一行代码就能完成很多工作;我将给您举个例子,请看下面的代码:
/// <summary>
/// Get all Details by settlement
/// </summary>
/// <param name="settlement">settlement you want to get details for</param>
/// <returns>a list of details</returns>
public List<Details> Get(Settlement settlement)
{
return new Operations().ExecuteQueryToList<Details>("RI_Settlements_Details_Get_BySettlement", Operations.CommandType.StoredProcedure, new Parameter("Settlement", settlement.MojID));
}
是的,仅仅一行代码就能完成以下步骤:
- 打开与数据库的连接(Oracle、MS SQL、Access、SQLite)。
- 创建 Command 对象。
- 向 SP 传递参数。
- 执行 查询。
- 读取 Data Reader。
- 将 Data Reader 的内容反序列化到一个 List 或单个对象中(感谢泛型和反射)。
对象已准备就绪。
使用代码
首先安装
Nuget
PM> Install-Packages GeniusesCode.Framework.Data
配置
您需要在应用程序设置中添加三项设置:
<configuration>
<appSettings>
<add key="AllowEncryption" value="true"/>
<add key="EncryptionKey" value="mTXMwEuxoAgt1P69mTXMw6Agt1P69mTXMw6VcpAgt1PmTXMw69mTXMw6"/>
<add key="DbConnection" value="Data Source=.\SQLEXPRESS;Initial Catalog=RealestateIndicator;Integrated Security=True"/>
</appSettings>
</configuration>
安装后,您可以像这样使用它:
/// <summary>
/// Get all Details
/// </summary>
/// <returns>a list of all details</returns>
public List<Details> Get()
{
return new Operations().ExecuteQueryToList<Details>("RI_Settlements_Details_Get");
}
您可以像这样传递参数:
/// <summary>
/// Get all Details by settlement
/// </summary>
/// <param name="settlement">settlement you want to get details for</param>
/// <returns>a list of details</returns>
public List<Details> Get(Settlement settlement)
{
return new Operations().ExecuteQueryToList<Details>("RI_Settlements_Details_Get_BySettlement", Operations.CommandType.StoredProcedure, new Parameter("Settlement", settlement.MojID));
}
对于创建、更新和删除,您无需映射任何内容。Operations 类将 查询 存储过程 以获取参数,然后将其映射到您提供的对象,代码如下:
/// <summary>
/// Create details and get the new ID of it
/// </summary>
/// <param name="details">details you want to create</param>
/// <param name="newDetailsID">the new details id</param>
/// <returns>true if details get created</returns>
public bool Create(Details details, out int newDetailsID)
{
newDetailsID = 0;
return new Operations().ExecuteNoneQuery<Details>("RI_Settlements_Details_Create", details);
}
还有一件事,如果您有 主键和外键 ,在创建子对象时,您需要父对象的主键。对于父类,查找主键属性并将其设置为主键,代码如下:
/// <summary>
/// Gets or sets ID of the Details
/// </summary>
[PrimaryKey]
public int ID { get; set; }
然后在子类中,只需将父属性设置为外键,代码如下:
/// <summary>
/// Gets or sets Category of the Details
/// </summary>
[ForeignKey]
public DetailsCategory Category { get; set; }
快速开始
所以我们有这些类:
Details 类:
/// <summary>
/// this class Represents Detailstable
/// </summary>
public class Details
{
/// <summary>
/// Initializes a new instance of the <see cref="Details"/> class.
/// </summary>
public Details()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Details"/> class.
/// </summary>
/// <param name="id">id of the Details</param>
public Details(int id)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Details"/> class.
/// </summary>
/// <param name="id">id of the Details</param>
/// <param name="total">total of the Details</param>
/// <param name="area">area of the </param>
/// <param name="dealsCount">deal count</param>
/// <param name="createAt">create at</param>
/// <param name="category">category of the details</param>
/// <param name="settlement">settlement of the details</param>
public Details(int id, string total, string area, int dealsCount, string createAt, DetailsCategory category, Settlement settlement)
{
this.ID = id;
this.Total = total;
this.Area = area;
this.CreateAt = createAt;
this.Category = category;
this.DealsCount = dealsCount;
this.Settlement = settlement;
}
/// <summary>
/// Gets or sets ID of the Details
/// </summary>
[PrimaryKey]
public int ID { get; set; }
/// <summary>
/// Gets or sets Details Deals Count
/// </summary>
public int DealsCount { get; set; }
/// <summary>
/// Gets or sets Details Total
/// </summary>
public string Total { get; set; }
/// <summary>
/// Gets or sets Area Category
/// </summary>
public string Area { get; set; }
/// <summary>
/// Gets or sets when the Details was created
/// </summary>
public string CreateAt { get; set; }
/// <summary>
/// Gets or sets Category of the Details
/// </summary>
[ForeignKey]
public DetailsCategory Category { get; set; }
}
Details Category 类
namespace RealestateIndicator.Model
{
using GeniusesCode.Framework.Data;
using GeniusesCode.Helper;
/// <summary>
/// this class Detail Category Category table
/// </summary>
public class DetailsCategory
{
/// <summary>
/// Initializes a new instance of the <see cref="DetailsCategory"/> class.
/// </summary>
public DetailsCategory()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DetailsCategory"/> class.
/// </summary>
/// <param name="id">id of the details category</param>
public DetailsCategory(int id)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Category"/> class.
/// </summary>
/// <param name="id">id of the settlement category</param>
/// <param name="name">name of the settlement category</param>
public DetailsCategory(int id, string name)
{
this.ID = id;
this.Name = name;
}
/// <summary>
/// Gets or sets Details Category ID
/// </summary>
public int ID { get; set; }
/// <summary>
/// Gets or sets Details Category Name
/// </summary>
public string Name { get; set; }
}
Geniuses Code DLL
Operations 类
Operations 类在执行以下操作之后:
public class Operations
{
public Operations();
public Operations(string connectionString, string encryptionKey = null, bool allowEncryption = false);
public bool AllowEncryption { get; set; }
public string ConnectionStirng { get; set; }
public string EncryptionKey { get; set; }
public string Decrypt(string data);
public string Decrypt(string data, string key);
public bool Delete(string tableName, string whereColumnName, object whereValue);
public string Encrypt(string data);
public string Encrypt(string data, string key);
public bool ExecuteNoneQuery<T>(string command, T dealWithThisObject);
public bool ExecuteNoneQuery(string command, Operations.CommandType commandType = Operations.CommandType.Text, params Parameter[] parameters);
public bool ExecuteNoneQuery<T>(string command, T dealWithThisObject, out int paramter);
public bool ExecuteNoneQuery<T>(string command, T dealWithThisObject, out List<Parameter> outParams);
public bool ExecuteNoneQuery(string command, out List<Parameter> outPramas, Operations.CommandType commandType = Operations.CommandType.Text, params Parameter[] parameters);
public DataTable ExecuteQuery(string command, Operations.CommandType commandType = Operations.CommandType.Text, params Parameter[] parameters);
public DataTable ExecuteQuery(string command, out List<Parameter> outPramas, Operations.CommandType commandType = Operations.CommandType.Text, params Parameter[] parameters);
public List<T> ExecuteQueryToList<T>(string command, Operations.CommandType commandType = Operations.CommandType.Text, params Parameter[] parameters);
public List<T> ExecuteQueryToList<T>(string command, out List<Parameter> outPramas, Operations.CommandType commandType = Operations.CommandType.Text, params Parameter[] parameters);
public T ExecuteQueryToObject<T>(string command, Operations.CommandType commandType = Operations.CommandType.Text, params Parameter[] parameters);
public T ExecuteQueryToObject<T>(string command, out List<Parameter> outPramas, Operations.CommandType commandType = Operations.CommandType.Text, params Parameter[] parameters);
public bool Insert(string tableName, params Parameter[] parameters);
public bool Update(string tableName, string whereColumnName, object whereValue, params Parameter[] parameters);
public enum CommandType
{
StoredProcedure = 0,
Text = 1,
TableDirect = 2,
}
}
加密和解密方法
您可以使用存储在配置文件中的密钥或您传递给 Operations 的密钥来加密和解密任何字符串:
string word = new Operations().Encrypt("ALGHABBAN");
string word2 = new Operations().Encrypt("ALGHABBAN", "this my key");
string word3 = new Operations().Decrypt(word);
string word4 = new Operations().Decrypt(word2, "this my key");
如果您通过配置文件启用加密,Operations 类将自动为您加密和解密所有字符串属性。
带映射的 Execute None Query 方法
您有 5 种方法可以 执行 None Query 命令(更新、删除、插入),
首先,执行对象的 None Query。例如,如果我想在 Details category 表中插入新行,我将使用这个存储过程:
CREATE PROCEDURE RI_Settlements_Details_Categories_Create
@ID int output,
@Name nvarchar(1500)
AS
BEGIN
insert into RI_Settlements_Details_Categories(Name)
values (@Name)
SET @ID = SCOPE_IDENTITY();
END
执行 None Query,不返回输出参数。
/// <summary>
/// Create new Details
/// </summary>
/// <param name="detailsCategory">details you want to create</param>
/// <returns>true if details get created</returns>
public bool Create(DetailsCategory detailsCategory)
{
return new Operations().ExecuteNoneQuery<DetailsCategory>(
"RI_Settlements_Details_Categories_Create", detailsCategory);
}
正如您所看到的,没有任何数据映射,没有参数,没有命令,什么都没有。Operations 类将获取存储过程名称并获取所有参数,然后 Operations 类将遍历参数列表并创建一个新的命令参数,该参数将从 Details category 的对应属性获取值。
执行 None Query,返回一个整数输出参数。
/// <summary>
/// Create new Details
/// </summary>
/// <param name="detailsCategory">details you want to create</param>
/// <returns>true if details get created</returns>
public bool Create(DetailsCategory detailsCategory)
{
int outNewDetailsID = 0;
return new Operations().ExecuteNoneQuery<DetailsCategory>(
"RI_Settlements_Details_Categories_Create", detailsCategory, out outNewDetailsID);
}
执行 None Query,返回输出参数列表。
/// <summary>
/// Create details and get the new ID of it
/// </summary>
/// <param name="detailsCategory">details you want to create</param>
/// <param name="newDetailsID">the new details id</param>
/// <returns>true if details get created</returns>
public bool Create(DetailsCategory detailsCategory, out int newDetailsID)
{
newDetailsID = 0;
List<Parameter> newDetailsIDParamters = new List<Parameter>();
bool createDetails = new Operations().ExecuteNoneQuery("RI_Settlements_Details_Categories_Create", detailsCategory, out newDetailsIDParamters );
if (newDetailsIDParamters != null && newDetailsIDParamters.Count > 0)
{
newDetailsID = int.Parse(newDetailsIDParamters[0].Value.ToString());
}
return createDetails;
}
不带映射的 Execute None Query 方法
执行 None Query,不返回输出参数。
/// <summary>
/// Update details Category
/// </summary>
/// <param name="detailsCategory">details Category you want to update</param>
/// <returns>true if details Category get updated</returns>
public bool Update(DetailsCategory detailsCategory)
{
return new Operations().ExecuteNoneQuery(
"RI_Settlements_Details_Categories_Update",
Operations.CommandType.StoredProcedure,
new Parameter("ID", detailsCategory.ID),
new Parameter("Name", detailsCategory.Name));
}
执行 None Query,返回输出参数列表。
/// <summary>
/// Create details and get the new ID of it
/// </summary>
/// <param name="detailsCategory">details you want to create</param>
/// <param name="newDetailsID">the new details id</param>
/// <returns>true if details get created</returns>
public bool Create(DetailsCategory detailsCategory, out int newDetailsID)
{
newDetailsID = 0;
List<Parameter> newDetailsIDParamters = new List<Parameter>();
bool createDetails = new Operations().ExecuteNoneQuery(
"RI_Settlements_Details_Categories_Create",
out newDetailsIDParamters,
Operations.CommandType.StoredProcedure,
new Parameter("ID", detailsCategory.ID, Parameter.Direction.Output),
new Parameter("Name", detailsCategory.Name));
if (newDetailsIDParamters != null && newDetailsIDParamters.Count > 0)
{
newDetailsID = int.Parse(newDetailsIDParamters[0].Value.ToString());
}
return createDetails;
}
Execute Query 方法
不带数据映射的 Execute Query:
DataTable detailsCastegory = new Operations().ExecuteQuery("RI_Settlements_Details_Categories_Get");
将数据映射到对象列表的 Execute Query:
return new Operations().ExecuteQueryToList<DetailsCategory>("RI_Settlements_Details_Categories_Get");
将数据映射到对象的 Execute Query:
DetailsCategory detailsCategory = new Operations().ExecuteQueryToObject<DetailsCategory>("RI_Settlements_Details_Categories_Get_ByID", Operations.CommandType.StoredProcedure, new Parameter("ID", id));
您不局限于存储过程
您也可以在代码中处理查询。例如,您可以执行以下操作:
DetailsCategory detailsCategory = new Operations().ExecuteQueryToObject<DetailsCategory>("select * from RI_Settlement_Details_Category where ID = @ID ", Operations.CommandType.Text, new Parameter("ID", id));
或者
DetailsCategory detailsCategory = new Operations().ExecuteQueryToObject<DetailsCategory>("select * from RI_Settlement_Details_Category where ID = 15", Operations.CommandType.Text);
主键和外键
在上面的示例中,我们有 Details 和 DetailsCategory 对象。所以每个 details 都有一个 category。如果您想创建一个新的 details 对象而不使用自动映射选项,您可以编写以下代码:
/// <summary>
/// Update Details
/// </summary>
/// <param name="details">details you want to update</param>
/// <returns>true if details get updated</returns>
public bool Update(Details details)
{
return new Operations().ExecuteNoneQuery(
"RI_Settlements_Details_Update",
Operations.CommandType.StoredProcedure,
new Parameter("ID", details.ID),
new Parameter("Settlement", details.Settlement.MojID),
new Parameter("CreateAt", details.CreateAt),
new Parameter("Total", details.Total),
new Parameter("DealsCount", details.DealsCount),
new Parameter("Area", details.Area),
new Parameter("Category", details.Category.ID));
}
注意代码的最后一行,这一行:
new Parameter("Category", details.Category.ID)
您不是在传递 category,而是在传递 category 的 ID,因为 category 的 ID 是 DetailsCategory 表的主键。DetealisCategory 表的主键。
现在,当涉及到自动映射时,您如何告诉 ID 是 DetailsCategory 对象的 ID 是主键?我想做的是遍历每个属性,直到遇到外键,然后我需要找到该属性的主键并获取其值。
因此,为了做到这一点,您需要向 DetailsCategory 的 ID 添加一个名为 PrimaryKey 的属性,请参见代码:
/// <summary>
/// Gets or sets ID of the Details
/// </summary>
[PrimaryKey]
public int ID { get; set; }
然后您需要转到 Details 对象中的外键并添加 ForeignKey 属性,请参见代码:
/// <summary>
/// Gets or sets Category of the Details
/// </summary>
[ForeignKey]
public DetailsCategory Category { get; set; }
关系
在上面的示例中,details 有一个名为 category 的属性,我想能够像这样编写:
Console.WriteLine(details.Category.Name) // printer
让我带您了解 Operations 在 Execute Query 情况下的数据映射过程:它会遍历给定对象中的每个属性。如果找到用户定义类型,它会查找一个接收整数的构造函数。如果没有接收整数的构造函数,它将抛出错误。
在此构造函数中,您只需要做以下操作:
/// <summary>
/// Initializes a new instance of the <see cref="DetailsCategory"/> class.
/// </summary>
/// <param name="id">id of the details category</param>
public DetailsCategory(int id)
{
DetailsCategory detailsCategory = new Operations().ExecuteQueryToObject<DetailsCategory>("RI_Settlement_Details_Category_GetByID", Operations.CommandType.StoredProcedure, new Parameter("ID", id));
if (detailsCategory != null)
{
this.ID = detailsCategory.ID;
this.Name = detailsCategory.Name;
}
}
这就是您可以从 details 对象访问 category 名称的方式。
验证
验证也是您开始处理数据库时的一个重要部分,您需要始终检查是否从用户那里获取了正确的值。在我的代码中,有 3 种重复的验证类型:
- 非空,且字符串非空或非空。
- 数据库存在(当您需要检查 category 是否在数据库中,以及 details 对象是否在数据库中时)
- 表达式,用于验证邮箱或密码等。
最后,您将得到类似这样的代码:
public bool Create(User user)
{
if(user != null)
{
if(!string.IsNullOrWhiteSpace(user.Name))
{
if (!string.IsNullOrWhiteSpace(user.Password))
{
if (this.ValidatPassword(user.Password))
{
if (!string.IsNullOrWhiteSpace(user.Email))
{
if(this.ValidatEmail(user.Email))
{
if (user.Role != null)
{
if(new Role().Get(user.Role.ID) != null)
{
return new Operations().ExecuteNoneQuery<User>("UMS_users_Create", user);
}
throw new Exception("Role Can't be null, or not regested");
}
}
}
throw new Exception("Email can't be null");
}
}
throw new Exception("Password can't be null");
}
throw new Exception("User name can't be null or empty");
}
throw new Exception("User can't be null");
}
因此,为了解决这些问题,我创建了一个名为 AutoValidation 的类,它派生自 Attributes:
namespace GeniusesCode.Framework.Data
{
using System;
/// <summary>
/// Auto Validation Class
/// </summary>
public class AutoValidation : Attribute
{
/// <summary>
/// Gets or sets ValidationType
/// </summary>
public enum ValidationType
{
Number,
Custom,
DataBaseExistence,
}
/// <summary>
/// Gets or sets All Null
/// </summary>
public bool AllowNull { get; set; }
/// <summary>
/// Gets or sets Expression Object
/// </summary>
public string Expression { get; set; }
/// <summary>
/// Gets or set Message
/// </summary>
public string ErrorMessage { get; set; }
/// <summary>
///
/// </summary>
public AutoValidation.ValidationType Type { get; set; }
}
}
现在,如果我的 User 类中有一个名为 name 的属性,并且该 name 不得为 null,我就可以这样做:
/// <summary>
/// Gets or sets User Name
/// </summary>
[AutoValidation(
AllowNull = false,
ErrorMessage = "User Name can't be null")]
public string Name { get; set; }
并在 web.config 中将 auto validation 设置为 true,如下所示:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="DbConnection" value="Smoething"/>
<add key="AutoValidation" value="True"/>
</appSettings>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
</configuration>
现在,当我尝试创建 user 对象时,我会得到这个:

如果您想验证电子邮件,请执行以下操作:
/// <summary>
/// Gets or sets User Email
/// </summary>
[AutoValidation(
AllowNull = false,
ErrorMessage = "Wrong Email Address",
Expression = @"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$",
Type = AutoValidation.ValidationType.Custom)]
public string Email { get; set; }
结果将是

任何用户都会有一个角色,如果您想在添加用户之前检查该角色是否存在于数据库中,您需要将 ValidationType 设置为 DataBaseExistence,并创建一个接收 int(角色的 ID)并返回 Role 对象的方法:
/// <summary>
/// Gets or sets User Role
/// </summary>
[AutoValidation(
AllowNull = false,
ErrorMessage = "Role Can't be null, or not regested",
Type = AutoValidation.ValidationType.DataBaseExistence)]
[ForeignKey]
public Role Role { get; set; }
将 Get 方法设置为 DataBaseExistence
/// <summary>
/// Get role by id
/// </summary>
/// <param name="id">id for the role you want to get</param>
/// <returns>a role object</returns>
[DataBaseExistence]
public Role Get(int id)
{
return new Operations().ExecuteQueryToObject<Role>("GKPI_Roles_Get_ByID", Operations.CommandType.StoredProcedure, new Parameter("ID", id));
}
结果将是:

模型和控制器生成
您还可以使用 Geniuses Code Generator 为数据库中的表生成模型类,它是一个简单直接的工具。
要开始,只需点击“表和项目”中的数据库图标 添加连接字符串,然后点击保存,您将在“表和项目”窗口中看到所有列出的表。现在选择表列表中的一个表,您可以在属性窗口中更新该表的属性。
类名:这是您为该表使用的类的名称。例如,表名可以是 NorthWind_Categpries,而类名可以是 Category。
GetByIDStoredProucderName:在这里您可以设置按 ID 检索的存储过程的名称。
Columns:在这里您可以编辑并设置表的列,请参阅图片:
如果您想启用属性的自动验证,只需将 AutoValidation 设置为 true。如果您将列设置为主键或外键,请更改类型为 object 类型,设置错误消息、所有内容或表达式,您需要做的一切都可以通过书写来完成,您也可以通过可视化界面来完成。
现在要查看 C# 代码,您可以双击表列表中的表名,或者右键单击并选择“生成代码”,或者只是单击“<?”图标。任何这些选项都将显示类似如下的内容:
现在您已完成更新和自定义表及其属性,并准备好生成代码。首先,您需要通过点击“表和项目”窗口中的项目图标来编辑项目设置。 您可以设置以下属性:
命名空间:模型的命名空间。
SaveTo:您希望生成的代码保存在哪里。
公司名称:您希望在文件版权头部显示的公司的名称。
名称:项目的名称。
完成后,您只需点击 即可生成包含控制器的 Model 类,并以 Visual Studio 项目的形式呈现。

数据库同步
只需一行代码即可同时执行到多个数据库的 Non Query。

在 app.Config 或 web.config 中添加 DbConnections,并将所有数据库连接字符串传递进去。
日志记录器
要启用日志记录器,只需在设置部分添加以下设置:
<appSetting>
<add key="AllowLogging" value="True"/>
<add key="WriteLogIn" value="WindowsEvent"/>
<add key=SoruceName" value="GeniusesCode.Framework.SQLServer.Test"/>
<add key="LoggerPath" value="C:\Log.text"/>
<add Key=GenericMessage" value="Something wrong happend" />
</appSetting>
新用户将看到 Generic Message 的值。您还可以使用 WindowsEvent 或 Text 来写入日志。
