0

我正在向使用 n 层架构的旧 asp.net 应用程序添加新功能。一个基本的例子可以给出

目的

public class Test
{
    public int ID{get;set;}
    public int name{get;set;}
}

数据访问层

public static List<Test> GetTests()
{
    List<Test> list = new List<Test>();
    try
    {
        //codes
        SqlDataReader dr  = com.ExecuteReader();
        while(dr.Read())
            list.Add(FillTestRecord(dr))
        //codes
    }
    catch{}
    return list;
}

private static Test FillTestRecord(IDataRecord dr)
{
    Test test = new Test();
    try{test.ID = Convert.ToInt32(dr["ID"]);}
    catch{}
    try{test.Name = Convert.ToInt32(dr["Name"]);}
    catch{}
    return test;
}

开发需要我向对象类添加新字段,并且为了可重用性,我对每种类型的对象仅使用一个 Fill*Record 方法。此方法可由许多其他 DAL 方法调用,这些方法的 IDataRecord 可能不包含对象的所有列。因此,我分别为每个属性放置了 try-catch 块。这可确保正确解析 IDataRecord 中的所有可用列。

我的问题是有没有更好的方法呢?这种架构的最佳实践是什么?

更新

在阅读了 David L 和 Anup 的评论/答案后,我尝试了另一种使用扩展方法的方法。方法如下

public static bool TryGetOrdinal(this IDataRecord dr, string column, out int ordinal)
{
    try
    {
        ordinal = dr.GetOrdinal(column);
    }
    catch(Exception ex)
    {
        ordinal = -1; //Just setting a value that GetOrdinal doesn't return
        return false;
    }
    return true;
}

所以FillTestRecord方法是

private static Test FillTestRecord(IDataRecord dr)
{
    Test test = new Test();

    int ordinal = default(int);

    if(dr.TryGetOrdinal("ID",out ordinal))
        test.ID = Convert.ToInt32(dr.GetValue(ordinal));
    if(dr.TryGetOrdinal("Name",out ordinal))
        test.Name = Convert.ToString(dr.GetValue(ordinal));

    return test;
}

对此的任何建议都将受到高度赞赏。

更新 03-02-2016

在调试过程中,我发现try-catch如果GetOrdinalDataRecord. 所以我写了一个新方法来获取 中的列名DataReader并替换GetOrdinalArray.IndexOf.

public static bool TryGetOrdinal(this IDataRecord dr, string[] columnNames, string column, out int ordinal)
{
    ordinal = Array.IndexOf(columnNames, column);
    return ordinal >= 0;
}

而我的FillTestRecord变成——

private static Test FillTestRecord(IDataRecord dr, string[] columnNames)
{
    Test test = new Test();

    int ordinal = default(int);

    if(dr.TryGetOrdinal(columnNames, "id",out ordinal))
        test.ID = Convert.ToInt32(dr.GetValue(ordinal));
    if(dr.TryGetOrdinal(columnNames, "name",out ordinal))
        test.Name = Convert.ToString(dr.GetValue(ordinal));

    return test;
}

列名像这样传递给 Fill 方法 -

using (var dr = com.ExecuteReader())
{
    string[] colNames = dr.GetColumnNames();
    while (dr.Read())
        list.Add(FillTestRecord(dr, colNames));
}

'GetColumnNames' 是新的扩展方法 -

public static string[] GetColumnNames(this IDataReader dr)
{
    string[] columnNames = new string[dr.FieldCount];
    for (int i = 0; i < dr.FieldCount; i++)
    {
        columnNames[i] = dr.GetName(i).ToLower();
    }
    return columnNames;
}
4

3 回答 3

1

Seems to me that you are in the right direction.
As long as the parsing is being done in a centralized location which is re-used by all upper level classes, this is looks like a good solution.

The only thing I would change is replacing the try-catch statements with checking if the data exists in the columns. surely there is a way to tell (column does not exist? DB-Null value?) You could implement that using a something similar to the TryParse methods.

    private static Test FillTestRecord(IDataRecord dr)
    {
        Test test = new Test();
        int tempId;
        if (TryParseDataRow<int>(dr, "ID", out tempId))
        {
            test.Id = tempId;
        }
        return test;
    }

    private static bool TryParseDataRow<T>(IDataRecord record, string column, out T value)
    {
        value = default(T);
        bool success = true;

        if (record == null)
        {
            //nothing you can do with a null object
            success = false;
        }
        else if (!record.HasColumn(column)) //not sure if this will throw exeption or return null. you can check in your project
        {
            success = false;
        }
        else if (record[column] != typeof(T))
        {
            //object was of an unexpected type
            success = false;
        }           
        else
        {
            //cast the value into the output parameter
            value = (T)record[column];
        }

        return success;
    }

And of course you will have to implement the HasColumn method (Implemented here as extension):

    /// <summary>
    /// Determines whether the specified record has column.
    /// </summary>
    /// <param name="record">The record.</param>
    /// <param name="columnName">Name of the column.</param>
    /// <returns>true if column exist, false otherwise</returns>
    public static bool HasColumn(this IDataRecord record, string columnName)
    {
        for (int i = 0; i < record.FieldCount; i++)
        {
            if (record.GetName(i).Equals(columnName, StringComparison.InvariantCultureIgnoreCase))
                return true;
        }
        return false;
    }
于 2013-10-07T04:27:28.463 回答
1

这是我整理的一些代码,用于将 IDataRecord 映射到属性

    public static T ParseRecord<T>(this IDataRecord reader) where T : new()
    {
        var model = new T();
        var type = typeof(T);

        for (int i = 0; i < reader.FieldCount; i++) {
            var fieldType = reader.GetFieldType(i);
            var fieldName = reader.GetName(i);
            var val = reader.GetValue(i);
            var prop = type.GetProperty(fieldName);

            // handle or throw instead here if needed
            if (prop == null)
                continue;

            var propType = prop.PropertyType;

            // HACK: remove this if you don't want to coerce to strings
            if (propType == typeof(string))
                prop.SetValue(model, val.ToString());
            else if (fieldType != propType)
                throw new Exception($"Type mismatch field {fieldType} != prop {propType}");
            else
                prop.SetValue(model, val);
        }

        return model;
    }
于 2018-05-18T19:00:24.513 回答
0

我正在使用以下代码来映射不同对象的属性。它使用反射来获取源对象和目标对象的属性,但您可以轻松地将其更改为使用 IDataRecord:

public static T MapDTO<T>(object dto) where T : new()
{
    T Result = new T();

    if (dto == null)
        return Result;

    dto.GetType().GetProperties().ToList().ForEach(p =>
        {
            PropertyInfo prop = Result.GetType().GetProperty(p.Name);

            if (prop != null && prop.CanWrite)
            {
                try
                {
                    var convertedVal = Convert.ChangeType(p.GetValue(dto, null), prop.PropertyType);
                    prop.SetValue(Result, convertedVal, null);
                }
                catch (Exception ex)
                {
                    try
                    {
                        prop.SetValue(Result, p.GetValue(dto, null), null);
                    }
                    catch (Exception ex1) { }
                }
            }
        });
    return Result;
}

这里的关键是 source 和 dest 属性应该具有相同的名称。

于 2013-10-07T11:04:15.923 回答