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

ADO.Net 实现独立于数据库和实体的DataAccessing

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (10投票s)

2012年11月25日

CPOL

11分钟阅读

viewsIcon

27375

downloadIcon

1017

本教程旨在展示一种设计独立于数据库和实体的DataAccess层的基础方法。

下载 DataAccess.zip

引言

本教程旨在展示一种设计独立于数据库(MSSQL、MySql、Oracle、Sql Compact 等)和实体的数据访问层的方法。在这种情况下,有许多框架(例如 Entity Framework)、ORM 可以简化数据库的访问和查询执行。但您可能希望以自己的方式处理操作。

基本思想

我一直很喜欢使用反射和特性。所以这个想法就来源于此。使用特性在 C# 代码中定义数据对象、数据字段,并使用反射将它们映射到数据库表、列等。最后,以任何方式构建查询,并使用指定的数据库类型执行,就是这样。您可以在其官方网站上轻松找到知名数据库的 ADO.Net 库。以下是本教程中使用的列表

MySql Connector for .Net

Oracle Data Provider for .Net

为什么要将数据操作与数据库和实体分离?

拥有独立的数据访问层可能有很多好理由,这里有一些: 

1. 您编写一次数据访问层,并在所有其他项目中使用它。

2. 您可能希望在将来修改实体/表。那么如果您的数据访问层是硬编码的,您将不得不修改您的方法、类等。

3. 经过一番(应该有的)激烈的讨论后,您可能想更换数据库!如果没有切换开关,您将陷入一片混乱。

等等。

如何实现?

下面是结构的一个简单示意图:  

Data access provider

1. 将数据操作与数据库类型隔离

我们将创建一个数据访问提供程序类,该类具有指定的数据库类型和连接字符串,一旦在构造函数中分配,就永远无法更改。然后,我们将设计方法来创建连接、执行查询、读取表,使用 ADO.Net 中定义的接口。这些接口列在下面

  • IDbConnection
  • IDbCommand
  • IDataParameter
  • IDataReader

摆脱数据库类型依赖的关键是使用这些接口。因为 ADO.Net 中所有的数据提供程序都实现了这些接口来创建连接、执行查询、读取行以及 MSSQL、MySql、Oracle、Sql Compact 等所有其他操作。

2. 将数据操作与实体隔离

为了解决这个问题,我们需要使用反射方法来获取将被映射的实体的Type,并查找其属性、属性类型,以便精确地映射到数据库表。此外,我们将借助特性来定义数据库特定的属性,如列名、表名、是否可空、是否是标识符等。下面将列出两个自定义特性来处理这些任务,一个用于定义表,另一个用于定义列

  • DatabaseTableAttribute
  • DatabaseColumnAttribute

我们还将使用泛型方法来完全分离实体。这比将实体的类型传递给方法并将返回值装箱回实体类型要好。例如,我们的 select 方法的签名将如下所示

public List<T> SelectAll<T>() where T : new() 

1. 将类型映射为数据库表 

1.1. 创建特性

在这一部分,我们将创建特性来定义数据库对象,如表和列,以及可接受的属性(唯一、可空、标识符等)。因此,将有两种不同的特性;一种用于表定义,另一种用于列定义。

这是用于定义表的DatabaseTableAttribute的代码。它将在使用反射映射/解析类时帮助我们。它只有一个属性来指定表名。

[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public sealed class DatabaseTableAttribute : Attribute
{
    public string TableName { get; set; }
}     

下面是用于定义列的DatabaseColumnAttribute的代码。它有四个属性,您可以轻松猜测它们的作用。您还可以添加更多属性以获得更真实的结果。此特性的用法将类似于DatabaseTableAttribute。两者都有助于映射类型-表并将数据库特定的字段指定为。

[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
public sealed class DatabaseColumnAttribute : Attribute
{
    public string ColumnName { get; set; }
    public bool IsIdentifier { get; set; }
    public bool IsNullable { get; set; }
    public bool IsAutoValue { get; set; }
} 

1.2. 映射类型

在将被映射的类型/属性有两种不同的情况

1. 类型/属性使用了上面定义的特性。

2. 未使用。

我们将设计映射器,使其接受这两种情况并按预期运行。这意味着映射器方法将具有默认行为来映射未指定的类型/列。例如;即使一个类型没有使用DatabaseTableAttribute,我们也会将其类型名称映射为表名。所以,如果您的表名与您的实体名相同,您就不必使用该特性来定义表名。但是使用特性将有助于提取表结构。

这是用于映射列的ColumnDefinition类的代码。您可以轻松地从名称中了解属性的用途。我想重点介绍AssociatedPropertyName。此属性保存映射类中此列定义的属性名称。这有助于我们在从数据读取器检索行并将其映射回实体时获取值。

public class ColumnDefinition
{ 
    public string ColumnName { get; set; }
    public string AssociatedPropertyName { get; set; }
    public DbType DbType { get; set; }
    public bool IsAutoValue { get; set; }
    public bool IsIdentifier { get; set; }
    public bool IsNullable { get; set; }
 
    public ColumnDefinition() 
    {
        IsAutoValue = false;
        IsIdentifier = false;
        IsNullable = true;
    }
 
    public ColumnDefinition(string columnName, string associatedPropertyName, DbType dbType, bool isAutoValue, bool isIdentifier, bool isNullable)
    {
        ColumnName = columnName;
        AssociatedPropertyName = associatedPropertyName;
        DbType = dbType;
        IsAutoValue = isAutoValue;
        IsIdentifier = isIdentifier;
        IsNullable = isNullable;
    }
} 

这是用于映射表的TableScheme类的代码。  

public class TableScheme
{
    public string TableName { get; set; }
    public List<ColumnDefinition> Columns { get; set; }
}  

要获取类型的 TableScheme,我们需要一个如下所示的方法。GetTableSchemeFromType方法接受一个名为type的参数,并检查该类型是否使用了我们的自定义特性。如果使用,则根据特性中定义的、用户指定的选项进行解析。但是,如果不使用,则使用默认行为进行解析。我们的默认行为是

  1. 如果未使用DatabaseTable特性或未指定TableName,则将类名用作表名。
  2. 如果未使用DatabaseColumn特性或未指定ColumnName,则将属性名用作列名。
  3. 如果未使用DatabaseTable特性,则永远不要检查DatabaseColumn特性,并对属性使用默认行为。
private TableScheme GetTableSchemeFromType(Type type)
{
    TableScheme dbTable = new TableScheme();
    string tableName;
    List<ColumnDefinition> columns = new List<ColumnDefinition>();
    var attributes = type.GetCustomAttributes(typeof(DatabaseTableAttribute), false);
    //If type uses DatabaseTableAttribute, then map it using specified fields like table name.
    if (attributes.Length > 0)
    {
        var attr = attributes[0] as DatabaseTableAttribute;
        //If TableName isn't specified, then use the name of type as table name.
        tableName = string.IsNullOrEmpty(attr.TableName) ? type.Name : attr.TableName;
        //Loop properties of the type.
        foreach (var prop in type.GetProperties())
        {
            var propAttributes = prop.GetCustomAttributes(typeof(DatabaseColumnAttribute), false);
            //If the current property uses DatabaseColumnAttribute, then map it using specified fields like column name, nullable etc.
            if (propAttributes.Length > 0)
            {
                var propAttr = propAttributes[0] as DatabaseColumnAttribute;
                //If ColumnName isn't specified, then use the name of property as column name.
                string columnName = string.IsNullOrEmpty(propAttr.ColumnName) ? prop.Name : propAttr.ColumnName;
                //Get System.Data.DbType of property using the method below.
                DbType dbType = Helper.DbTypeConverter.ConvertFromSystemType(prop.PropertyType);
                bool isAutoValue = propAttr.IsAutoValue;
                bool isIdenifier = propAttr.IsIdentifier;
                bool isNullable = propAttr.IsNullable;
                columns.Add(new ColumnDefinition(columnName, prop.Name, dbType, isAutoValue, isIdenifier, isNullable));
            }
            //If the curent property doesn't use DatabaseColumnAttribute, then behave as default.
            else
            {
                //If property name is "ID" or "[TABLENAME]ID", then set it as identifier.
                bool isIdentifier = prop.Name.ToUpper() == "ID" || prop.Name.ToUpper() == tableName.ToUpper() + "ID";
                columns.Add(new ColumnDefinition(prop.Name, prop.Name, Helper.DbTypeConverter.ConvertFromSystemType(prop.PropertyType),
                    isIdentifier, isIdentifier, true));
            }
        }
    }
    //If type doesn't use DatabaseTableAttribute, then behave as default.
    else
    {
        tableName = type.Name;
        foreach (var prop in type.GetProperties())
        {
            bool isIdentifier = prop.Name.ToUpper() == "ID" || prop.Name.ToUpper() == tableName.ToUpper() + "ID";
            columns.Add(new ColumnDefinition(prop.Name, prop.Name, Helper.DbTypeConverter.ConvertFromSystemType(prop.PropertyType),
                isIdentifier, isIdentifier, true));
        }
    }
    dbTable.TableName = tableName;
    dbTable.Columns = columns;
    return dbTable;
} 

我们需要将系统类型转换为DbType,以便与命令中的参数一起使用。用于将系统类型转换为DbType的帮助类DbTypeConverter的代码如下(来自此处

public class DbTypeConverter
{
    private static Dictionary<Type,DbType> typeMap = new Dictionary<Type, DbType>();
    static DbTypeConverter()
    {
        InitTypes();
    }
    private static void InitTypes()
    {
        typeMap[typeof(byte)] = DbType.Byte;
        typeMap[typeof(sbyte)] = DbType.SByte;
        typeMap[typeof(short)] = DbType.Int16;
        typeMap[typeof(ushort)] = DbType.UInt16;
        typeMap[typeof(int)] = DbType.Int32;
        typeMap[typeof(uint)] = DbType.UInt32;
        typeMap[typeof(long)] = DbType.Int64;
        typeMap[typeof(ulong)] = DbType.UInt64;
        typeMap[typeof(float)] = DbType.Single;
        typeMap[typeof(double)] = DbType.Double;
        typeMap[typeof(decimal)] = DbType.Decimal;
        typeMap[typeof(bool)] = DbType.Boolean;
        typeMap[typeof(string)] = DbType.String;
        typeMap[typeof(char)] = DbType.StringFixedLength;
        typeMap[typeof(Guid)] = DbType.Guid;
        typeMap[typeof(DateTime)] = DbType.DateTime;
        typeMap[typeof(DateTimeOffset)] = DbType.DateTimeOffset;
        typeMap[typeof(byte[])] = DbType.Binary;
        typeMap[typeof(byte?)] = DbType.Byte;
        typeMap[typeof(sbyte?)] = DbType.SByte;
        typeMap[typeof(short?)] = DbType.Int16;
        typeMap[typeof(ushort?)] = DbType.UInt16;
        typeMap[typeof(int?)] = DbType.Int32;
        typeMap[typeof(uint?)] = DbType.UInt32;
        typeMap[typeof(long?)] = DbType.Int64;
        typeMap[typeof(ulong?)] = DbType.UInt64;
        typeMap[typeof(float?)] = DbType.Single;
        typeMap[typeof(double?)] = DbType.Double;
        typeMap[typeof(decimal?)] = DbType.Decimal;
        typeMap[typeof(bool?)] = DbType.Boolean;
        typeMap[typeof(char?)] = DbType.StringFixedLength;
        typeMap[typeof(Guid?)] = DbType.Guid;
        typeMap[typeof(DateTime?)] = DbType.DateTime;
        typeMap[typeof(DateTimeOffset?)] = DbType.DateTimeOffset;
    }
    public static DbType ConvertFromSystemType(Type systemType)
    {
        if (typeMap.ContainsKey(systemType))
            return typeMap[systemType];
        else
            throw new InvalidCastException("The system type is not convertable.");
    }
} 

2. 隔离的数据访问 

现在,我们将设计一个DataAccess层来接管所有的数据库工作。它将独立于数据库类型,如 mssql、mysql、oracle 等。所以,我们将使用名为IDbConnection, IDbCommand, IDataParameter, IDataReader 的接口来将数据操作与数据库类型分离。

DataAccessProvider类的构造函数如下

public DatabaseType Database { get; private set; }
public string ConnectionString { get; private set; } 
public DataAccessProvider(string connectionString, DatabaseType databaseType)
{
    Database = databaseType;
    ConnectionString = connectionString;
    using (var conn = CreateConnection())
    {
        conn.Open();
    }
}  

构造函数以连接字符串和数据库类型作为参数,并通过一种简单(非常简单)的方式测试连接。ConnectionStringDatabase属性是只读的。它们将用于我们稍后创建的许多方法的默认选择。顺便说一句,DatabaseType是一个枚举,如下所示

public enum DatabaseType
{
    MSSql,
    MySql,
    Oracle,
    SqlCompact
} 

这是我们的路线图;首先使用指定的连接字符串和数据库类型创建连接。然后在此连接上创建命令。创建命令时,也创建要使用的参数。并且只使用接口!

现在,让我们编写创建数据库连接的方法。有三个重载的CreateConnection方法。第一个使用DataAccessProvider类的构造函数传递的连接字符串和数据库类型。第二个提供一个选项,用于指定一个临时连接字符串,以创建与构造函数中传递的数据库类型相对应的连接。第三个一起接受两个必需的参数并创建一个连接以继续。

public IDbConnection CreateConnection()
{
    return CreateConnection(ConnectionString,Database);
}
public IDbConnection CreateConnection(string connectionString)
{
    return CreateConnection(connectionString,Database);
}
public IDbConnection CreateConnection(string connectionString, DatabaseType database)
{
    IDbConnection connection;
    switch (database)
    {
        case DatabaseType.MSSql:
            connection = new SqlConnection();
            break;
        case DatabaseType.MySql:
            connection = new MySqlConnection();
            break;
        case DatabaseType.Oracle:
            connection = new OracleConnection();
            break;
        case DatabaseType.SqlCompact:
            connection = new SqlCeConnection();
            break;
        default:
            connection = new SqlConnection();
            break;
    }
    connection.ConnectionString = connectionString;
    return connection;
}  

我们将使用相同的方法创建命令。但是应该有一个方法来获取将为其创建命令的连接的数据库类型。为此,我们有一个名为GetDatabaseType的方法,它接受IDbConnection作为连接,并找到其数据库类型。您可以在下面看到三个重载的CreateCommand方法。第一个将空字符串和 null 参数传递给第三个方法。然后第三个方法使用构造函数中传递的默认数据库类型创建命令,并将命令字符串设置为空。第二个提供一个选项,用于指定一个临时连接,以创建具有构造函数中传递的数据库类型的命令。第三个一起接受两个必需的参数并创建一个命令以继续。

public IDbCommand CreateCommand()
{
    return CreateCommand(string.Empty, null);
}
public IDbCommand CreateCommand(IDbConnection connection)
{
    return CreateCommand(string.Empty, connection);
}
public IDbCommand CreateCommand(string commandText, IDbConnection connection)
{
    IDbCommand command;
    DatabaseType database = connection == null ? Database : GetDatabaseType(connection);
    switch (database)
    {
        case DatabaseType.MSSql:
            command = new SqlCommand(commandText, (SqlConnection)connection);
            break;
        case DatabaseType.MySql:
            command = new MySqlCommand(commandText, (MySqlConnection)connection);
            break;
        case DatabaseType.Oracle:
            command = new OracleCommand(commandText, (OracleConnection)connection);
            break;
        case DatabaseType.SqlCompact:
            command = new SqlCeCommand(commandText, (SqlCeConnection)connection);
            break;
        default:
            command = new SqlCommand(commandText, (SqlConnection)connection);
            break;
    }
    return command;
} 

下面可以看到上面提到的GetDatabaseType方法的代码

private DatabaseType GetDatabaseType(IDbConnection connection)
{
    DatabaseType database = DatabaseType.Unknown;
    if (connection is SqlConnection)
    {
        database = DatabaseType.MSSql;
    }
    else if (connection is MySqlConnection)
    {
        database = DatabaseType.MySql;
    }
    else if (connection is OracleConnection)
    {
        database = DatabaseType.Oracle;
    }
    else if (connection is SqlCeConnection)
    {
        database = DatabaseType.SqlCompact;
    }
    return database;
} 

下一步:创建参数。我们这里也有三个重载的方法。第一个调用第三个方法,并创建一个DbTypeDbType.String的参数,用于构造函数中传递的默认数据库类型。第二个提供一个选项来指定参数名称、数据库值类型和值。第三个接受四个参数并为指定的数据库创建一个参数。

public IDataParameter CreateParameter()
{
    return CreateParameter("", DbType.String, null, Database);
}
public IDataParameter CreateParameter(string parameterName, DbType dbType, object value)
{
    return CreateParameter(parameterName, dbType, value, Database);
}
public IDataParameter CreateParameter(string parameterName, DbType dbType, object value, DatabaseType database)
{
    IDataParameter parameter = null;
    switch (database)
    {
        case DatabaseType.MSSql:
            parameter = new SqlParameter() { ParameterName = parameterName, DbType = dbType, Value = value };
            break;
        case DatabaseType.MySql:
            parameter = new MySqlParameter() { ParameterName = parameterName, DbType = dbType, Value = value };
            break;
        case DatabaseType.Oracle:
            parameter = new OracleParameter() { ParameterName = parameterName, DbType = dbType, Value = value };
            break;
        case DatabaseType.SqlCompact:
            parameter = new SqlCeParameter() { ParameterName = parameterName, DbType = dbType, Value = value};
            break;
        case DatabaseType.Unknown:
            break;
        default:
            parameter = new SqlParameter() { ParameterName = parameterName, DbType = dbType, Value = value };
            break;
    }
    return parameter;
} 

在目前这个点,我们可以无依赖地创建连接、命令和参数。所以,让我们使用这些方法。现在我们将创建ExecuteReader方法来执行查询并将结果作为IDataReader检索。第一个使用指定的查询字符串和一个空的参数列表调用第二个方法。第二个提供一个选项来指定可以通过我们上面描述的CreateParameters方法创建的参数。第二个方法使用CreateConnection方法创建一个连接,并使用CreateCommand方法在此连接上创建一个命令。您可以看到没有任何像SqlDataReaderMySqlParameter这样的变量或参数。所有操作都围绕接口进行!

public IDataReader ExecuteReader(string query)
{
    return ExecuteReader(query,new List<IDataParameter>());
}
public IDataReader ExecuteReader(string query, List<IDataParameter> parameters)
{
    IDbConnection connection = CreateConnection();
    connection.Open();
    IDbCommand command = CreateCommand(query, connection);
    foreach (var param in parameters)
    {
        command.Parameters.Add(param);
    }
    return command.ExecuteReader(CommandBehavior.CloseConnection);
} 

ExecuteReader方法一样,我们将创建ExecuteNonQuery方法来处理 insert、update 和 delete 等查询。这里有两个方法。第一个使用指定的查询字符串和一个空的参数列表调用第二个方法。第二个提供一个选项来指定可以通过我们CreateParameters或以其他方式创建的参数。然后使用我们的CreateConnection方法创建连接,在该连接上创建命令,将参数添加到命令并执行它。您可以看到这些方法也独立于派生类型。

public int ExecuteNonQuery(string query)
{
    return ExecuteNonQuery(query,new List<IDataParameter>());
}
public int ExecuteNonQuery(string query, List<IDataParameter> parameters)
{
    using (IDbConnection connection = CreateConnection())
    {
        connection.Open();
        IDbCommand command = CreateCommand(query, connection);
        foreach (var param in parameters)
        {
            command.Parameters.Add(param);
        }
        return command.ExecuteNonQuery();
    }
} 

在这种情况下,我们可以完全独立于数据库类型来创建连接并在这些连接上执行查询。下一步是将数据读取器解析为实体,或者可能是动态对象。下面是将数据读取器解析为实体的名为ParseDataReaderToEntityList的方法。它接受两个参数;第一个是从我们的ExecuteReader方法中检索到的数据读取器,第二个是使用GetTableSchemeFromType方法映射的TableScheme,该方法接受一个类型并将其映射为表,如上所述。然后使用TableScheme中定义的AssociatedPropertyNameColumnName属性来从数据读取器中获取值,并将其设置在类型T的创建实例上。之后,将实例添加到列表中,并在最后返回列表。

private List<T> ParseDataReaderToEntityList<T>(IDataReader reader, TableScheme dbTable) where T : new()
{
    Type type = typeof(T);
    List<T> result = new List<T>();
    while (reader.Read())
    {
        T t = new T();
        foreach (var column in dbTable.Columns)
        {
            type.GetProperty(column.AssociatedPropertyName).SetValue(t, reader[column.ColumnName], null);
        }
        result.Add(t);
    }
    return result;
} 

现在让我们使用上述所有方法从表中选择所有记录,并将其映射到与正确表匹配的实体。为此,我们将创建一个名为SelectAll<T> 的泛型方法,如下所示。第一个将空字符串作为 where 子句传递给第二个方法。然后第二个方法处理工作。第二个接受一个名为 whereClause 的字符串参数来过滤查询中的记录。此方法以非常简单的方式构建 select 查询,并使用我们的ExecuteReader方法执行它。然后使用ParseDataReaderToEntityList<T>方法将读取器解析为T类型,并作为result返回。第三个提供一个选项来指定完全自定义的 select 查询和查询参数。然后完成第二个方法的工作。 

public List<T> SelectAll<T>() where T : new()
{
    return SelectAll<T>(string.Empty);
}

public List<T> SelectAll<T>(string whereClause) where T : new()
{
    List<T> result = new List<T>();
    Type type = typeof(T);
    TableScheme dbTable = GetTableSchemeFromType(type);
    var query = "Select * From " + dbTable.TableName + " " + whereClause;
    IDataReader reader = ExecuteReader(query);
    result = ParseDataReaderToEntityList<T>(reader,dbTable);
    return result;
}

public List<T> SelectAll<T>(string selectQuery, List<IDataParameter> parameters) where T: new()
{
    List<T> result = new List<T>();
    TableScheme dbTable = GetTableSchemeFromType(typeof(T));
    IDataReader reader = ExecuteReader(selectQuery, parameters);
    result = ParseDataReaderToEntityList<T>(reader, dbTable);
    reader.Close();
    return result;
}  

我们可能希望将数据记录映射到动态对象而不是实体类。在下面的方法中,我们使用ExecuteReader方法来检索具有作为方法参数传递的查询字符串的IDataReader对象。然后,我们循环遍历读取器中的所有记录,并将记录的每个列添加到ExpandoObject中,并附带其值(ExpandoObject 表示一个可以在运行时动态添加和删除成员的对象)。此处请参阅SelectAll方法的代码

public List<dynamic> SelectAll(string tableName)
{
    List<dynamic> result = new List<dynamic>();
    using (IDataReader reader = ExecuteReader("Select * From "+ tableName))
    {
        while (reader.Read())
        {
            dynamic expando = new ExpandoObject();
            for (int i = 0; i < reader.FieldCount; i++)
            {
                string columnName = reader.GetName(i);
                ((IDictionary<String, Object>)expando).Add(columnName, reader[columnName]);
            }
            result.Add(expando);
        }
    }
    return result;
} 

我想您明白了。现在您可以轻松地创建如SelectUpdateDelete等方法,只需努力构建查询即可。或者不用管它,只需使用我们的ExecuteNonQuery方法和查询字符串即可。

下面,您可以看到一个删除记录的示例泛型方法。 此方法接受一个名为databaseObject的参数,类型为T。首先获取类型的 TableScheme,然后检查它是否有一个标识符列,通过 lambda 表达式。如果有,则使用此标识符在 where 子句中构建查询; 否则,将所有列用于 where 子句,以进行精确匹配以删除正确的记录。  

public int Delete<T>(T databaseObject) where T : new()
{
    int result = new int();
    Type type = typeof(T);
    TableScheme dbTable = GetTableSchemeFromType(type);
    List<IDataParameter> parameters = new List<IDataParameter>();
 
    bool canExecute = false;
    bool hasIdentifier = dbTable.Columns.Where((x) => { return x.IsIdentifier; }).Count() > 0;
 
    StringBuilder queryBuilder = new StringBuilder("Delete From " + dbTable.TableName + " Where ");
 
    if (hasIdentifier) 
    {
        ColumnDefinition column = dbTable.Columns.Where((x) => { return x.IsIdentifier; }).First();
        queryBuilder.Append(column.ColumnName + "= @" + column.ColumnName);
        parameters.Add(CreateParameter("@" + column.ColumnName,
            column.DbType,
            type.GetProperty(column.AssociatedPropertyName).GetValue(databaseObject, null)));
        canExecute = true;
    }
    else
    {
        foreach (var column in dbTable.Columns)
        {
            queryBuilder.Append(column.ColumnName + "= @" + column.ColumnName + " And ");
            parameters.Add(CreateParameter("@" + column.ColumnName,
                column.DbType,
                type.GetProperty(column.AssociatedPropertyName).GetValue(databaseObject, null)));
            if (!canExecute)
                canExecute = true;
        }
        queryBuilder.Append("_END_");
        queryBuilder = queryBuilder.Replace(" And _END_","");
    }
 
    if (canExecute)
    {
        result = ExecuteNonQuery(queryBuilder.ToString(), parameters);
    }
 
    return result;
} 

让我们做一个改进。在 Select<T>方法中,我们将 where 子句作为字符串传递。但是我们可以使用一种更集成语言的方式:委托。在这里,.Net 框架已经有一个名为Func<T,U>的委托可以被使用。我最好在代码中描述它。下面您将看到重载的Update<T>方法。第一个获取一个对象,构建一个查询并执行它。但是第二个方法使用SelectAll<T>方法来获取所有记录,并使用委托来更新指定的记录。然后您就可以使用 lambda 表达式作为方法参数了。在这一点上,很清楚,从第二个方法中在循环中调用第一个Update<T>方法来处理所有匹配的记录并不是最好的方法,如果您关心(并且必须)性能的话。当然,您可以根据需要进行更改。缓存机制可以解决性能问题。

public int Update<T>(T databaseObject) where T : new()
{
    int result = new int();
    Type type = typeof(T);
    TableScheme dbTable = GetTableSchemeFromType(type);
    List<IDataParameter> parameters = new List<IDataParameter>();
    StringBuilder queryBuilder = new StringBuilder("Update " + dbTable.TableName + " Set ");
    StringBuilder whereClauseBuilder = new StringBuilder(" where ");

    bool appendWhereClause = false;

    foreach (var column in dbTable.Columns)
    {
        if (column.IsIdentifier)
        {
            whereClauseBuilder.Append(column.ColumnName);
            whereClauseBuilder.Append(" = @");
            whereClauseBuilder.Append(column.ColumnName);
            whereClauseBuilder.Append(" and ");

            parameters.Add(CreateParameter("@"+column.ColumnName,
                column.DbType,
                type.GetProperty(column.AssociatedPropertyName).GetValue(databaseObject,null)));

            appendWhereClause = true;
        }
        else if (!column.IsAutoValue)
        {
            queryBuilder.Append(column.ColumnName);
            queryBuilder.Append(" = @");
            queryBuilder.Append(column.ColumnName);
            queryBuilder.Append(",");

            parameters.Add(CreateParameter("@" + column.ColumnName,
                    column.DbType,
                    type.GetProperty(column.AssociatedPropertyName).GetValue(databaseObject, null)));
        }
    }
    queryBuilder.Append("|end");
    queryBuilder.Replace(",|end", "");
    whereClauseBuilder.Append("|end");
    whereClauseBuilder.Replace(" and |end", "");

    queryBuilder.Append(appendWhereClause ? whereClauseBuilder.ToString() : "");
    result = ExecuteNonQuery(queryBuilder.ToString(), parameters);

    return result;
}

public int Update<T>(Func<T, bool> selectionPredicate, Action<T> updatePredicate) where T:new()
{
    int result = new int();

    foreach (var item in Select(selectionPredicate))
    {
        updatePredicate(item);
        result += Update<T>(item);
    }

    return result;
}  

用法

我们创建的数据访问提供程序接管数据工作,并与数据库类型和实体分离。用法如下: 

DataAccessProvider dap = new DataAccessProvider(Settings.Default.ConnStr, DatabaseType.MySql);
dap.Update<Test>(x => x.Name == "Halil", x => x.Name = "İbrahim"); 

我还将示例项目附加到了文章中。它包含更多的方法,如SelectUpdateDelete等。当然,它并不包含完整和最佳的解决方案,但我希望它能有助于理解如何实现。感谢您的阅读。  

关注点

ADO.Net 有助于将数据库操作与数据库类型分离。我建议使用接口而不是派生类。这样,代码将更加灵活,并且可以轻松更改。  

© . All rights reserved.