Entity framework Code First - настроить отображение для SqlQuery

Я использую Entity Framework 5 (с подходом Code First), чтобы заполнить класс моего из старой хранимой процедуры параметрами, и это работает нормально (подробности следуют). Моя проблема в том, что я хочу сопоставить имена столбцов с свойством с разными именами (мне не нравятся имена, исходящие от Erp). Я попытался использовать класс конфигурации (например, то, что я делаю, когда я сопоставляю представления или таблицы), чтобы указать имя столбца для свойств с другим именем, и вот мои результаты:

  • Если я не использую класс конфигурации (я не добавляю его в методе OnModelCreating для DbContext), тогда EF работает, но загружает только свойства, которые точно совпадают с именем столбцов (и это то, что я ожидаемый в этом случае); other - null;
  • Если я использую класс конфигурации (добавив его в modelBuilder в методе OnModelCreating для DbContext), то EF вызывает исключение, заявляя, что "Устройство чтения данных несовместимо с указанным"... Item ". type, 'Description', не имеет соответствующего столбца в устройстве чтения данных с тем же именем", и это звучит очень странно для меня, потому что в конфигурации я указываю, что свойство Description сопоставляется столбцу ItemDescription.

Почему настройка влияет на мой результат, но ее спецификация не используется для сопоставления столбцов? Есть ли другой способ указать это сопоставление с помощью SqlQuery?

Вот подробности:

Мой класс POCO:

public class Item
    {
        public String Id { get; set; }
        public String Description { get; set; }
    }

Класс конфигурации:

public class ItemConfiguration : EntityTypeConfiguration<Item>
    {
        public ItemConfiguration()
        {
            HasKey(x => new { x.Id });
            Property(x => x.Id).HasColumnName("Code");
            Property(x => x.Description).HasColumnName("ItemDescription");
        }
    }

Хранимая процедура возвращает данные с столбцами "Код" и "ItemDescription"; Я называю это следующим образом:

var par = new SqlParameter();
par.ParameterName = "@my_par";
par.Direction = ParameterDirection.Input;
par.SqlDbType = SqlDbType.VarChar;
par.Size = 20;
par.Value = ...;

var data = _context.Database.SqlQuery<Item>("exec spItem @my_par", par);

и с этим я добавляю конфигурацию в контекст:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
      modelBuilder.Configurations.Add(new ItemConfiguration());
}

Спасибо!

Ответ 1

Я нашел здесь:

http://entityframework.codeplex.com/workitem/233?PendingVoteId=233

что "метод SqlQuery разработан, чтобы не учитывать какое-либо сопоставление...".

Они также говорят: "Мы согласны с тем, что было бы полезно иметь возможность присвоить атрибуты Column Column SqlQuery, чтобы мы открывали эту проблему и помещали ее в наш портфель для будущего рассмотрения". Итак, если у вас есть мой такая же проблема, пожалуйста, голосуйте: -)

Ответ 2

Между тем, вы можете использовать этот метод. Немного тестов (потому что это работало для моих классов), но не сложно было при необходимости исправить... Для этого нужен контекст (для извлечения сопоставленных пользовательских типов), и ему нужно другое соединение для одновременного запуска сервера данных.

Использование:
Список студентов = Mapper.Map(контекст, (новый 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);
        }

    }
}

Ответ 3

Я просто напишу ниже метод расширения, чтобы преобразовать SQL-запрос в свойство с именем SQL, а затем запросить данные.

надеюсь быть полезным

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

Ответ 4

После нескольких лет использования подхода bubi и реализации некоторого кода, я решил опубликовать наши улучшения здесь. Пожалуйста, обратите внимание, что есть ссылки на другие пространства имен, которые я не буду публиковать. Просто адаптируйте его под свои нужды.

В любом случае, я надеюсь, что это кому-нибудь поможет.

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