I\'m using Entity Framework 5 (with Code First approach) to populate a class of mine from a legacy stored procedure with parameters, and this is working fine (details follow
I,ve just write bellow extention method to convert sql query to a property named sql and then query data.
hope be useful
public static class DbSetExtensions
{
public static DbSqlQuery<TEntity> SqlColumnQuery<TEntity>(this DbSet<TEntity> dbSet, string sqlQuery)
where TEntity : class
{
var context = GetContext(dbSet);
return dbSet.SqlQuery(MapQueryToColumns(sqlQuery, context, typeof(TEntity)));
}
public static DbContext GetContext<TEntity>(this DbSet<TEntity> dbSet)
where TEntity : class
{
object internalSet = dbSet
.GetType()
.GetField("_internalSet", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(dbSet);
object internalContext = internalSet
.GetType()
.BaseType
.GetField("_internalContext", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(internalSet);
return (DbContext)internalContext
.GetType()
.GetProperty("Owner", BindingFlags.Instance | BindingFlags.Public)
.GetValue(internalContext, null);
}
private static string MapQueryToColumns(string sqlQuery , DbContext context, Type entityType)
{
ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext;
var metadataWorkspace = ((EntityConnection)objectContext.Connection).GetMetadataWorkspace();
IEnumerable<EntitySetMapping> entitySetMappingCollection = metadataWorkspace.GetItems<EntityContainerMapping>(DataSpace.CSSpace).Single().EntitySetMappings;
//IEnumerable<AssociationSetMapping> associationSetMappingCollection = metadataWorkspace.GetItems<EntityContainerMapping>(DataSpace.CSSpace).Single().AssociationSetMappings;
var entitySetMappings = entitySetMappingCollection.First(o => o.EntityTypeMappings.Select(e => e.EntityType.Name).Contains(entityType.Name));
var entityTypeMapping = entitySetMappings.EntityTypeMappings[0];
string tableName = entityTypeMapping.EntitySetMapping.EntitySet.Name;
MappingFragment mappingFragment = entityTypeMapping.Fragments[0];
List<string> propertyMappings = new List<string>();
foreach (PropertyMapping propertyMapping in mappingFragment.PropertyMappings)
{
propertyMappings.Add(string.Format("{0} {1}", ((ScalarPropertyMapping)propertyMapping).Column.Name, propertyMapping.Property.Name));
}
var joinFields = string.Join(",",propertyMappings.ToArray());
return string.Format("SELECT {0} FROM ({1})", joinFields, sqlQuery);
}
}
Meanwhile, you can use this method. Few tests (because it worked for my classes) but not to difficult to fix if needed... It need a context (to retrieve mapped custom types) and it need a different connection to run a datareader on it at the same time.
Usage:
List students = Mapper.Map(context, (new SchoolContext()).Database.Connection, "Select * from Students");
public static class Mapper
{
/// <summary>
/// Maps the result of a query into entities.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="context">The context.</param>
/// <param name="queryConnection">The connection to run the query. Must be different from the one on the context.</param>
/// <param name="sqlQuery">The SQL query.</param>
/// <returns>An entity list</returns>
/// <exception cref="System.ArgumentNullException">
/// context
/// or
/// queryConnection
/// or
/// sqlQuery
/// </exception>
public static List<T> Map<T>(DbContext context, DbConnection queryConnection, string sqlQuery) where T:new()
{
if (context == null)
throw new ArgumentNullException("context");
if (queryConnection == null)
throw new ArgumentNullException("queryConnection");
if (sqlQuery == null)
throw new ArgumentNullException("sqlQuery");
var connectionState = queryConnection.State;
if (connectionState != ConnectionState.Open)
queryConnection.Open();
DbCommand command = queryConnection.CreateCommand();
command.CommandText = sqlQuery;
DbDataReader reader = command.ExecuteReader();
List<T> entities = new List<T>();
while (reader.Read())
{
entities.Add(InternalMap<T>(context, reader));
}
if (connectionState != ConnectionState.Open)
queryConnection.Close();
return entities;
}
private static T InternalMap<T>(DbContext context, DbDataReader reader) where T: new()
{
T entityObject = new T();
InternalMapEntity(context, reader, entityObject);
return entityObject;
}
private static void InternalMapEntity(DbContext context, DbDataReader reader, object entityObject)
{
ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext;
var metadataWorkspace = ((EntityConnection)objectContext.Connection).GetMetadataWorkspace();
IEnumerable<EntitySetMapping> entitySetMappingCollection = metadataWorkspace.GetItems<EntityContainerMapping>(DataSpace.CSSpace).Single().EntitySetMappings;
IEnumerable<AssociationSetMapping> associationSetMappingCollection = metadataWorkspace.GetItems<EntityContainerMapping>(DataSpace.CSSpace).Single().AssociationSetMappings;
var entitySetMappings = entitySetMappingCollection.First(o => o.EntityTypeMappings.Select(e => e.EntityType.Name).Contains(entityObject.GetType().Name));
var entityTypeMapping = entitySetMappings.EntityTypeMappings[0];
string tableName = entityTypeMapping.EntitySetMapping.EntitySet.Name;
Console.WriteLine(tableName);
MappingFragment mappingFragment = entityTypeMapping.Fragments[0];
foreach (PropertyMapping propertyMapping in mappingFragment.PropertyMappings)
{
object value = Convert.ChangeType(reader[((ScalarPropertyMapping) propertyMapping).Column.Name], propertyMapping.Property.PrimitiveType.ClrEquivalentType);
entityObject.GetType().GetProperty(propertyMapping.Property.Name).SetValue(entityObject, value, null);
Console.WriteLine("{0} {1} {2}", propertyMapping.Property.Name, ((ScalarPropertyMapping)propertyMapping).Column, value);
}
foreach (var navigationProperty in entityTypeMapping.EntityType.NavigationProperties)
{
PropertyInfo propertyInfo = entityObject.GetType().GetProperty(navigationProperty.Name);
AssociationSetMapping associationSetMapping = associationSetMappingCollection.First(a => a.AssociationSet.ElementType.FullName == navigationProperty.RelationshipType.FullName);
// associationSetMapping.AssociationTypeMapping.MappingFragment.PropertyMappings contains two elements one for direct and one for inverse relationship
EndPropertyMapping propertyMappings = associationSetMapping.AssociationTypeMapping.MappingFragment.PropertyMappings.Cast<EndPropertyMapping>().First(p => p.AssociationEnd.Name.EndsWith("_Target"));
object[] key = propertyMappings.PropertyMappings.Select(c => reader[c.Column.Name]).ToArray();
object value = context.Set(propertyInfo.PropertyType).Find(key);
propertyInfo.SetValue(entityObject, value, null);
}
}
}
I found here:
http://entityframework.codeplex.com/workitem/233?PendingVoteId=233
that "The SqlQuery method is designed not to take any mapping into account ...".
They say also "We agree that it would be useful to have the option to make SqlQuery honor Column attributes so we're keeping this issue open and putting it on our backlog for future consideration.", so, if you have my same problem, please vote :-)
After some years using bubi's approach, and implementing some code, I decided to post our improvements in here. Please, be advised that there are references to other namespaces which I WON'T post. Just adapt it to your needs.
Anyway, I hope it helps somebody.
using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Data; using System.Data.Common; using System.Data.Entity.Core.EntityClient; using System.Data.Entity.Core.Mapping; using System.Data.Entity.Core.Metadata.Edm; using System.Data.Entity.Infrastructure; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; public abstract partial class BaseService where TEntity : EntityDefault { private const int MAX_ITEMS_PER_PREDICATE = 500; /// /// Lista imutável contendo todos os predicates, por tipo da entidade, a serem buscados no banco de dados. /// private ImmutableDictionary> Predicates { get; set; } private ImmutableDictionary PredicatesCount { get; set; } private ImmutableDictionary> LoadedPredicates { get; set; } /// /// Lista imutável contendo as entidades, que são propriedades de navegação, já buscadas no banco de dados. /// private ImmutableList NavigationEntities { get; set; } /// /// Lista imutável contendo todas as propriedades de navegação /// private ImmutableList NavigationProperties { get; set; } /// /// Maps the result of a query into entities. /// /// /// The SQL query. /// List of parameters to be passed to the procedure /// /// It might return null when query is null or empty. /// An entity list /// /// context /// or /// queryConnection /// or /// sqlQuery /// public List SqlQuery(string query, Dictionary parameters, params KeyValuePair[] options) where T : EntityDefault { DbConnection queryConnection = null; try { InitOrResetSqlQueryVariables(); if (query.HasntValue()) { throw new ArgumentNullException(nameof(query)); } queryConnection = Db.Database.Connection; var connectionState = queryConnection.State; if (connectionState != ConnectionState.Open) { queryConnection.Open(); } var command = queryConnection.CreateCommand(); command.CommandType = CommandType.StoredProcedure; command.CommandText = query; if (parameters != null) { command.AddParameters(parameters); } var reader = command.ExecuteReader(); var entities = new List(); while (reader.Read()) { entities.Add(MapEntity(reader)); } LoadNavigationProperties(entities, options); return entities; } finally { InitOrResetSqlQueryVariables(); if (Db.BaseDb.AutoCloseConnection && queryConnection != null) { if (queryConnection.State != ConnectionState.Closed) { queryConnection.Close(); } queryConnection.Dispose(); } } } public List SqlQuery(string query, List parameters, params KeyValuePair[] options) where T : EntityDefault { DbConnection queryConnection = null; try { InitOrResetSqlQueryVariables(); if (query.HasntValue()) { throw new ArgumentNullException(nameof(query)); } queryConnection = Db.Database.Connection; var connectionState = queryConnection.State; if (connectionState != ConnectionState.Open) { queryConnection.Open(); } var command = queryConnection.CreateCommand(); command.CommandType = CommandType.StoredProcedure; command.CommandText = query; if (parameters != null) { command.Parameters.AddRange(parameters.ToArray()); } var reader = command.ExecuteReader(); var entities = new List(); while (reader.Read()) { entities.Add(MapEntity(reader)); } LoadNavigationProperties(entities, options); return entities; } finally { InitOrResetSqlQueryVariables(); if (Db.BaseDb.AutoCloseConnection && queryConnection != null) { if (queryConnection.State != ConnectionState.Closed) { queryConnection.Close(); } queryConnection.Dispose(); } } } private T MapEntity(IDataRecord reader) { var entityObject = Activator.CreateInstance(); MapEntity(reader, entityObject); return entityObject; } private void MapEntity(IDataRecord reader, object entityObject) { var objectContext = ((IObjectContextAdapter)Db).ObjectContext; var metadataWorkspace = ((EntityConnection)objectContext.Connection).GetMetadataWorkspace(); var entitySetMappingCollection = metadataWorkspace.GetItems(DataSpace.CSSpace).Single().EntitySetMappings; var associationSetMappingCollection = metadataWorkspace.GetItems(DataSpace.CSSpace) .Single() .AssociationSetMappings.ToList(); var entitySetMappings = entitySetMappingCollection.First( o => o.EntityTypeMappings.Select(e => e.EntityType.Name).Contains(entityObject.GetType().Name)); var entityTypeMapping = entitySetMappings.EntityTypeMappings[0]; var tableName = entityTypeMapping.EntitySetMapping.EntitySet.Name; Debug.WriteLine(tableName); var mappingFragment = entityTypeMapping.Fragments[0]; // Maps the properties of the entity itself foreach (var propertyMapping in mappingFragment.PropertyMappings) { var valueBeforCasting = reader[((ScalarPropertyMapping)propertyMapping).Column.Name]; var value = valueBeforCasting is DBNull ? null : propertyMapping.Property.IsEnumType ? Convert.ChangeType(valueBeforCasting, typeof(int)) : Convert.ChangeType(valueBeforCasting, propertyMapping.Property.PrimitiveType.ClrEquivalentType); entityObject.GetType() .GetProperty(propertyMapping.Property.Name) .SetValue(entityObject, value, null); Debug.WriteLine("{0} {1} {2}", propertyMapping.Property.Name, ((ScalarPropertyMapping)propertyMapping).Column, value); } if (NavigationProperties.Count == 0) { NavigationProperties = NavigationProperties.AddRange(entityTypeMapping.EntityType.NavigationProperties); } // Maps the associated navigational properties foreach (var navigationProperty in NavigationProperties) { var propertyInfo = entityObject.GetType().GetProperty(navigationProperty.Name); // TODO: Por Marco em 26/11/2015 /* * Verificar em QueryOptions (que neste momento não é passada para esta rotina) se foi solicitado Eager Loading desta navigationProperty. * Caso negativo executar um "continue;" * * Isso ajudará a evitar consultas desnecessárias ao banco de dados. */ var propertyType = propertyInfo.PropertyType; var associationSetMapping = associationSetMappingCollection.First( a => a.AssociationSet.ElementType.FullName == navigationProperty.RelationshipType.FullName); // associationSetMapping.AssociationTypeMapping.MappingFragment.PropertyMappings contains two elements one for direct and one for inverse relationship var propertyMappings = associationSetMapping.AssociationTypeMapping.MappingFragment.PropertyMappings .Cast().First(p => p.AssociationEnd.Name.EndsWith("_Target")); var key = propertyMappings.PropertyMappings.Select(c => reader[c.Column.Name]).ToArray(); if (!key.Any() || key[0] is DBNull) continue; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Monta o PredicateBuilder que será utilizado para trazer todas as entidades associadas solicitadas var outerPredicate = typeof(PredicateBuilder).InvokeStaticGenericMethod(propertyType, "False"); if (!Predicates.ContainsKey(propertyType)) { var predicatesList = new List { outerPredicate }; Predicates = Predicates.Add(propertyType, predicatesList); LoadedPredicates = LoadedPredicates.Add(propertyType, new List()); PredicatesCount = PredicatesCount.Add(propertyType, 0); } var loadedPredicates = LoadedPredicates[propertyType]; if (loadedPredicates.All(p => p != Convert.ToInt32(key[0]))) { loadedPredicates.Add(Convert.ToInt32(key[0])); BuildPredicate(propertyType, outerPredicate, Convert.ToInt32(key[0])); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Seta o Id como helper para a rotina LoadAssociatedEntities var value = Activator.CreateInstance(propertyType); var idProperty = propertyType.GetProperty("Id"); idProperty.SetValue(value, key[0]); propertyInfo.SetValue(entityObject, value, null); } } private void BuildPredicate(Type propertyType, object outerPredicate, int pkValue) { var parameter = Expression.Parameter(propertyType, "p"); var property = Expression.Property(parameter, "Id"); var valueToCompare = Expression.Constant(pkValue); var equalsExpression = Expression.Equal(property, valueToCompare); var funcType = typeof(Func).MakeGenericType(propertyType, typeof(bool)); var lambdaExpression = Expression.Lambda(funcType, equalsExpression, parameter); var predicateList = Predicates[propertyType]; var predicatesCount = PredicatesCount[propertyType]; if (predicatesCount % MAX_ITEMS_PER_PREDICATE == 0) { predicateList.Add(outerPredicate); } var predicate = predicateList.Last(); predicate = typeof(PredicateBuilder).InvokeStaticGenericMethod(propertyType, "Or", predicate, lambdaExpression); predicateList[predicateList.Count - 1] = predicate; predicatesCount++; PredicatesCount = PredicatesCount.Replace(propertyType, predicatesCount); } /// /// Carrega as entidades associadas solicitadas via EagerLoading /// /// Tipo específico de EntityDefault /// Lista de entidades que irão ter as entidades associadas carregadas /// Array de Eager Loadings a serem carregados private void LoadNavigationProperties(IReadOnlyList entities, params KeyValuePair[] eagerLoadings) where T : EntityDefault { foreach (var predicateItem in Predicates) { var newEagerLoadings = new List>(); var newOptions = eagerLoadings .Where(p => p.Key == QueryOptions.DefineInclude || p.Key == QueryOptions.DefineIncludes) .ToList(); var predicateWhere = predicateItem; // Loop em todas as propriedades de navegação de T que sejam do mesmo tipo do predicate.Key // Esse loop terá alimentado newEagerLoadings com os valores adequados. foreach ( var navigationProperty in NavigationProperties.Where( p => entities[0].GetType().GetProperty(p.Name).PropertyType == predicateWhere.Key)) { newOptions = newOptions.Where(p => p.Value.ToString().StartsWith(navigationProperty.Name)).ToList(); if (!newOptions.Any()) continue; // ReSharper disable once LoopCanBeConvertedToQuery foreach (var option in newOptions) { if (!option.Value.ToString().Contains(".")) { continue; } var newOption = Pairing.Of(option.Key, option.Value.ToString() .RemovePrefix(navigationProperty.Name + ".") .RemovePrefix(navigationProperty.Name)); if (newOption.HasntValue() || newOption.Value.ToString().IsNullOrEmpty()) { continue; } newEagerLoadings.Add(newOption); } } var predicateList = predicateItem.Value; var funcType = predicateItem.Value.First().InvokeMethod("Compile", true).GetType(); var newInstanceOfThis = GetInstanceOfService(funcType.GenericTypeArguments[0], Db); foreach (var predicate in predicateList) { // A fim de tentar evitar bugs de StackOverflow GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); var expandedPredicate = typeof(Extensions).InvokeStaticGenericMethod(funcType, "Expand", predicate); var selectResponse = (IEnumerable)newInstanceOfThis.InvokeGenericMethod(predicateItem.Key, "Many", expandedPredicate, newEagerLoadings.ToArray()); var listOfItems = selectResponse.ToList(); // Obtém o retorno // Executa a query e preenche PredicateEntities NavigationEntities = NavigationEntities.AddRange(listOfItems); } } // Loop nas entidades para atribuir as entidades associadas foreach (var entity in entities) { // Loop nas propriedades de navegação, para listar as entidades associadas foreach (var navigationProperty in NavigationProperties) { // navigationProperty é a entidade associada que será atribuída a entity var propertyInfo = entity.GetType().GetProperty(navigationProperty.Name); var propertyType = propertyInfo.PropertyType; var propertyValue = propertyInfo.GetValue(entity); if (propertyValue == null) { continue; } var idPropertyInfo = propertyType.GetProperty("Id"); var keyValue = idPropertyInfo.GetValue(propertyValue); if (keyValue == null) { continue; } var key = Convert.ToInt32(keyValue); // Pega a lista de entidades associadas que sejam do mesmo tipo da propriedade de navegação var associatedEntitiesOfSameType = NavigationEntities.Where(p => p.GetType() == propertyType) .ToList(); if (!associatedEntitiesOfSameType.Any()) { // O usuário não solicitou EagerLoading dessa navigationProperty continue; } // Busca a entidade associada pelo Id, alimentado em "InternalMapEntity" var associatedEntityInstance = associatedEntitiesOfSameType.FirstOrDefault( p => Convert.ToInt32(idPropertyInfo.GetValue(p)) == key); if (associatedEntityInstance == null) continue; // Não localizada. Removida do banco de dados? // Atribui a entidade associada a "entity" propertyInfo.SetValue(entity, associatedEntityInstance); } } } }