I am thinking about making a custom attribute so that when we are using multiple data readers [SqldataReader
] on different objects/tables, we could use the attribute to get the type of the property, and the "columnName
" of the property. This way, we could then have a method that takes the data reader as a param
, and from there could reflect the attributes to read in the columns. An example of what is currently being done is below, and then an example of what I am trying to accomplish. The problem I am having, is how to manage how to tell it what the (Type) is.
我正在考虑创建一个自定义属性,以便当我们在不同的对象/表上使用多个数据读取器[SqldataReader]时,我们可以使用该属性来获取属性的类型,以及属性的“columnName”。这样,我们就可以有一个方法,将数据读取器作为参数,从那里可以反映要在列中读取的属性。目前正在做的一个例子如下,然后是我想要完成的一个例子。我遇到的问题是如何管理如何告诉它(类型)是什么。
private static App GetAppInfo(SqlDataReader dr)
{
App app = new App();
app.ID = MCCDBUtility.GetDBValueInt(dr, "APPLICATION_ID");
app.Name = MCCDBUtility.GetDBValueString(dr, "APPNAME");
app.Desc = MCCDBUtility.GetDBValueString(dr, "APPDESCRIPTION");
app.Version = MCCDBUtility.GetDBValueString(dr, "APP_VERSION");
app.Type = MCCDBUtility.GetDBValueString(dr, "APPLICATIONTYPEID");
app.AreaName = MCCDBUtility.GetDBValueString(dr, "AREANAME");
return app;
}
What I am thinking though, so if I had a class for example like so:
我在想什么,所以如果我有一个例如这样的课:
[DataReaderHelper("MethodNameToGetType", "ColumnName")]
public string APPNAME {get;set;}
How could I go about this?
我怎么能这样做?
1 个解决方案
#1
Fist of all, this is possible and if you like I could add a code sample. But: This is not a good idea.
所有的拳头,这是可能的,如果你愿意,我可以添加一个代码示例。但是:这不是一个好主意。
Why, you ask?
你为什么问?
First - DataReader provides you with a method GetSchemaTable() which contains a property DataType
which is a System.Type
object. So basically you could create a MCCDBUtility.GetValue(dr, "columnName")
that does the logic for your.
首先 - DataReader为您提供了一个方法GetSchemaTable(),它包含一个属性DataType,它是一个System.Type对象。所以基本上你可以创建一个MCCDBUtility.GetValue(dr,“columnName”)来为你的逻辑做。
Second - What about you have a int property on your object but your datareader returns a decimal. For that case you can use Convert.ChangeType(value, type)
第二个 - 你的对象上有一个int属性,但你的datareader返回一个小数。对于这种情况,您可以使用Convert.ChangeType(值,类型)
If you combine that you can achive what you want with
如果你结合起来,你可以实现你想要的
instance.Id = MCCDBUtility.GetValue<int>(dr, "columnName")
public T GetValue<T>(IDataReader reader, string columnName)
{
object value GetValue(reader, columnName);
return Convert.ChangeType(value, typeof(T));
}
private object GetValue(IDataReader reader, string columnName)
{
var schmema = reader.GetSchemaTable();
var dbType = typeof(object);
foreach(DataRowView row in schema.DefaultView)
if (row["columnName"].ToString().Equals(columnName, StringComparer.OrdinalIgnoreCase))
return row["ColumnType"];
if (dbType.Equals(typeof(int))
return GetInt(reader, columnName)
... // you get the point
else
return GetObject(reader, columnName);
}
And Third - Don't do this anyway there are great tools for mapping your query to your business objects. I don't want to name them all but a very lightweight and easy to understand is Dapper.NET
, give it a try. https://github.com/StackExchange/dapper-dot-net
第三 - 无论如何不要这样做有很好的工具可以将查询映射到业务对象。我不想将它们全部命名,但是非常轻巧且易于理解的是Dapper.NET,试一试。 https://github.com/StackExchange/dapper-dot-net
In combination with https://github.com/tmsmith/Dapper-Extensions you can easily map your database queries to your pocos
结合https://github.com/tmsmith/Dapper-Extensions,您可以轻松地将数据库查询映射到您的pocos
Update
As promised, here is the code for implementing on your own. Just create a Visual Studio Test project, insert the code and let it run. For readablity I omitted the unused IReadReader interface implementations, so you have to let intellisense create them for you.
正如所承诺的,这是自己实现的代码。只需创建一个Visual Studio Test项目,插入代码并让它运行。为了可读性,我省略了未使用的IReadReader接口实现,因此您必须让intellisense为您创建它们。
Run the test and enjoy.
运行测试并享受。
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
var values = new Dictionary<string, object>();
values.Add("ProductId", 17);
values.Add("ProductName", "Something");
values.Add("Price", 29.99M);
var reader = new FakeDataReader(values);
var product1 = new Product();
reader.SetValue(product1, p => p.Id);
reader.SetValue(product1, p => p.Name);
reader.SetValue(product1, p => p.Price);
Assert.AreEqual(17, product1.Id);
Assert.AreEqual("Something", product1.Name);
Assert.AreEqual(29.99M, product1.Price);
var product2 = new Product();
reader.SetAllValues(product2);
Assert.AreEqual(17, product2.Id);
Assert.AreEqual("Something", product2.Name);
Assert.AreEqual(29.99M, product2.Price);
}
}
public class Product
{
[Mapping("ProductId")]
public int Id { get; set; }
[Mapping("ProductName")]
public string Name { get; set; }
public decimal Price { get; set; }
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple=false)]
public class MappingAttribute : Attribute
{
public MappingAttribute(string columnName)
{
this.ColumnName = columnName;
}
public string ColumnName { get; private set; }
}
public static class IDataReaderExtensions
{
public static void SetAllValues(this IDataReader reader, object source)
{
foreach (var prop in source.GetType().GetProperties())
{
SetValue(reader, source, prop);
}
}
public static void SetValue<T, P>(this IDataReader reader, T source, Expression<Func<T, P>> pe)
{
var property = (PropertyInfo)((MemberExpression)pe.Body).Member;
SetValue(reader, source, property);
}
private static void SetValue(IDataReader reader, object source, PropertyInfo property)
{
string propertyName = property.Name;
var columnName = propertyName;
var mapping = property.GetAttribute<MappingAttribute>();
if (mapping != null) columnName = mapping.ColumnName;
var value = reader.GetValue(reader.GetOrdinal(columnName));
var value2 = Convert.ChangeType(value, property.PropertyType);
property.SetValue(source, value2, null);
}
}
public static class ICustomFormatProviderExtensions
{
public static T GetAttribute<T>(this ICustomAttributeProvider provider)
{
return (T)provider.GetCustomAttributes(typeof(T), true).FirstOrDefault();
}
}
public class FakeDataReader : IDataReader
{
private Dictionary<string, object> values;
public FakeDataReader(Dictionary<string, object> values)
{
this.values = values;
}
public int GetOrdinal(string name)
{
int i = 0;
foreach (var key in values.Keys)
{
if (key.Equals(name, StringComparison.OrdinalIgnoreCase)) return i;
i++;
}
return -1;
}
public object GetValue(int i)
{
return values.Values.ToArray()[i];
}
}
#1
Fist of all, this is possible and if you like I could add a code sample. But: This is not a good idea.
所有的拳头,这是可能的,如果你愿意,我可以添加一个代码示例。但是:这不是一个好主意。
Why, you ask?
你为什么问?
First - DataReader provides you with a method GetSchemaTable() which contains a property DataType
which is a System.Type
object. So basically you could create a MCCDBUtility.GetValue(dr, "columnName")
that does the logic for your.
首先 - DataReader为您提供了一个方法GetSchemaTable(),它包含一个属性DataType,它是一个System.Type对象。所以基本上你可以创建一个MCCDBUtility.GetValue(dr,“columnName”)来为你的逻辑做。
Second - What about you have a int property on your object but your datareader returns a decimal. For that case you can use Convert.ChangeType(value, type)
第二个 - 你的对象上有一个int属性,但你的datareader返回一个小数。对于这种情况,您可以使用Convert.ChangeType(值,类型)
If you combine that you can achive what you want with
如果你结合起来,你可以实现你想要的
instance.Id = MCCDBUtility.GetValue<int>(dr, "columnName")
public T GetValue<T>(IDataReader reader, string columnName)
{
object value GetValue(reader, columnName);
return Convert.ChangeType(value, typeof(T));
}
private object GetValue(IDataReader reader, string columnName)
{
var schmema = reader.GetSchemaTable();
var dbType = typeof(object);
foreach(DataRowView row in schema.DefaultView)
if (row["columnName"].ToString().Equals(columnName, StringComparer.OrdinalIgnoreCase))
return row["ColumnType"];
if (dbType.Equals(typeof(int))
return GetInt(reader, columnName)
... // you get the point
else
return GetObject(reader, columnName);
}
And Third - Don't do this anyway there are great tools for mapping your query to your business objects. I don't want to name them all but a very lightweight and easy to understand is Dapper.NET
, give it a try. https://github.com/StackExchange/dapper-dot-net
第三 - 无论如何不要这样做有很好的工具可以将查询映射到业务对象。我不想将它们全部命名,但是非常轻巧且易于理解的是Dapper.NET,试一试。 https://github.com/StackExchange/dapper-dot-net
In combination with https://github.com/tmsmith/Dapper-Extensions you can easily map your database queries to your pocos
结合https://github.com/tmsmith/Dapper-Extensions,您可以轻松地将数据库查询映射到您的pocos
Update
As promised, here is the code for implementing on your own. Just create a Visual Studio Test project, insert the code and let it run. For readablity I omitted the unused IReadReader interface implementations, so you have to let intellisense create them for you.
正如所承诺的,这是自己实现的代码。只需创建一个Visual Studio Test项目,插入代码并让它运行。为了可读性,我省略了未使用的IReadReader接口实现,因此您必须让intellisense为您创建它们。
Run the test and enjoy.
运行测试并享受。
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
var values = new Dictionary<string, object>();
values.Add("ProductId", 17);
values.Add("ProductName", "Something");
values.Add("Price", 29.99M);
var reader = new FakeDataReader(values);
var product1 = new Product();
reader.SetValue(product1, p => p.Id);
reader.SetValue(product1, p => p.Name);
reader.SetValue(product1, p => p.Price);
Assert.AreEqual(17, product1.Id);
Assert.AreEqual("Something", product1.Name);
Assert.AreEqual(29.99M, product1.Price);
var product2 = new Product();
reader.SetAllValues(product2);
Assert.AreEqual(17, product2.Id);
Assert.AreEqual("Something", product2.Name);
Assert.AreEqual(29.99M, product2.Price);
}
}
public class Product
{
[Mapping("ProductId")]
public int Id { get; set; }
[Mapping("ProductName")]
public string Name { get; set; }
public decimal Price { get; set; }
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple=false)]
public class MappingAttribute : Attribute
{
public MappingAttribute(string columnName)
{
this.ColumnName = columnName;
}
public string ColumnName { get; private set; }
}
public static class IDataReaderExtensions
{
public static void SetAllValues(this IDataReader reader, object source)
{
foreach (var prop in source.GetType().GetProperties())
{
SetValue(reader, source, prop);
}
}
public static void SetValue<T, P>(this IDataReader reader, T source, Expression<Func<T, P>> pe)
{
var property = (PropertyInfo)((MemberExpression)pe.Body).Member;
SetValue(reader, source, property);
}
private static void SetValue(IDataReader reader, object source, PropertyInfo property)
{
string propertyName = property.Name;
var columnName = propertyName;
var mapping = property.GetAttribute<MappingAttribute>();
if (mapping != null) columnName = mapping.ColumnName;
var value = reader.GetValue(reader.GetOrdinal(columnName));
var value2 = Convert.ChangeType(value, property.PropertyType);
property.SetValue(source, value2, null);
}
}
public static class ICustomFormatProviderExtensions
{
public static T GetAttribute<T>(this ICustomAttributeProvider provider)
{
return (T)provider.GetCustomAttributes(typeof(T), true).FirstOrDefault();
}
}
public class FakeDataReader : IDataReader
{
private Dictionary<string, object> values;
public FakeDataReader(Dictionary<string, object> values)
{
this.values = values;
}
public int GetOrdinal(string name)
{
int i = 0;
foreach (var key in values.Keys)
{
if (key.Equals(name, StringComparison.OrdinalIgnoreCase)) return i;
i++;
}
return -1;
}
public object GetValue(int i)
{
return values.Values.ToArray()[i];
}
}