EasySqlCe: 在 C# 中轻松访问 SQL CE






4.81/5 (9投票s)
在 C# 中轻松访问 SQL Compact Edition 数据库的方法
序言
本文实际上是对SQLCEtools的一次更新。该解决方案旨在方便地使用便携式数据库 SQL Compact Edition。旧版本中的一些问题需要解决:首先,旧版本无法在安装了 .NET 3.5 的系统上运行,而我的新工作环境就是这种情况。此外,SQLCEtools的代码相当复杂且存在大量重复。因此,我对代码进行了重写和重构。我将其重命名为 EasySqlCe,因为它旨在提供一种简单的方式来使用 SQL-CE。由于它与我编写的静态 SQLCEtools 方法大相径庭,因此我为此编写了这篇新文章。与 SQLCEtools 的主要区别在于:
- 针对 .NET 3.5 及更高版本编写(而非 .NET 4.0 及更高版本)。
- 您需要引用 EasySqlCe:
using EasySqlCe
。 - 您需要实例化一个 '
AccessPoint
',请参阅演示项目。 - 文件名、文件路径和密码将在
AccessPoint
实例的相应属性中设置。您需要考虑安全性和/或密码保护。 - 是否使用 'LIKE' 而非 '=' 来处理 Where 子句,需要在 '
UseLikeStatement
' 属性中设置。另外,还需要指定通配符 ('%') 的使用位置(前后)。 - DateTime 也需要是可空的:'
DateTime?
',这样就不需要 'DateTimeNull
' 和大量的额外代码。我不明白当初为什么会使用它,可能是因为当时我才开始用 C# 编程。 - 当使用模板类时,会提供一个名为
UID
的唯一标识符属性。
背景
任何使用过 SQL-CE 的人都一定经历过执行普通数据库任务所需的繁琐步骤。Linq-to-SQL 对 SQL 数据库有所帮助,但对于 SQL-CE 来说不易启动。此外,对于需要数据库的简单任务,应该提供一个简单的解决方案。因此,我创建了这个项目。只需按照一套规则声明一个类,您就可以访问 SQL-CE 数据库,而无需为 INSERT、SELECT、DELETE 等 SQL 语句编写方法。您可以通过 DEMO 表单进行尝试。
使用代码
要读取数据,只需创建一个与数据表同名的类,并添加与数据表中字段名称和类型匹配的属性。此外,在您的表单或包含访问数据库的方法的类中,实例化一个包含数据库名称、路径和密码的数据库对象。显然,必须安装 SQL Server Compact Edition 3.5。数据库必须添加到解决方案中(只需将其拖到解决方案资源管理器中,无需 TableAdapters 或其他任何内容)。不要忘记添加对 System.Data.SqlServerCe
的引用,并且在部署应用程序时,您需要将 SQL-CE DLL 复制到您的应用程序文件夹:
- sqlceca35.dll
- sqlcecompact35.dll
- sqlceer35EN.dll
- sqlceme35.dll
- sqlceoledb35.dll
- sqlceqp35.dll
- sqlcese35.dll
就是这样。请记住,应包含某种形式的安全措施以**保护密码**。您需要尝试使用这些方法来熟悉它们。包含以下方法:
CreateDataBase
:如果具有该名称的数据库不存在,则创建一个全新的数据库CheckTable
:检查是否存在与所引用对象同名的表CreateTable
:创建一个与所引用类匹配的表Insert
Select
CheckIndex
:检查是否存在特定对象的索引CreateIndex
:为特定属性/字段创建一个新索引TableDirectReader
:用于批量读取的更快函数更新
删除
ResetIdentifier
:重置标识符,仅当表为空时
演示表单展示了一些功能。我没有为删除和更新添加示例,因为这些方法的使用方式相同。代码中也提供了说明。在演示中,我们展示了“帮助程序”类应该是什么样子。当类继承自 EasySqlCe.Template
时,会提供一个名为“UID
”的唯一标识符。(不一定需要使用此模板。请注意,如果您不继承模板类,请记住声明一个前面带有属性“[UniqueIdentifier]
”的属性):
/// <summary>
/// Declare a helper-class. Note: the name of the class must be exactly
/// the name of the DataTable.
/// </summary>
private class TestTable : EasySqlCe.Template
{
public TestTable() { }
public TestTable(string name)
{
if (!String.IsNullOrEmpty(name)) this.Name = name;
else this.Name = null;
}
public string Name { get; set; }
public DateTime? Date { get; set; }
public bool? Checked { get; set; }
}
必须实例化一个 EasySqlCe.AccessPoint
对象。我将其放在部分类中,以便每个方法都可以使用它。但是,也可以选择在每个方法中声明它:
private EasySqlCe.AccessPoint DataBase;
在事件处理程序中演示了这些方法。这是执行 select 语句的方式:
var founddata = DataBase.Select(search);
如果您想读取整个表,看起来会是这样:
var founddata = DataBase.Select(new TestTable());
如果您想搜索特定记录,可以使用重载的构造函数:
var founddata = DataBase.Select(new TestTable("mr Bean"));
insert 语句看起来是这样的:
DataBase.Insert(searchItem);
还能比这更简单吗?
工作原理
这是 'Select' 方法核心的样子:
if (!AccessPointReady()) return null;
SetPropertyInfosAndUniqueIdentifier(SearchItem);
List<T> dataList = new List<T>();
string selectStatement = ConstructSQLStatementSelect(SearchItem);
SqlCeConnection connection = new SqlCeConnection(ConnectionString());
try
{
if (connection.State == ConnectionState.Closed) connection.Open();
SqlCeCommand command = GetSqlCeCommand(connection, selectStatement);
AddParametersWithValuesFromProperties(SearchItem, command, Suffix.Where);
SqlCeResultSet ResultSet =
command.ExecuteResultSet(ResultSetOptions.Scrollable);
if (ResultSet.HasRows) while (ResultSet.Read())
dataList.Add(FillObjectWithResultSet(new T(), ResultSet));
return dataList;
}
逐行分析:
if (!AccessPointReady()) return null;
检查是否提供了文件名。
SetPropertyInfosAndUniqueIdentifier(SearchItem);
执行以下操作:
void SetPropertyInfosAndUniqueIdentifier<T>(T SearchItem) where T : class, new()
{
PropertiesOfT = typeof(T).GetProperties();
this.CurrentUniqueIdentifier = PropertiesOfT.First(p =>
(p.GetCustomAttributes(typeof(UniqueIdentifier), true)).Length > 0);
}
私有属性 PropertiesOfT
用于存储 T
的属性,以便稍后使用,并且可以通过其前面的属性识别唯一标识符。唯一标识符存储在私有属性中。
然后:
string selectStatement = ConstructSQLStatementSelect(SearchItem);
执行以下操作:
string ConstructSQLStatementSelect<T>(T ObjectOfInterest) where T : class, new()
{
string sqlStatement = "SELECT " +
GetFieldNamesFromProperties(ObjectOfInterest, false) + " FROM " +
typeof(T).Name;
string whereClause = GetWhereClauseFromProperties(ObjectOfInterest);
sqlStatement += whereClause.Length == 0 ? "" : " WHERE " + whereClause;
return sqlStatement;
}
GetFieldNamesFromProperties 是一个重要的方法,最终会产生以下操作:
string GetNamesFromProperties
(bool ExcludeUniqueIdentifier,
string Prefix,
Suffix AddToParameterName)
{
string betweenProperties = ", " + Prefix;
StringBuilder nameString = new StringBuilder();
foreach (PropertyInfo propertyOfT in PropertiesOfT)
{
if (!ExcludeUniqueIdentifier ||
(propertyOfT.GetCustomAttributes(typeof(UniqueIdentifier),
true)).Length == 0)
{
if (nameString.Length > 0)
nameString.Append(betweenProperties);
else nameString.Append(Prefix);
nameString.Append(propertyOfT.Name);
if (AddToParameterName != Suffix.None)
nameString.Append(AddToParameterName.ToString());
}
}
return nameString.ToString();
}
实际上,是将引用的对象的每个属性的名称添加到包含在 'SELECT' 语句中的字符串中。根据参数,唯一标识符可能会被排除或不被排除。如有必要,会添加前缀(例如,在 SQL 语句中字段名称使用多次的情况下,添加 1、2 等)。
另一个重要的方法是创建一个 'WHERE' 子句:
string GetEquationsFromProperties<T>
(T ObjectOfInterest,
Suffix AddToParameterName,
Separator SeparateBy) where T : class, new()
{
string betweenProperties = " " + this.WhereClauseComparer + " @";
StringBuilder equations = new StringBuilder(4096);
foreach (PropertyInfo propertyOfT in PropertiesOfT)
{
object valueOfProperty = propertyOfT.GetValue(ObjectOfInterest, null);
if (valueOfProperty != null &&
(!ExcludeUniqueIdentifier(AddToParameterName) ||
(propertyOfT.GetCustomAttributes(typeof(UniqueIdentifier),
true)).Length == 0))
{
if (equations.Length > 0) equations.Append(Separate(SeparateBy));
equations.Append(propertyOfT.Name);
equations.Append(betweenProperties);
equations.Append(propertyOfT.Name.ToLower());
equations.Append(AddToParameterName.ToString());
}
}
return equations.ToString();
}
创建的等式根据参数,用“,”或“AND”分隔
当继续分析 Select 方法时,我们进入 try-catch 块,并使用以下方法:
void AddParametersWithValuesFromProperties<t>
(T ObjectOfInterest,
SqlCeCommand Command,
Suffix AddToParameterName) where T : class, new()
{
bool excludeUniqueIdentifier = ExcludeUniqueIdentifier(AddToParameterName);
foreach (PropertyInfo propertyOfT in PropertiesOfT)
{
if (!excludeUniqueIdentifier || propertyOfT != CurrentUniqueIdentifier)
{
object propertyValue = propertyOfT.GetValue(ObjectOfInterest, null);
string parameterName = "@" + propertyOfT.Name.ToLower()
+ AddToParameterName.ToString().ToLower();
if (propertyValue != null && _UseLikeStatement)
propertyValue = AddWildCard(propertyValue);
if (propertyValue == null) propertyValue = DBNull.Value;
if (!Command.Parameters.Contains(parameterName))
Command.Parameters.Add(parameterName,
GetSqlDbTypeFromDotNetType(propertyOfT.PropertyType));
Command.Parameters[parameterName].Value = propertyValue;
}
}
}
在此方法中,通过添加参数和值来进一步准备 SqlCeCommand
——因此得名。请注意,当 UseLikeStatement
标志设置为 true 时,可以在数据之前或之后添加通配符。
最后,在执行 SqlCeCommand 后,会实例化一个新对象并用 SqlCeResultSet
中的数据填充:
T FillObjectWithResultSet<T>(T dataItem,
SqlCeResultSet ResultSet) where T : class, new()
{
foreach (PropertyInfo propertyOfT in PropertiesOfT)
{
PropertyInfo singlepropinf = typeof(T).GetProperty(propertyOfT.Name);
int ordinal = ResultSet.GetOrdinal(propertyOfT.Name);
object result = ResultSet.GetValue(ordinal);
if (result is DBNull) singlepropinf.SetValue(dataItem, null, null);
else singlepropinf.SetValue(dataItem, result, null);
}
return dataItem;
}
我试图尽可能使用能解释其功能的名称来命名方法和属性等,并且我也尽量进行了重构,所以我希望这段代码比 SQLCEtools 的代码更容易理解。
历史
我对 'readdata' 方法的第一次尝试是在 2011 年 3 月 2 日发布到 'stackoverflow.com' 上的一个问题。SQLCEtools 在该网站上的第一个版本发布于 2011 年 8 月 6 日。EasySqlCe 的第一个版本提交于 2012 年 10 月 24 日。
在此次更新中,修复了 'Update' 函数中的一个 bug,以及其他一些细节。