Entity framework Code First - configure mapping for SqlQuery

前端 未结 4 740
无人共我
无人共我 2020-12-05 15:24

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

4条回答
  •  臣服心动
    2020-12-05 16:25

    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);
                    }
                }
            }
        }
    

提交回复
热议问题