2013-01-20 40 views
2

我對泛型和反射的經驗非常少。我從下面的例子中假定的是,它需要太多的時間來執行。有沒有辦法讓我不使用反射來完成以下操作..替代反射

場景 我正在研究一種通用的方法。它需要一個傳遞給它的類的實例,並從所有屬性中生成SqlParameters。以下是稱爲「Store」的泛型方法的代碼,以及另一種將c#類型轉換爲DbType的SqlDbType的方法。

 List<SqlParameter> parameters = new List<SqlParameter>(); 
     public T Store<T>(T t) 
     { 
      Type type = t.GetType(); 
      PropertyInfo[] props = (t.GetType()).GetProperties(); 
      foreach (PropertyInfo p in props) 
      { 
       SqlParameter param = new SqlParameter(); 
       Type propType = p.PropertyType; 
       if (propType.BaseType.Name.Equals("ValueType") || propType.BaseType.Name.Equals("Array")) 
       { 
        param.SqlDbType = GetDBType(propType); //e.g. public bool enabled{get;set;} OR public byte[] img{get;set;} 
       } 
       else if (propType.BaseType.Name.Equals("Object")) 
       { 
        if (propType.Name.Equals("String"))// for string values 
         param.SqlDbType = GetDBType(propType); 
        else 
        { 
         dynamic d = p.GetValue(t, null); // for referrences e.g. public ClassA obj{get;set;} 
         Store<dynamic>(d); 
        } 
       } 
       param.ParameterName = p.Name; 
       parameters.Add(param); 
      } 
      return t; 
     } 



     // mehthod for getting the DbType OR SqlDbType from the type... 
     private SqlDbType GetDBType(System.Type type) 
     { 
      SqlParameter param; 
      System.ComponentModel.TypeConverter tc; 
      param = new SqlParameter(); 
      tc = System.ComponentModel.TypeDescriptor.GetConverter(param.DbType); 
      if (tc.CanConvertFrom(type)) 
      { 
       param.DbType = (DbType)tc.ConvertFrom(type.Name); 
      } 
      else 
      { 
       // try to forcefully convert 
       try 
       { 
        param.DbType = (DbType)tc.ConvertFrom(type.Name); 
       } 
       catch (Exception e) 
       { 
        switch (type.Name) 
        { 
         case "Char": 
          param.SqlDbType = SqlDbType.Char; 
          break; 
         case "SByte": 
          param.SqlDbType = SqlDbType.SmallInt; 
          break; 
         case "UInt16": 
          param.SqlDbType = SqlDbType.SmallInt; 
          break; 
         case "UInt32": 
          param.SqlDbType = SqlDbType.Int; 
          break; 
         case "UInt64": 
          param.SqlDbType = SqlDbType.Decimal; 
          break; 
         case "Byte[]": 
          param.SqlDbType = SqlDbType.Binary; 
          break; 
        } 
       } 
      } 
      return param.SqlDbType; 
     } 

要叫我的方法假設我有2類如下

public class clsParent 
{ 
    public int pID { get; set; } 
    public byte[] pImage { get; set; } 
    public string pName { get; set; } 
} 

and 

public class clsChild 
{ 
    public decimal childId { get; set; } 
    public string childName { get; set; } 
    public clsParent parent { get; set; } 
} 

and this is a call 


clsParent p = new clsParent(); 
p.pID = 101; 
p.pImage = new byte[1000]; 
p.pName = "John"; 
clsChild c = new clsChild(); 
c.childId = 1; 
c.childName = "a"; 
c.parent = p; 

Store<clsChild>(c); 
+1

由於您正在迭代未知類型的屬性並獲取有關這些屬性的信息,因此我認爲您沒有任何方法可以避免此處的反射,實際上,您當前對泛型的使用似乎沒有提供任何服務目的。你可能只需要一個'object'參數:'public void Store(object t)' – JLRishe

+0

@JLRishe它不是我想要完成的方法的完整版本,但未知類型的方式是處理是明確的這種方法...我已經搜索了很多唯一的問題「時間」,但沒有找到任何解決方案...我想用這種方法使用ADO.Net –

+1

@AbdulMajid:不是一種選擇,但只有一個建議:如果類型在運行時重複存儲,您可以嘗試通過引入一些緩存來調整反射方法。你可以從Type.GetProperties()緩存返回的數組。對於要分配或轉換值的部分,您可以嘗試在Func <>實例中緩存邏輯並重用它們。 –

回答

2

如果您想擺脫反思,您可以在下面的代碼中找到靈感。

這裏所有對要存儲在數據庫中的對象以及sql屬性值賦值的訪問都由數據類型的運行時編譯表達式構建來處理。

保存值的表假定爲test,並假定字段名稱與屬性值相同。

對於每個屬性構建一個Mapping<T>。它將保存包含數據庫字段的FieldName,SqlParameter,該SqlParameter應該被正確插入SQL INSERT語句(main中的示例),最後如果包含已編譯的操作,該操作可以獲取輸入T對象的實例並將其分配給值爲SqlParameters財產Value。在Mapper<T>課上完成這些映射集合的構建。代碼內聯解釋。

最後main方法顯示如何將這些東西綁定在一起。

using System; 
using System.Collections.Generic; 
using System.Data.SqlClient; 
using System.Diagnostics; 
using System.Linq; 
using System.Linq.Expressions; 

namespace ExpTest 
{ 
    class Program 
    { 
     public class Mapping<T> 
     { 
      public Mapping(string fieldname, SqlParameter sqlParameter, Action<T, SqlParameter> assigner) 
      { 
       FieldName = fieldname; 
       SqlParameter = sqlParameter; 
       SqlParameterAssignment = assigner; 
      } 
      public string FieldName { get; private set; } 
      public SqlParameter SqlParameter { get; private set; } 
      public Action<T, SqlParameter> SqlParameterAssignment { get; private set; } 
     } 

     public class Mapper<T> 
     { 
      public IEnumerable<Mapping<T>> GetMappingElements() 
      { 
       foreach (var reflectionProperty in typeof(T).GetProperties()) 
       { 
        // Input parameters to the created assignment action 
        var accessor = Expression.Parameter(typeof(T), "input"); 
        var sqlParmAccessor = Expression.Parameter(typeof(SqlParameter), "sqlParm"); 

        // Access the property (compiled later, but use reflection to locate property) 
        var property = Expression.Property(accessor, reflectionProperty); 

        // Cast the property to ensure it is assignable to SqlProperty.Value 
        // Should contain branching for DBNull.Value when property == null 
        var castPropertyToObject = Expression.Convert(property, typeof(object)); 


        // The sql parameter 
        var sqlParm = new SqlParameter(reflectionProperty.Name, null); 

        // input parameter for assignment action 
        var sqlValueProp = Expression.Property(sqlParmAccessor, "Value"); 

        // Expression assigning the retrieved property from input object 
        // to the sql parameters 'Value' property 
        var dbnull = Expression.Constant(DBNull.Value); 
        var coalesce = Expression.Coalesce(castPropertyToObject, dbnull); 
        var assign = Expression.Assign(sqlValueProp, coalesce); 

        // Compile into action (removes reflection and makes real CLR object) 
        var assigner = Expression.Lambda<Action<T, SqlParameter>>(assign, accessor, sqlParmAccessor).Compile(); 

        yield return 
         new Mapping<T>(reflectionProperty.Name, // Table name 
          sqlParm, // The constructed sql parameter 
          assigner); // The action assigning from the input <T> 

       } 
      } 
     } 

     public static void Main(string[] args) 
     { 
      var sqlStuff = (new Mapper<Data>().GetMappingElements()).ToList(); 

      var sqlFieldsList = string.Join(", ", sqlStuff.Select(x => x.FieldName)); 
      var sqlValuesList = string.Join(", ", sqlStuff.Select(x => '@' + x.SqlParameter.ParameterName)); 

      var sqlStmt = string.Format("INSERT INTO test ({0}) VALUES ({1})", sqlFieldsList, sqlValuesList); 

      var dataObjects = Enumerable.Range(1, 100).Select(id => new Data { Foo = 1.0/id, ID = id, Title = null }); 

      var sw = Stopwatch.StartNew(); 

      using (SqlConnection cnn = new SqlConnection(@"server=.\sqlexpress;database=test;integrated security=SSPI")) 
      { 
       cnn.Open(); 

       SqlCommand cmd = new SqlCommand(sqlStmt, cnn); 
       cmd.Parameters.AddRange(sqlStuff.Select(x => x.SqlParameter).ToArray()); 

       dataObjects.ToList() 
        .ForEach(dto => 
         { 
          sqlStuff.ForEach(x => x.SqlParameterAssignment(dto, x.SqlParameter)); 
          cmd.ExecuteNonQuery(); 
         }); 
      } 


      Console.WriteLine("Done in: " + sw.Elapsed); 
     } 
    } 

    public class Data 
    { 
     public string Title { get; set; } 
     public int ID { get; set; } 
     public double Foo { get; set; } 
    } 
} 
+0

我已經做出了這個單獨的答案,因爲它包含了一個非常詳盡的嘗試,使用Linq的'Expression'命名空間來分配值。 – faester

+0

@AbdulMajid是否在完成後粘貼代碼? – Kiquenet

2

我想你在使用一個標準的ORM像NHibernateEntity Framework綜合效益會。兩者都可以執行(可定製)從類到關係數據庫的映射,NHibernate爲您提供了所有標準DBMS系統之間的完全靈活性。

說了這麼多,你應該能夠通過使用Linq表情,以後可以編譯獲得一定的功能;這應該會給你更好的表現。

+0

我不想使用NHibernate或實體框架或LINQ to SQL,只是因爲我唯一遇到的問題是執行簡單操作所需的時間。 我的目標是將我的類映射到簡單的ADO.Net –

+0

@ AbdulMajid這個選擇完全是你的決定。我試圖創建一個編譯的sqlparameter值賦值方法並作爲單獨的答案發布。它變得非常棘手,但是可行。 :) – faester

2

有人告訴你,反思是真的表現重,但是你還沒有真正經過探查運行代碼。

我想你的代碼,並花了18毫秒(65000個蜱)來運行,我必須說,這是相當快相比,它會採取將數據保存在數據庫中的時間。 但你說的確實太多了。 我發現你的代碼在轉換字節[]時調用tc.ConvertFrom時引發了一個異常。 刪除clsParent中的byte [] pImage時,運行時降至850個滴答聲。

這裏的性能問題是Exception,而不是反射。

我已經冒昧給您GetDBType改成這樣:

private SqlDbType GetDBType(System.Type type) 
    { 
     SqlParameter param; 
     System.ComponentModel.TypeConverter tc; 
     param = new SqlParameter(); 
     tc = System.ComponentModel.TypeDescriptor.GetConverter(param.DbType); 
     if (tc.CanConvertFrom(type)) 
     { 
      param.DbType = (DbType)tc.ConvertFrom(type.Name); 
     } 
     else 
     { 
      switch (type.Name) 
      { 
       case "Char": 
        param.SqlDbType = SqlDbType.Char; 
        break; 
       case "SByte": 
        param.SqlDbType = SqlDbType.SmallInt; 
        break; 
       case "UInt16": 
        param.SqlDbType = SqlDbType.SmallInt; 
        break; 
       case "UInt32": 
        param.SqlDbType = SqlDbType.Int; 
        break; 
       case "UInt64": 
        param.SqlDbType = SqlDbType.Decimal; 
        break; 
       case "Byte[]": 
        param.SqlDbType = SqlDbType.Binary; 
        break; 

       default: 
        try 
        { 
         param.DbType = (DbType)tc.ConvertFrom(type.Name); 
        } 
        catch 
        { 
         // Some error handling 
        } 
        break; 
      } 
     } 
     return param.SqlDbType; 
    } 

我希望這將幫助你在你的追求。

+0

謝謝,主要消耗的時間是以下行動態d = p.GetValue(t,null); actualy沒有人告訴我有關代碼,它是緩慢的,但我從所需的時間asumed假設.. –

+1

我真的不能告訴,我用對象d = p.GetValue(t,null),工作就像魅力。自從我在VS 2010中工作以來,我對動態關鍵字性能一無所知。 – Casperah

+0

這是一個遲到的評論,但與使用「對象」相比,使用「動態」變量具有開銷,因爲大量工作是在運行時完成。 Marc Gravell的FastMember lib值得一看https://code.google。com/p/fast-member/ –

2

不是一種選擇,但只有一個建議:如果類型是運行時重複存儲,您可以嘗試通過引入一些緩存來調整反射的方法。

而是具有:

PropertyInfo[] props = (t.GetType()).GetProperties(); 

嘗試以下高速緩存方法:

PropertyInfo[] props = GetProperties(type); 

其中GetProperties(Type)是實現這樣的:

private Dictionary<Type, PropertyInfo[]> propertyCache; 
// ... 
public PropertyInfo[] GetProperties(Type t) 
{ 
    if (propertyCache.ContainsKey(t)) 
    { 
     return propertyCache[t]; 
    } 
    else 
    { 
     var propertyInfos = t.GetProperties(); 
     propertyCache[t] = propertyInfos; 
     return propertyInfos; 
    } 
} 

這是你如何緩存類型.GetProperties()方法調用。您可以對代碼的其他部分應用相同的方法進行查找。例如,您使用param.DbType = (DbType)tc.ConvertFrom(type.Name);的地方。也可以用查找來替換ifs和switch。但在你做這樣的事情之前,你應該真的做一些分析。這使代碼變得複雜,如果沒有充分的理由你就不應該這樣做。

0

除非使用buil後處理來重寫代碼,否則沒有其他選擇。 如果僅將它用於準備動態發射類型/方法/委託並將其自然包含爲策略模式,則可以使用反射而不會出現任何性能問題。