将数据解析为数据访问层中的对象的最佳做法是什么?
本文关键字:数据 最佳 对象 是什么 访问 | 更新日期: 2023-09-27 18:34:06
我正在向使用 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
在调试过程中,我发现如果在DataRecord
中找不到提供的列名时GetOrdinal
抛出错误,try-catch
会对性能造成很大影响。所以我写了一个新方法,该方法获取DataReader
中的列名,并将GetOrdinal
替换为Array.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;
}
,你走在正确的方向上。
只要解析是在所有上层类重用的集中位置完成的,这看起来是一个很好的解决方案。
我唯一要更改的是将try-catch
语句替换为检查列中是否存在数据。 当然有办法告诉(列不存在?数据库空值?您可以使用类似于 TryParse 方法来实现它。
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;
}
当然,您必须实现HasColumn
方法(此处作为扩展实现(:
/// <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;
}
这是我整理的一些代码,用于将 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;
}
我使用以下代码来映射不同对象的属性。它使用反射来获取源对象和目标对象的属性,但您可以轻松地将其更改为使用 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;
}
这里的关键是源属性和目标属性应具有相同的名称。