Entity Framework Code First - 配置 SqlQuery 的映射

13
我正在使用Entity Framework 5(采用Code First方法)从具有参数的遗留存储过程中填充一个类,这很好用(细节如下)。 我的问题是,我想将列名映射到具有不同名称的属性(我不喜欢来自Erp的名称)。 我尝试使用配置类(就像映射到视图或表时所做的那样),以指定具有不同名称的属性的列名称,以下是我的结果:
  • 如果我不使用配置类(我没有在DbContext的OnModelCreating方法中添加它),那么EF可以工作,但仅加载与列名称完全匹配的属性(在这种情况下,这就是我预期的);其他属性则为null;
  • 如果我使用配置类(在DbContext的OnModelCreating方法中将其添加到modelBuilder中),那么EF会引发异常,指出“数据读取器与指定的'...Item'不兼容。类型的成员'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");
        }
    }

存储过程返回带有 "Code" 和 "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());
}

谢谢你!


我很高兴看到这篇文章。我一直在苦苦挣扎,而我所需要的就是exec部分。我找到了很多例子,但没有一个有exec部分。 - Brendan Vogt
4个回答

11

我在这里找到了:

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

其中提到:“SqlQuery方法的设计不考虑任何映射...”。

他们还说:“我们同意,让SqlQuery遵守列属性将非常有用,因此我们将保持此问题处于打开状态,并将其放入我们的待办事项中以便日后考虑。”所以,如果您与我有相同的问题,请投票支持:-)


1
2014年10月,仍然没有任何进展。在CodePlex上投票支持此问题。 - Paul Smith
2
2015年3月。现在它处于提议状态。在CodePlex上得到了赞同。 - Shashank
1
2016年1月仍未实现。 - silkfire
1
2016年6月 - 没有骰子 - Zar Shardan
1
2017年8月 - 尚未实施 - code4j
1
2019年9月,EF6.2仍未实现。 - IulianT

3

同时,你可以使用这种方法。 虽然测试次数较少(因为它已经在我的课程中工作),但如果需要的话也不难修复...... 它需要一个上下文(用于检索映射的自定义类型),并且需要一个不同的连接来同时运行数据读取器。

用法:
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);
        }

    }
}

0
我刚写了下面的扩展方法,可以将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);
        }
    }

0

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接