Entity framework Code First - configure mapping fo

2020-01-31 02:20发布

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). My problem is that I want to map the columns' names to property with different names (I don't like the names coming from the Erp). I tried to use a Configuration class (like what I do when I map to views or tables) to specify the column name for properties with a different name, and here are my results:

  • if I don't use the configuration class (I don't add it in the OnModelCreating method of the DbContext) then EF works but loads only the properties that match exactly with the name of the columns (and this is what I expected in this case); others property are null;
  • if I use the configuration class (adding it to the modelBuilder in the OnModelCreating method of the DbContext) then EF raises an exception stating that "The data reader is incompatible with the specified '...Item'. A member of the type, 'Description', does not have a corresponding column in the data reader with the same name", and this sounds very strange to me, because in the configuration I specify that the property Description map to the column ItemDescription.

Why the configuration is affecting my result but its specification are not used to map the columns? Is there another way to specify this mapping using SqlQuery?

Here are the details:

My POCO class:

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

The configuration class:

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

The stored procedure return the data with the columns "Code" and "ItemDescription"; I call it in this way:

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

and with this I add the configuration to the context:

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

Thank you!

4条回答
够拽才男人
2楼-- · 2020-01-31 02:36

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);
                }
            }
        }
    }
查看更多
SAY GOODBYE
3楼-- · 2020-01-31 02:46

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 :-)

查看更多
三岁会撩人
4楼-- · 2020-01-31 02:49

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

    }
}
查看更多
等我变得足够好
5楼-- · 2020-01-31 02:55

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);
        }
    }
查看更多
登录 后发表回答