asp.net mvc:.net核心:如何将存储的proc结果集映射到类字段

时间:2018-04-09 01:17:28

标签: stored-procedures asp.net-core

我得到了这个简单的模型类:

公共类PrvProduct     {

    [Key]
    public Int32 ProductId
    {
        get; set;
    }
    public Int64 ProductLineId;
    public String MfgPartNumber;
    public String ProductName;
    public String ProductDescription;

}

我尝试使用.net核心调用存储过程,它工作正常,返回一个PrvProduct对象列表。问题是:他们的字段是空的,除非我自己填写代码。 ProductId总是在那里,不知道为什么(也许是因为我输入了[key]属性?)但其余的都不是。

是否有一种将类字段映射到结果集的简单方法,例如在ado.net中(我只会执行SQLDataAdapter.Fill(MyDataTable),而MyDataTable字段将按字段名称显示)...或者我是每次必须做下面的选项2?

非常感谢!

string sqlQuery =" EXEC Maint.GetProductList'" + sNameFilter +"'&#34 ;;             //选项1:这在每个PrvProduct的字段中都没有值(ProductId获取值可能是因为它的[key],其他的不是't)             IQueryable results = _context.Products.FromSql(sqlQuery).AsNoTracking();

        //option 2: this works, but... do i have to do this for every stored proc i call, every field, or is there a beter way to map class fields to returned results fields?
        List<PrvProduct> oList = new List<PrvProduct>();
        using (var command = _context.Database.GetDbConnection().CreateCommand())
        {
            command.CommandText = sqlQuery;
            command.CommandType = CommandType.Text;

            _context.Database.OpenConnection();

            using (var result = command.ExecuteReader())
            {
                while (result.Read())
                {
                    // Map to your entity
                    oList.Add(new PrvProduct
                    {
                        ProductId = result.GetInt32(0),
                        ProductName = result.GetString(1)
                    });
                }
            }
        }

1 个答案:

答案 0 :(得分:0)

在EF Core中,如果您使用一个DbSet实体执行存储过程,则它将自动映射它。问题是,在许多情况下,例如,您需要将存储过程映射到DTO,而DTO并不是DbSet实体的一部分。在这种情况下,您需要返回时间并手动进行映射,这是浪费时间。

为了避免手动映射数据读取器,我添加了一堆扩展方法来为您完成此操作。代码并不完美,我仍在对其进行改进,但在大多数情况下。

一旦添加了我将在下面描述的扩展方法,就可以像这样使用它:

return dbContext.Database.SqlQuery<SalesReportDTO>("spGetSalesReport",
                SqlParameterBuilder.Build("customerId", customerId),
                SqlParameterBuilder.Build("dateFrom", from),
                SqlParameterBuilder.Build("dateTo", to)).ToList();

DatabaseFacadeExtensions:将扩展方法添加到DatabaseFacade类,使您可以从dbContext.Database调用方法SqlQuery,就像我们以前使用Entity Framework 6一样。

    public static class DatabaseFacadeExtensions
    {
        public static List<T> SqlQuery<T>(this DatabaseFacade database, string query, params SqlParameter[] parameters)
        {
            return SqlQuery<T>(database, query, null, CommandType.StoredProcedure, parameters);
        }

        public static List<T> SqlQuery<T>(this DatabaseFacade database, string query, CommandType commandType, params SqlParameter[] parameters)
        {
            return SqlQuery<T>(database, query, null, commandType, parameters);
        }

        public static List<T> SqlQuery<T>(this DatabaseFacade database, string query, int? commandTimeout, params SqlParameter[] parameters)
        {
            return SqlQuery<T>(database, query, commandTimeout, CommandType.StoredProcedure, parameters);
        }

        public static List<T> SqlQuery<T>(this DatabaseFacade database, string query, int? commandTimeout, CommandType commandType, params SqlParameter[] parameters)
        {
            using (var cmd = database.GetDbConnection().CreateCommand())
            {
                cmd.CommandText = query;
                cmd.CommandType = commandType;
                if (commandTimeout.HasValue)
                {
                    cmd.CommandTimeout = commandTimeout.Value;
                }
                cmd.Parameters.AddRange(parameters);

                if (cmd.Connection.State == System.Data.ConnectionState.Closed)
                {
                    cmd.Connection.Open();
                }

                try
                {
                    using (var reader = cmd.ExecuteReader())
                    {
                        return reader.MapToList<T>();
                    }
                }
                finally
                {
                    cmd.Connection.Close();
                }
            }
        }

    }

DbDataReaderExtensions:向DbDataReader类添加扩展方法,以便它可以将数据阅读器映射到您自己的案例。

public static class DbDataReaderExtensions
{
    public static List<T> MapToList<T>(this DbDataReader dr)
    {
        var objList = new List<T>();

        if (dr.HasRows)
        {
            bool isSingleValue = typeof(T).IsPrimitive || typeof(T) == typeof(string);
            IEnumerable<PropertyInfo> props = null;
            Dictionary<string, DbColumn> colMapping = null;

            if (!isSingleValue)
            {
                props = typeof(T).GetRuntimeProperties();
                colMapping = dr.GetColumnSchema()
                    .Where(x => props.Any(y => y.Name.ToLower() == x.ColumnName.ToLower()))
                    .ToDictionary(key => key.ColumnName.ToLower());
            }

            while (dr.Read())
            {
                T obj;
                if (isSingleValue)
                {
                    obj = (T)dr.GetValue(0);

                }
                else
                {
                    obj = Activator.CreateInstance<T>();
                    foreach (var prop in props)
                    {
                        string propertyName = prop.Name.ToLower();
                        if (!colMapping.ContainsKey(propertyName))
                        {
                            continue;
                        }

                        var val = dr.GetValue(colMapping[propertyName].ColumnOrdinal.Value);
                        if (val != DBNull.Value)
                        {
                            // enum property
                            if (prop.PropertyType.IsEnum)
                            {
                                prop.SetValue(obj, Enum.ToObject(prop.PropertyType, val));
                            }
                            // nullable enum property
                            if (prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>) && Nullable.GetUnderlyingType(prop.PropertyType).IsEnum)
                            {
                                prop.SetValue(obj, Enum.ToObject(Nullable.GetUnderlyingType(prop.PropertyType), val));
                            }
                            else
                            {
                                prop.SetValue(obj, val);
                            }
                        }
                    }
                }

                objList.Add(obj);
            }
        }

        return objList;
    }

    public static T MapToObject<T>(this DbDataReader dr)
    {
        var props = typeof(T).GetRuntimeProperties();

        if (dr.HasRows)
        {
            var colMapping = dr.GetColumnSchema()
                .Where(x => props.Any(y => y.Name.ToLower() == x.ColumnName.ToLower()))
                .ToDictionary(key => key.ColumnName.ToLower());

            if (dr.Read())
            {
                T obj = Activator.CreateInstance<T>();
                foreach (var prop in props)
                {
                    var val = dr.GetValue(colMapping[prop.Name.ToLower()].ColumnOrdinal.Value);
                    prop.SetValue(obj, val == DBNull.Value ? null : val);
                }

                return obj;
            }
        }

        return default(T);
    }

}

下一个类是可选的,但是我使用了一种更简单的方式来构建参数,而在上述示例中,它是必需的:

    public class SqlParameterBuilder
    {
        public static SqlParameter Build(string name, bool? value)
        {
            if (value.HasValue)
            {
                return new SqlParameter() { ParameterName = name, Value = value.Value };
            }

            return new SqlParameter() { ParameterName = name, Value = DBNull.Value };
        }

        public static SqlParameter Build(string name, int? value)
        {
            if (value.HasValue)
            {
                return new SqlParameter() { ParameterName = name, Value = value.Value };
            }

            return new SqlParameter() { ParameterName = name, Value = DBNull.Value };
        }


        public static SqlParameter Build(string name, string value)
        {
            if (value != null)
            {
                return new SqlParameter() { ParameterName = name, Value = value };
            }

            return new SqlParameter() { ParameterName = name, Value = DBNull.Value };
        }

        public static SqlParameter Build(string name, DateTime? value)
        {
            if (value != null)
            {
                return new SqlParameter { ParameterName = name, SqlDbType = SqlDbType.DateTime, Value = value };
            }

            return new SqlParameter() { ParameterName = name, Value = DBNull.Value };
        }

        public static SqlParameter Build(string name, Guid? value)
        {
            if (value.HasValue)
            {
                return new SqlParameter { ParameterName = name, SqlDbType = SqlDbType.UniqueIdentifier, Value = value };
            }

            return new SqlParameter() { ParameterName = name, Value = DBNull.Value };
        }

        public static SqlParameter Build(string name, int[] values)
        {
            SqlParameter par = new SqlParameter(name, SqlDbType.Structured);
            par.TypeName = "dbo.IntParameterList";
            DataTable dt = new DataTable();
            dt.Columns.Add("id", typeof(int));
            par.Value = dt;

            if (values != null)
            {
                foreach (int value in values.Where(p => p != 0))
                {
                    dt.Rows.Add(value);
                }
            }

            return par;
        }

        public static SqlParameter Build(string name, string[] values, VarcharParameterListEnum varcharParameterListType = VarcharParameterListEnum.Varchar50)
        {
            SqlParameter par = new SqlParameter(name, SqlDbType.Structured);
            switch(varcharParameterListType)
            {
                case VarcharParameterListEnum.Varchar15:
                    par.TypeName = "dbo.Varchar15ParameterList";
                    break;
                case VarcharParameterListEnum.Varchar50:
                    par.TypeName = "dbo.Varchar50ParameterList";
                    break;
                case VarcharParameterListEnum.Varchar100:
                    par.TypeName = "dbo.Varchar100ParameterList";
                    break;
                case VarcharParameterListEnum.Varchar255:
                    par.TypeName = "dbo.Varchar255ParameterList";
                    break;
                case VarcharParameterListEnum.Varchar510:
                    par.TypeName = "dbo.Varchar510ParameterList";
                    break;
            }

            DataTable dt = new DataTable();
            dt.Columns.Add("textValue", typeof(string));
            par.Value = dt;

            if (values != null)
            {
                foreach (var value in values.Where(p => !string.IsNullOrWhiteSpace(p)))
                {
                    dt.Rows.Add(value);
                }
            }

            return par;
        }

    }