【问题标题】:C# dynamic assignment of properties in a loopC#在循环中动态分配属性
【发布时间】:2013-02-14 01:06:19
【问题描述】:

我没有完全得到/看到我所希望的并且可能错过了,所以无论如何我会再问一次。 我有一堂课,上面有字段。我有一个使用相同列和相应数据类型查询的数据表。我有一个包含我关心的所有字段的字符串数组,以及它们的特定顺序。

我想要做的是循环遍历数组中的每个元素,并将检索数据表结果集的数据行中的值放入类结构的字段中。类似于下面的伪代码

String WorkWithThese[] = { "MyFld1", "AnotherFld", "ThirdFld" };
DataTable oTbl = GetMySQLQuery( "Select * from MyTable where ID = 1" );
DataRow oDR = oTbl.Rows[0];

MyOtherClass oMC = new MyOtherClass();


foreach( String s in WorkWithThese )
   // NOW, what I'm looking to do...
   oMC.<the field represented by 's'> = oDR[s];

我知道 oDR[s] 会起作用,因为它是结果集中具有相同名称的有效列。我的问题是更新 oMC 中的单个字段。

这是打算在手持设备上使用,因此内存限制需要最佳性能,但在其他各种领域也需要动态的可维护性......两者的温和融合。我希望避免这样做

oMC.MyFld1 = oDR["MyFld1"];
oMC.MyFld1 = oDR["AnotherFld"];
oMC.MyFld1 = oDR["ThirdFld"];

当每个表引用可能有 50-60 个字段时

【问题讨论】:

  • 我很担心你为什么会这么快改变任务。你的 setter 中有业务逻辑吗?

标签: c# reflection dynamic


【解决方案1】:

我想出了一个解决方案,它允许我采用自动化方法,同时帮助提高性能......希望该技术也可以帮助其他人。我的问题是在使用 System.Data 时(通过 SqlCE,但也适用于其他数据库后端)。每次我尝试创建 SQL 命令对象来执行插入、更新或其他操作,并将“参数”添加到 sql 对象、获取正确的数据类型等时,都会降低性能。所以,我为插入/更新做了这个。在我的数据管理器类(我使用的每个表一个)上,我添加了 IDbCommand 对象的对象,一个分别用于插入/更新。在构造函数期间,我会预先查询表以获取最终行对象的结构,并预先构建查询和参数(跳过主键 ID),例如...

private void BuildDefaultSQLInsert()
{

   // get instance to the object ONCE up front
   // This is a private property on the data manager class of IDbCommand type
   oSQLInsert = GetSQLCommand("");

   // pre-build respective insert statement and parameters ONCE. 
   // This way, when actually called, the object and their expected
   // parameter objects already in place.  We just need to update
   // the "Value" inside the parameter
   String SQLCommand = "INSERT INTO MySQLTable ( ";
   String InsertValues = "";

   // Now, build a string of the "insert" values to be paired, so
   // add appropriate columns to the string, and IMMEDIATELY add their
   // respective "Value" as a parameter
   DataTable MyTable = GetFromSQL( "Select * from MySQLTable where MyIDColumn = -1" );
   foreach (DataColumn oCol in MyTable.Columns)
   {
      // only add columns that ARE NOT The primary ID column
      if (!(oCol.ColumnName.ToUpper() == "MYIDCOLUMN" ))
      {
         // add all other columns comma seperated...
         SQLCommand += oCol.ColumnName + ",";

         InsertValues += "?,";
         // Ensure a place-holder for the parameters so they stay in synch 
         // with the string.  My AddDbParm() function would create the DbParameter
         // by the given column name and default value as previously detected
         // based on String, Int, DateTime, etc...
         oSQLInsert.Parameters.Add(AddDbParm(oCol.ColumnName, oCol.DefaultValue));
      }
   }

   // Strip the trailing comma from each element... command text, and its insert values
   SQLCommand = SQLCommand.Substring(0, SQLCommand.Length - 1);
   InsertValues = InsertValues.Substring(0, InsertValues.Length - 1);

   // Now, close the command text with ") VALUES ( " 
   // and add  the INSERT VALUES element parms
   SQLCommand += " ) values ( " + InsertValues + " )";

   // Update the final command text to the SQLInsert object 
   // and we're done with the prep ONCE
   oSQLInsert.CommandText = SQLCommand;

}

接下来,当我需要实际执行所有记录的插入操作时,我通过我的 Add() 函数执行此操作,并传入我正在处理的 DataRow 的一个实例。由于 SQLInsert 对象已经使用相应的参数构建,因此我可以循环浏览与数据管理器负责的相同类型的数据行,并使用数据行的当前“值”更新参数对象

public Boolean AddMyRecord(DataRow oDR)
{
   // the parameter name was set based on the name of the column, 
   // so I KNOW there will be a match, and same data type
   foreach (IDbDataParameter oDBP in oSQLInsert.Parameters)
      oDBP.Value = oDR[oDBP.ParameterName];

   ExecuteMySQLCommand( oSQLInsert );
}

通过针对手持设备的一些定时试验,运行/验证大约 20 个查询和 10 个插入的前后时间从大约 10 秒缩短到 2.5 秒。该技术与执行 SQLUpdate 的技术类似,但在构建字符串/对象循环结束时将 WHERE 子句强制用于表的主 ID 列。它工作得很好。现在,如果我需要扩展表的结构或列序列,我不必更改插入、更新过程的任何代码。

【讨论】:

    【解决方案2】:

    同意其他人关于使用反射的速度,但这样的事情可能会起作用:

            public static void SetProperty(object myObject, string name, string valueString)
            {
                try
                {
                    Type type = myObject.GetType();
                    PropertyInfo property = type.GetProperty(name);
    
                    if (property != null && property.CanWrite)
                    {
                        object value = null;
                        if (property.PropertyType == typeof(double))
                            value = Convert.ToDouble(valueString);
                        else if (property.PropertyType == typeof(int))
                            value = Convert.ToInt32(valueString);
                        else if (property.PropertyType == typeof(bool))
                            value = Convert.ToBoolean(valueString);
                        else if (property.PropertyType == typeof(DateTime))
                            value = DateTime.Parse(valueString);
                        ...
                        else
                            Debug.Assert(false, property.PropertyType.AssemblyQualifiedName + " not handled");
    
                        property.SetValue(myObject, value, null);
                    }
                }
                catch (FormatException)
                {
                    //Unable to set the property '{0}' to '{1}', name, valueString
                }
                catch (NullReferenceException)
                {
                    //Property not defined (or even deprecated)
                }
            }
    

    【讨论】:

      【解决方案3】:

      还没有在 CompactFramework 上尝试过,但您可以尝试使用反射来查找属性来设置我的姓名(=> 数据库中的列名)。然后,您可以使用反射设置属性。

      然而,这是性能最差的方式...

      【讨论】:

        【解决方案4】:

        使用reflection 可以做到这一点,但速度非常慢。我会推荐:

        • 手动填写字段(没有那么多工作)
        • 使用代码生成为您编写代码
        • 使用 ORM(最佳解决方案!)

        【讨论】:

        • 只是出于兴趣,您认为 ORM 将如何发现和填充字段?建议不要反思,然后在同一个答案中宣传它是令人惊讶的。
        • 让我解释一下。 orms 通过代码生成或反射来解决它(对!),但它们以非常聪明的方式解决它,通常只在启动时执行一次,因此它不会影响整体性能。
        【解决方案5】:

        当您在手持设备中寻找性能时,使用重新选择肯定不是要走的路。虽然有一些像 EntitySpaces 这样的 ORM 工具,但我相信通过手动方法可能会在一些代码生成工具的帮助下获得更好的性能。

        【讨论】:

        • 这就是我所期待的答案...感谢大家确认我的直觉...
        【解决方案6】:

        这是我的解决方案,使用反射,我不知道是否适合性能,但您可以通过点击解决可能的问题...

            /// <summary>
            /// Riempie una Entità con i valori presenti in un DataRow automaticamente.
            /// L'automatismo funzione solo se i nomi delle colonne (campi del DataBase) corrispondono ai nomi 
            /// delle properties se una Property non ha colonna del DataRow semplicemente è valorizzata al Default
            /// (come dopo una istanzizione dell'oggetto mediante "new"). Se una colonna esiste ma non c'è la 
            /// corrispondente proprietà la segnalazione dell'eccezione dipenderà dal flag: columnCanNotCorrespond
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="dr"></param>
            /// <param name="columnCanNotCorrespond">Se true, non scatta eccezione se c'è una colonna che non è corrispondente ad una Property</param>
            /// <returns></returns>
            public static T GetEntity<T>(DataRow dr, bool columnCanNotCorrespond)
            {
                Type entityType = typeof(T);
                T entity = (T)entityType.Assembly.CreateInstance(entityType.FullName);
        
                if (columnCanNotCorrespond)
                {
                    foreach (DataColumn dc in dr.Table.Columns)
                    {
                        object columnValue = dr[dc.ColumnName];                 
                        if (entity.GetType().GetProperty(dc.ColumnName) != null) //La Property Esiste?
                            entity.GetType().GetProperty(dc.ColumnName).SetValue(entity, columnValue, null);
        
                    }
                }
                else //Scatterà eccezione se la Property non corrisponde alla colonna!
                {
                    foreach (DataColumn dc in dr.Table.Columns)
                    {
                        object columnValue = dr[dc.ColumnName];                 
                        entity.GetType().GetProperty(dc.ColumnName).SetValue(entity, columnValue, null);
                    }
        
                }
                return (T)entity;
            }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-06-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-03-03
          • 1970-01-01
          • 2011-06-29
          • 2021-04-05
          相关资源
          最近更新 更多