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

在经典的 ADO.NET 中包含

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (4投票s)

2012 年 7 月 5 日

CPOL

3分钟阅读

viewsIcon

22000

downloadIcon

696

在经典的 ADO.NET 中实现和使用 INCLUDE 方法。

引言

EF 中的 INCLUDE 方法是一个非常有用的工具,存在于 ObjectSetObjectQuery 类中。它有助于选择您需要使用的所有相关实体,并且只需一次调用即可完成,从而获得出色的性能。

如果由于某种原因您无法在项目中使用 EF,则无法使用 INCLUDE 方法,因为在经典的 ADO.NET(非 EF)中,它不存在。

在经典的 ADO.NET 中,大多数情况下,您需要获取一个实体及其相关实体的数据,您需要多次调用数据库来选择所需的所有信息,这非常耗时。

因此,我为经典的 ADO.NET 创建了 INCLUDE 方法,以提高从相关实体获取数据时的性能。

Using the Code

要使用此方法,您只需要遵循以下规则

  • 数据库中的每个表都有其 Entity 类。
  • 在表中及其 Entity 类中使用相同的字段名称保持一致。
  • 创建适当的存储过程以实现 INCLUDE 方法。
  • EntityBase 类继承以创建您的实体。
  • 使用 SqlHelper 类与您的数据库交互。

EntityBase 类

此类只有一个方法来获取在子类中标记为 OUTPUT 的字段。

public abstract class EntityBase
{
    protected EntityBase()
    {
    }

    public string[] GetOutputFieldNames()
    {
        List<string> result = new List<string>();

        PropertyInfo[] properties = this.GetType().GetProperties();
        foreach (PropertyInfo property in properties)
        {
            FieldDirectionAttribute[] fieldDirectionsAtt = 
              (FieldDirectionAttribute[])property.GetCustomAttributes(
               typeof(FieldDirectionAttribute), true);
            if (fieldDirectionsAtt != null && fieldDirectionsAtt.Length > 0 &&
                (fieldDirectionsAtt[0].Direction == ParameterDirection.InputOutput ||
                fieldDirectionsAtt[0].Direction == ParameterDirection.Output))
                result.Add(property.Name);
        }

        return result.ToArray();
    }
}

SqlHelper 类

ExecuteQueryStoredProcedure 方法是一个基本方法,用于使用 ADO.NET 和存储过程从数据库填充 dataset 对象。

public sealed class SqlHelper
{
    public static DataSet ExecuteQueryStoredProcedure(SqlConnection sqlConnection, 
           string storedProcedureName, List<SqlParameter> parameters)
    {
        DataSet result = new DataSet();

        using (SqlCommand sqlCommand = new SqlCommand())
        {
            sqlCommand.Connection = sqlConnection;
            sqlCommand.CommandType = CommandType.StoredProcedure;
            sqlCommand.CommandText = storedProcedureName;

            if (parameters != null && parameters.Count > 0)
                sqlCommand.Parameters.AddRange(parameters.ToArray());

            if (sqlCommand.Connection.State != ConnectionState.Open)
                sqlCommand.Connection.Open();

            SqlDataAdapter sqlDataAdapter = new SqlDataAdapter();
            sqlDataAdapter.SelectCommand = sqlCommand;
            sqlDataAdapter.Fill(result);
        }

        return result;
    }

ExecuteQueryStoredProcedure 方法负责使用 INCLUDE 查询根据要填充的 Entity 类型填充 datasetCreateDynamicQuery 是根据 Entity 类型创建 INCLUDE 查询的方法。创建 INCLUDE 查询后,此方法调用前一个方法,使用 INCLUDE 脚本填充 dataset,最后将主键放入 dataset 中传入的表中。

private static DataSet ExecuteQueryStoredProcedure<T>(SqlConnection sqlConnection, 
        string storedProcedureName, List<SqlParameter> parameters, 
        params string[] includePaths) where T : EntityBase
{
    DataSet result = null;

    List<string> includePathList;
    string includeQuery = CreateDynamicQuery<T>(out includePathList, includePaths);

    if (parameters == null)
        parameters = new List<SqlParameter>();

    parameters.Add(new SqlParameter("@Include", includeQuery));

    result = ExecuteQueryStoredProcedure(sqlConnection, storedProcedureName, parameters);

    if (result != null && result.Tables.Count > 0)
    {
        Type mainType = typeof(T);
        result.Tables[0].TableName = mainType.Name;
        SetPrimaryKey(result.Tables[0], mainType);

        if (result.Tables.Count == (includePathList.Count + 1))
        {
            for (int i = includePathList.Count; i > 0; i--)
            {
                result.Tables[i].TableName = includePathList[i - 1];
                Type entityType = GetType(mainType, includePathList[i - 1]);
                
                SetPrimaryKey(result.Tables[i], entityType);
            }
        }
    }

    return result;
}

GetEntity 方法是此类 的核心;它从 DataRow 获取一个实体(从 dataset 获取相关实体)。

private static EntityBase GetEntity(DataRow dataRow, Type type, 
DataSet dataSet, EntityBase dontIncludeEntity)
{
    EntityBase result = null;

    if (dataRow != null && dataRow.Table.Columns.Count > 0)
    {
        result = (EntityBase)Activator.CreateInstance(type);
        DataColumnCollection dataColumns = dataRow.Table.Columns;

        foreach (PropertyInfo property in type.GetProperties())
        {
            if (dataColumns.Contains(property.Name))
            {   //If the property is a column in the table.
                object propertyValue = null;

                if (dataRow != null && dataRow[property.Name] != null)
                {
                    propertyValue = dataRow[property.Name];

                    if (propertyValue != null)
                    {
                        if (propertyValue is DBNull)
                            propertyValue = null;
                        else if (property.PropertyType.IsEnum)
                        {
                            //if (Enum.IsDefined(property.PropertyType, propertyValue)
                            propertyValue = Enum.Parse
                                            (property.PropertyType, propertyValue.ToString());
                        }
                    }
                }

                property.SetValue(result, propertyValue, null);
            }
            else if (dataSet != null && dataSet.Tables.Count > 0 && 
                                  dataSet.Tables.Contains(property.Name))
            {
                //If the property is not a column in the table (It's an Entity property)
                //Fill Entity property out using data from a related table (include).

                Type entityType = property.PropertyType;
                Type dontIncludeType = 
                   (dontIncludeEntity != null) ? dontIncludeEntity.GetType() : null;

                DataTable includedDataTable = dataSet.Tables[property.Name];

                if (!entityType.IsArray && (dontIncludeType == null ||
                    dontIncludeType != null && dontIncludeType != entityType))
                {
                    object[] foreignKeyValues = GetForeignKeyValues(dataRow, property);
                    DataRow includedRow = includedDataTable.Rows.Find(foreignKeyValues);

                    object entityValue = GetEntity(includedRow, entityType, dataSet, result);
                    result.GetType().GetProperty(property.Name).SetValue(result, entityValue, null);
                }
                else if (!entityType.IsArray)
                {
                    result.GetType().GetProperty(property.Name).SetValue
                                        (result, dontIncludeEntity, null);
                }
            }
        }
    }

    return result;
}

实体类

这是一个 Entity (Customer entity) 的示例。

[EntityAttributeBase("Customers", "Id")]
public class Customer : EntityBase
{
    [EnumStringType]
    public enum CustomerSize
    {
        None,
        Small,
        Medium,
        Large
    }

    [FieldDirection(ParameterDirection.InputOutput)]
    public long Id { get; set; }

    public string Name { get; set; }

    public string AddressLine1 { get; set; }

    public string AddressLine2 { get; set; }

    public string City { get; set; }
    
    public string State { get; set; }

    public string ZipCode { get; set; }

    public string CountryCode { get; set; }

    public string ContactName { get; set; }

    public string ContactEmail { get; set; }

    public string ContactPhone { get; set; }

    public CustomerSize Size { get; set; }

    [NavigationProperty]
    public Merchant[] Merchants { get; set; }

    [FieldDirection(ParameterDirection.InputOutput)]
    public Byte[] RowVersion { get; set; }
}

要使用此代码,您只需要创建从 EntityBase 类继承的实体并使用 SqlHelper 类。

示例

这是一个具体的例子,以便更好地理解这种方法。

这是一个假设的案例,一家公司(一家支付网关公司)的数据库结构如下图所示

DB Diagram

在这种情况下,网关公司有 CustomersCustomersMerchants,而这些 MerchantsTerminals

一旦您获取了 Terminal,您就可以使用 INCLUDE 功能(“Merchant.Customer”)获取其 MerchantCustomer

以下是如何从数据访问类使用此方法的示例

public Terminal GetTerminalByCode(string code)
{
    List<SqlParameter> parameters = new List<SqlParameter>();
        parameters.Add(new SqlParameter("@Code", code));

    return SqlHelper.GetEntity<Terminal>(_sqlConnection, 
           "Terminals_GetByCode", parameters, "Merchant.Customer");
}

调用 SqlHelper 类的 GetEntity 方法,您可以获取所需的实体及其依赖项。

此方法需要一个连接对象、负责从数据库获取实体的存储过程的名称、存储过程的参数以及所需的 include 路径。

以下是上一个示例中调用的存储过程的结构

CREATE PROCEDURE [dbo].[Terminals_GetByCode] 
        @Code nvarchar(10),
        @Include nvarchar(MAX) = null
AS
BEGIN
    SET NOCOUNT ON;

    SELECT t.* 
    INTO #tmpTable
    FROM Terminals t
    WHERE t.Code = @Code;
    
    SELECT tmp.* FROM #tmpTable tmp;    
    
    IF (@Include is not null) BEGIN
        exec (@Include);
    END
    
    drop table #tmpTable;

END

还有一件事:存储过程需要 #tmpTable,因为它被 @Include 变量使用。

© . All rights reserved.