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

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.81/5 (9投票s)

2012 年 10 月 24 日

GPL3

6分钟阅读

viewsIcon

43771

downloadIcon

1562

在 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,以及其他一些细节。

© . All rights reserved.