26

目前,我正在使用这样的东西:

    try
    {
      dr = SQL.Execute(sql);

      if(dr != null) {
         while(dr.Read()) {
           CustomObject c = new CustomObject();
           c.Key = dr[0].ToString();
           c.Value = dr[1].ToString();
           c.Meta = dr[2].ToString();
           customerInfo.CustomerList.Add(c);
         }
      }
      else
      {
          customerInfo.ErrorDetails="No records found";
      } 

有没有一种方法可以直接进行此映射,而不是我手动进行分配(假设列名与字段名匹配)。

然而,一个要求是我想通过我目前使用 sql 查询的方法而不是通过使用纯 LINQ 的方法来做到这一点。一方面,SQL 查询足够大,涉及复杂的 JOIN 并且已经过彻底的测试,所以我现在不想引入更多的错误。有什么建议么?

4

8 回答 8

9

一个简单的解决方案是为您创建一个构造函数,该构造函数CustomObject采用 a DataRow(来自示例,所以如果它是另一个类,请纠正我)。

在你的新构造函数中,按照你自己的例子做。

public CustomObject(DataRow row)
{
    Key = row[0].ToString();
    // And so on...
}

另一种方法是引入泛型,并在您的 SQL 类中创建一个新函数

示例(从将参数传递给模板类型的 C# generic new() 中获取代码):

// This function should reside in your SQL-class.
public IEnumerable<T> ExecuteObject<T>(string sql)
{
    List<T> items = new List<T>();
    var data = ExecuteDataTable(sql); // You probably need to build a ExecuteDataTable for your SQL-class.
    foreach(var row in data.Rows)
    {
        T item = (T)Activator.CreateInstance(typeof(T), row);
        items.Add(item);
    }
    return items;
}

示例用法:

public IEnumerable<CustomObject> GetCustomObjects()
{
    return SQL.ExecuteObject<CustomObject>("SELECT * FROM CustomObject");
}

我已经在 LinqPad 中测试了这段代码,它应该可以工作。

于 2012-07-03T07:09:20.840 回答
9

您可以通过为您的要求创建通用方法来实现。您也可以将新方法作为数据表的扩展。

    public static List<T> ToList<T>(this DataTable table) where T : class, new()
{
    try
    {
        List<T> list = new List<T>();

        foreach (var row in table.AsEnumerable())
        {
            T obj = new T();

            foreach (var prop in obj.GetType().GetProperties())
            {
                try
                {
                    PropertyInfo propertyInfo = obj.GetType().GetProperty(prop.Name);
                    propertyInfo.SetValue(obj, Convert.ChangeType(row[prop.Name], propertyInfo.PropertyType), null);
                }
                catch
                {
                    continue;
                }
            }

            list.Add(obj);
        }

        return list;
    }
    catch
    {
        return null;
    }
}

}

用法:

    DataTable dtCustomer = GetCustomers();
    List<CustomObject> CustomObjectList = dtCustomer.ToList<CustomObject>();
于 2014-09-23T07:59:25.197 回答
7

您应该研究 MicroORM。与提供您必须使用的 SDL 的常规 ORM 不同,MicroORM 允许您使用自己的 SQL 查询,并且仅提供从 SQL 结果集到 C# 对象以及从 C# 对象到 SQL 参数的映射。

我最喜欢的是PetaPoco,它还提供了一个查询构建器,它使用您自己的 SQL,但对参数编号进行了一些巧妙的操作。

于 2014-09-23T09:42:02.927 回答
4

@user1553525的答案很好,但是,如果您的列名与您的属性名称不完全匹配,则它不起作用。

因此,首先您需要创建一个自定义属性。然后在你的类中使用你试图反序列化的属性,最后,你想要反序列化 DataTable。

自定义属性

我们创建了一个自定义属性,该属性将应用于我们类内部的属性。我们创建类以具有Name稍后将用于从 DataTable 中获取正确列的属性。

[AttributeUsage(AttributeTargets.Property, Inherited = false)]
public class MySqlColName : Attribute
{
    private string _name = "";
    public string Name { get => _name; set => _name = value; }

    public MySqlColName(string name)
    {
        _name = name;
    }
}

反序列化的类

接下来,在我们要填充的类中,我们将使用[MySqlColName]我们刚刚创建的属性声明将链接到类中的属性的列名。

但是,如果属性名称与数据库列相同,我们不需要在属性中指定列名称,因为该.ToList<>()函数将假定属性名称中的列名称。

public class EventInfo
{
    [MySqlColName("ID")]
    public int EventID { get; set; }

    //Notice there is no attribute on this property? 
    public string Name { get; set; }

    [MySqlColName("State")]
    public string State { get; set; }

    [MySqlColName("Start_Date")]
    public DateTime StartDate { get; set; }

    [MySqlColName("End_Date")]
    public DateTime EndDate { get; set; }

}

数据表 ToList 扩展方法

最后,我们修改@user1553525的答案,添加检查以查看是否提供了我们的自定义属性。如果是,那么我们将列的名称设置为提供的名称,否则,我们使用属性名称(参见 try 块内的代码)。

public static List<T> ToList<T>(this DataTable table) where T : class, new()
{
    try
    {
        List<T> list = new List<T>();

        foreach (var row in table.AsEnumerable())
        {
            T obj = new T();

            foreach (var prop in obj.GetType().GetProperties())
            {
                try
                {
                    //Set the column name to be the name of the property
                    string ColumnName = prop.Name;

                    //Get a list of all of the attributes on the property
                    object[] attrs = prop.GetCustomAttributes(true);
                    foreach (object attr in attrs)
                    {
                        //Check if there is a custom property name
                        if (attr is MySqlColName colName)
                        {
                            //If the custom column name is specified overwrite property name
                            if (!colName.Name.IsNullOrWhiteSpace())                                        
                                ColumnName = colName.Name;
                        }
                    }

                    PropertyInfo propertyInfo = obj.GetType().GetProperty(prop.Name);

                    //GET THE COLUMN NAME OFF THE ATTRIBUTE OR THE NAME OF THE PROPERTY
                    propertyInfo.SetValue(obj, Convert.ChangeType(row[ColumnName], propertyInfo.PropertyType), null);
                }
                catch
                {
                    continue;
                }
            }

            list.Add(obj);
        }

        return list;
    }
    catch
    {
        return null;
    }
}//END METHOD

用法

最后,我们可以调用该.ToList<>()方法并获取序列化对象的列表

List<EventInfo> CustomObjectList;

using (DataTable dtCustomer = GetDataTable("SELECT * FROM EventIndex"))
{
    CustomObjectList = dtCustomer.ToList<EventInfo>();
}

旁注:我使用了一些自定义方法

public static bool IsNullOrWhiteSpace(this string x)
{
    return string.IsNullOrWhiteSpace(x);
}

public static DataTable GetDataTable(string Query)
{
    MySqlConnection connection = new MySqlConnection("<Connection_String>");
    try
    {            
        DataTable data = new DataTable();
        connection.Open();
        using (MySqlCommand command = new MySqlCommand(Query, connection))
        {
            data.Load(command.ExecuteReader());
        }
        return data;

    }
    catch (Exception ex)
    {
        // handle exception here
        Console.WriteLine(ex);
        throw ex;
    }
    finally
    {
        connection.Close();
    }            
}
于 2019-08-18T01:31:38.077 回答
3

假设:如果您只需要用于序列化或简单的临时输出的对象。

你可以像这样使用ExpandoObjectSqlDataReader.GetSchemaTable()

    private IEnumerable<dynamic> ReaderToAnonymmous(SqlCommand comm) {
        using (var reader = comm.ExecuteReader()) {
            var schemaTable = reader.GetSchemaTable();

            List<string> colnames = new List<string>();
            foreach (DataRow row in schemaTable.Rows) {
                colnames.Add(row["ColumnName"].ToString());
            }

            while (reader.Read()) {
                var data = new ExpandoObject() as IDictionary<string, Object>;
                foreach (string colname in colnames) {
                    var val = reader[colname];
                    data.Add(colname, Convert.IsDBNull(val) ? null : val);
                }

                yield return (ExpandoObject)data;
            }
        }
    }

尽管发布了更快的解决方案(我将其发布为临时 SQL/Reader 结果/输出的替代惰性方法)。

于 2016-10-10T14:26:14.297 回答
2

下面的函数接受一个 SQL 字符串和一个对象,它要求对象在 select 语句中的每一列都有一个属性。该对象必须被实例化。

public object SqlToSingleObject(string sSql, object o)
{
    MySql.Data.MySqlClient.MySqlDataReader oRead;
    using (ConnectionHelper oDb = new ConnectionHelper())
    {
        oRead = oDb.Execute(sSql);
        if (oRead.Read())
        {
            for (int i = 0; i < oRead.FieldCount; i++)
            {
                System.Reflection.PropertyInfo propertyInfo = o.GetType().GetProperty(oRead.GetName(i));
                propertyInfo.SetValue(o, Convert.ChangeType(oRead[i], propertyInfo.PropertyType), null);
            }

            return o;
        }
        else
        {
            return null;
        }
    }
}
于 2016-10-10T14:12:12.223 回答
1

在搜索此答案时,我发现您可以使用 Dapper 库:https ://dapper-tutorial.net/knowledge-base/44980945/querying-into-a-complex-object-with-dapper

你可以使用这样的东西:

        using (var connection = new SqlConnection(ConnectionString))
        {
            connection.Open();
            IList<CustomObject> result = connection.Query<CustomObject>(sql, commandType: CommandType.Text).ToList();
        }
于 2019-07-16T13:07:04.193 回答
0

虽然这个问题一直存在,但我找不到一个干净的解决方案。出于我的目的,我想出了以下在我的情况下效果很好的方法。

    using System.Dynamic;

    private IEnumerable<ExpandoObject> GetQueryToList()
    {
        try
        {
            using (var conn = new SqlConnection(ConnectionString))
            using (var cmd = new SqlCommand(MyQuery, conn))
            {
                var list = new List<ExpandoObject>();
                conn.Open();
                var reader = cmd.ExecuteReader();

                while (reader.Read())
                {
                    var expandoObject = new ExpandoObject();
                    for (var i = 0; i < reader.FieldCount; i++)
                    {
                        ((IDictionary<string, object>) expandoObject).Add(
                            reader.GetName(i), reader[i]);
                    }
                    list.Add(expandoObject);
                }

                reader.Close();
                return list;
            }
        }
        catch (Exception ex)
        {
            var m = MethodBase.GetCurrentMethod();
            Console.WriteLine(ex.Message + " " + m.Name);
        }

        return null;
    }
于 2021-06-10T02:26:58.653 回答