MVC 5实体框架6执行存储过程

时间:2014-10-15 04:40:35

标签: asp.net-mvc stored-procedures entity-framework-6

我被困住了。我有一个现有的应用程序,具有一个非常大的数据库和广泛的存储过程和函数库。我想要做的就是使用DbContext来执行存储过程并返回一组数据或映射到上下文中的一个实体。这是我在网上没有发现的神奇的东西吗?有人,有人,请帮忙。这是我到目前为止所得到的(并没有返回任何内容,结果为-1):

var contacts = db.Database.ExecuteSqlCommand("Contact_Search @LastName, @FirstName",
    new SqlParameter("@LastName", GetDataValue(args.LastName)),
    new SqlParameter("@FirstName", GetDataValue(args.FirstName)));

执行该操作返回-1。我也尝试过这个效果,但没有成功:

DbRawSqlQuery<Contact> data = db.Database.SqlQuery<Contact>
                                   ("EXEC Contact_Search @LastName, @FirstName",
                                       GetDataValue(args.LastName), 
                                       GetDataValue(args.FirstName));

我知道我可以通过这种方式添加edmx并映射到存储过程,但这不是首选方法。同样,我们的数据库包含近4.5亿条记录和一个包含近3,000个存储过程和函数的库。维持这将是一场噩梦。我是否正朝着正确的方向前进?实体框架是正确的选择吗?

2 个答案:

答案 0 :(得分:16)

哇,似乎在我放弃之后,我不知何故偶然发现了答案。我找到了关于执行存储过程的FANTASTIC post,在阅读之后,这是我的解决方案:

var contacts = db.Database.SqlQuery<Contact>("Contact_Search @LastName, @FirstName",

所以,非常感谢Anuraj的出色发帖!我的解决方案的关键是首先使用SqlQuery而不是ExecuteSqlCommand,并执行方法映射到我的实体模型(Contact)。

答案 1 :(得分:4)

此代码优于SqlQuery(),因为SqlQuery()无法识别[Column]属性。 这是在银盘上。

public static class StoredProcedureExtensions {   
/// <summary>
/// Execute Stored Procedure and return result in an enumerable object.
/// </summary>
/// <typeparam name="TEntity">Type of enumerable object class to return.</typeparam>
/// <param name="commandText">SQL query.</param>
/// <param name="parameters">SQL parameters.</param>
/// <param name="readOnly">Determines whether to attach and track changes for saving. Defaults to true and entities will not be tracked and thus a faster call.</param>
/// <returns>IEnumerable of entity type.</returns>
public static IEnumerable<TEntity> GetStoredProcedureResults<TEntity>(this DbContext dbContext, string query, Dictionary<string, object> parameters, bool readOnly = true) where TEntity : class, new()
{
  SqlParameter[] sqlParameterArray = DbContextExtensions.DictionaryToSqlParameters(parameters);

  return dbContext.GetStoredProcedureResults<TEntity>(query, sqlParameterArray, readOnly);
}

/// <summary>
/// Execute Stored Procedure and return result in an enumerable object.
/// </summary>
/// <typeparam name="TEntity">Type of enumerable object class to return.</typeparam>
/// <param name="commandText">SQL query.</param>
/// <param name="parameters">SQL parameters.</param>
/// <param name="readOnly">Determines whether to attach and track changes for saving. Defaults to true and entities will not be tracked and thus a faster call.</param>
/// <returns>IEnumerable of entity type.</returns>
public static IEnumerable<TEntity> GetStoredProcedureResults<TEntity>(this DbContext dbContext, string commandText, SqlParameter[] sqlParameterArray = null, bool readOnly = true) where TEntity : class, new()
{
  string infoMsg = commandText;
  try
  {
    //---- For a better error message
    if (sqlParameterArray != null)
    {
      foreach (SqlParameter p in sqlParameterArray)
      {
        infoMsg += string.Format(" {0}={1}, ", p.ParameterName, p.Value == null ? "(null)" : p.Value.ToString());
      }
      infoMsg = infoMsg.Trim().TrimEnd(',');
    }

    ///////////////////////////
    var reader = GetReader(dbContext, commandText, sqlParameterArray, CommandType.StoredProcedure);
    ///////////////////////////

    ///////////////////////////
    List<TEntity> results = GetListFromDataReader<TEntity>(reader);
    ///////////////////////////

    if(readOnly == false)
    {
      DbSet entitySet = dbContext.Set<TEntity>(); // For attaching the entities so EF can track changes
      results.ForEach(n => entitySet.Attach(n));  // Add tracking to each entity
    }

    reader.Close();
    return results.AsEnumerable();
  }
  catch (Exception ex)
  {
    throw new Exception("An error occurred while executing GetStoredProcedureResults(). " + infoMsg + ". Check the inner exception for more details.\r\n" + ex.Message, ex);
  }
}

//========================================= Private methods
#region Private Methods

private static DbDataReader GetReader(DbContext dbContext, string commandText, SqlParameter[] sqlParameterArray, CommandType commandType)
{
  var command = dbContext.Database.Connection.CreateCommand();
  command.CommandText = commandText;
  command.CommandType = commandType;
  if (sqlParameterArray != null) command.Parameters.AddRange(sqlParameterArray);

  dbContext.Database.Connection.Open();
  var reader = command.ExecuteReader(CommandBehavior.CloseConnection);
  return reader;
}

private static List<TEntity> GetListFromDataReader<TEntity>(DbDataReader reader) where TEntity : class, new()
{
  PropertyInfo[]                entityProperties = typeof(TEntity).GetProperties();
  IEnumerable<string>           readerColumnNames = (reader.GetSchemaTable().Select()).Select(r => r.ItemArray[0].ToString().ToUpper()); // uppercase reader column names. 
  List<MappingPropertyToColumn> propertyToColumnMappings = GetPropertyToColumnMappings<TEntity>(); // Maps the entity property names to the corresponding names of the columns in the reader

  var entityList = new List<TEntity>(); // Fill this
  while (reader.Read())
  {
    var element = Activator.CreateInstance<TEntity>();
    foreach (var entityProperty in entityProperties)
    {
      MappingPropertyToColumn mapping = propertyToColumnMappings._Find(entityProperty.Name);
      if (mapping == null) // This property has a [Not Mapped] attribute
      {
        continue; // Skip this one
      }

      var o = (object)reader[mapping.ColumnName]; // mapping must match all mapped properties to columns. If result set does not contain a column, then throw error like EF would.

      bool hasValue = o.GetType() != typeof(DBNull);

      if (mapping.IsEnum && hasValue) // Enum
      {
        entityProperty.SetValue(element, Enum.Parse(mapping.UnderlyingType, o.ToString()));
      }
      else
      {
        if (hasValue)
        { 
          entityProperty.SetValue(element, ChangeType(o, entityProperty.PropertyType)); 
        }
      }
    }
    entityList.Add(element);
  }

  return entityList;
}

public static object ChangeType(object value, Type conversion)
{
  var t = conversion;

  if (t.IsGenericType && t.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
  {
    if (value == null)
    {
      return null;
    }

    t = Nullable.GetUnderlyingType(t);
  }

  return Convert.ChangeType(value, t);
}

private static List<MappingPropertyToColumn> GetPropertyToColumnMappings<TEntity>() where TEntity : new()
{
  var type = typeof(TEntity);
  List<MappingPropertyToColumn> databaseMappings = new List<MappingPropertyToColumn>();

  foreach (var entityProperty in type.GetProperties())
  {
    bool isEnum = entityProperty.PropertyType.IsEnum;

    // [Not Mapped] Not Mapped Attribute
    var notMapped = entityProperty.GetCustomAttributes(false).FirstOrDefault(attribute => attribute is NotMappedAttribute);
    if (notMapped != null) // This property has a [Not Mapped] attribute
    {
      continue; // Skip this property 
    }

    // Determine if property is an enum
    Type underlyingType = null;
    if (entityProperty.PropertyType.IsGenericType && entityProperty.PropertyType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
    {
      underlyingType = Nullable.GetUnderlyingType(entityProperty.PropertyType); ;
      if (underlyingType != null && underlyingType.IsEnum)
      {
        isEnum = true;
      }
    }

    // [Column("tbl_columnname")] Column Name Attribute for mapping
    var columnMapping = entityProperty.GetCustomAttributes(false).FirstOrDefault(attribute => attribute is ColumnAttribute);

    if (columnMapping != null)
    {
      databaseMappings.Add(new MappingPropertyToColumn { PropertyName = entityProperty.Name, ColumnName = ((ColumnAttribute)columnMapping).Name.ToUpper(), IsEnum = isEnum, UnderlyingType = underlyingType }); // SQL case insensitive
    }
    else
    {
      databaseMappings._AddProperty(entityProperty.Name, entityProperty.Name, isEnum); // C# case sensitive
    }
  }

  return databaseMappings;
}

//====================================== Class for holding column mappings and other info for each property
private class MappingPropertyToColumn
{
  private string _propertyName;
  public string PropertyName
  {
    get { return _propertyName; }
    set { _propertyName = value; }
  }

  private string _columnName;
  public string ColumnName
  {
    get { return _columnName; }
    set { _columnName = value; }
  }

  private bool _isNullableEnum;
  public bool IsEnum
  {
    get { return _isNullableEnum; }
    set { _isNullableEnum = value; }
  }

  private Type _underlyingType;
  public Type UnderlyingType
  {
    get { return _underlyingType; }
    set { _underlyingType = value; }
  }

}

//======================================================= List<MappingPropertyToColumn> Extension methods
#region List<MappingPropertyToColumn> Extension methods
private static bool _ContainsKey<T>(this List<T> list, string key) where T : MappingPropertyToColumn
{
  return list.Any(x => x.PropertyName == key);
}
private static MappingPropertyToColumn _Find<T>(this List<T> list, string key) where T : MappingPropertyToColumn
{
  return list.Where(x => x.PropertyName == key).FirstOrDefault();
}
private static void _AddProperty<T>(this List<T> list, string propertyName, string columnName, bool isEnum, Type underlyingType = null) where T : MappingPropertyToColumn
{
  list.Add((T)new MappingPropertyToColumn { PropertyName = propertyName, ColumnName = columnName, IsEnum = isEnum, UnderlyingType = underlyingType }); // C# case sensitive
}
#endregion

#endregion  }