实体框架代码优先 - 为 SqlQuery 配置映射

Posted

技术标签:

【中文标题】实体框架代码优先 - 为 SqlQuery 配置映射【英文标题】:Entity framework Code First - configure mapping for SqlQuery 【发布时间】:2013-01-21 21:15:48 【问题描述】:

我正在使用 Entity Framework 5(使用 Code First 方法)从带有参数的遗留存储过程中填充我的一个类,这工作正常(详细信息如下)。 我的问题是我想将列的名称映射到具有不同名称的属性(我不喜欢来自 Erp 的名称)。 我尝试使用配置类(就像我映射到视图或表时所做的那样)来为具有不同名称的属性指定列名,这是我的结果:

如果我不使用配置类(我没有在 DbContext 的 OnModelCreating 方法中添加它),那么 EF 可以工作,但只加载与列名称完全匹配的属性(这就是我在这种情况下预计);其他属性为空; 如果我使用配置类(在 DbContext 的 OnModelCreating 方法中将其添加到 modelBuilder),则 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");
        
    

存储过程返回包含“代码”和“项目描述”列的数据;我是这样称呼的:

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 部分。 【参考方案1】:

我在这里找到的:

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

“SqlQuery 方法旨在不考虑任何映射...”。

他们还说“我们同意让 SqlQuery 尊重 Column 属性的选项很有用,因此我们保持这个问题开放并将其放在我们的积压工作中以供将来考虑。”,所以,如果你有我的同样的问题,请投票:-)

【讨论】:

2014 年 10 月,仍然一无所获。在 CodePlex 上投票。 2015 年 3 月。它现在具有建议状态。对 codeplex 投了赞成票。 2016 年 1 月仍未实施 2016 年 6 月 - 没有骰子 2017 年 8 月 - 尚未实施【参考方案2】:

同时,您可以使用此方法。 很少的测试(因为它适用于我的课程)但如果需要修复并不难...... 它需要一个上下文(检索映射的自定义类型),并且需要一个不同的连接来同时在其上运行数据读取器。

用法: 列出学生 = 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);
        

    

【讨论】:

【参考方案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 的方法几年并实现了一些代码之后,我决定在这里发布我们的改进。请注意,我不会发布对其他命名空间的引用。只需根据您的需要进行调整即可。

无论如何,我希望它对某人有所帮助。

使用系统; 使用 System.Collections.Generic; 使用 System.Collections.Immutable; 使用 System.Data; 使用 System.Data.Common; 使用 System.Data.Entity.Core.EntityClient; 使用 System.Data.Entity.Core.Mapping; 使用 System.Data.Entity.Core.Metadata.Edm; 使用 System.Data.Entity.Infrastructure; 使用 System.Diagnostics; 使用 System.Linq; 使用 System.Linq.Expressions; 公共抽象部分类 BaseService 其中 TEntity : EntityDefault 私有常量 int MAX_ITEMS_PER_PREDICATE = 500; /// /// Lista imutável Competitiono todos os predicates, por tipo da entidade, a serem buscados no banco de dados。 /// 私有 ImmutableDictionary> 谓词 获取;放; 私有 ImmutableDictionary PredicatesCount get;放; 私有 ImmutableDictionary> LoadedPredicates get;放; /// /// Lista imutável 竞争作为 entidades,que são propriedades de navegação,já buscadas no banco de dados。 /// 私有 ImmutableList NavigationEntities 获取;放; /// /// Lista imutável opinion todas as propriedades de navegação /// 私有 ImmutableList NavigationProperties get;放; /// /// 将查询结果映射到实体中。 /// /// /// SQL 查询。 /// 传递给过程的参数列表 /// /// 当查询为空或为空时,它可能返回空。 /// 一个实体列表 /// /// 语境 /// 要么 /// 查询连接 /// 要么 /// sql查询 /// 公共列表 SqlQuery(字符串查询,字典参数,参数 KeyValuePair[] 选项)其中 T:EntityDefault DbConnection queryConnection = null; 尝试 InitOrResetSqlQueryVariables(); 如果(查询.HasntValue()) 抛出新的 ArgumentNullException(nameof(query)); queryConnection = Db.Database.Connection; var connectionState = queryConnection.State; 如果(连接状态!=连接状态。打开) 查询连接.Open(); var command = queryConnection.CreateCommand(); command.CommandType = CommandType.StoredProcedure; command.CommandText = 查询; 如果(参数!= null) command.AddParameters(参数); var reader = command.ExecuteReader(); var 实体 = 新列表(); 而(读者。读()) entity.Add(MapEntity(reader)); LoadNavigationProperties(实体,选项); 返回实体; 最后 InitOrResetSqlQueryVariables(); if (Db.BaseDb.AutoCloseConnection && queryConnection != null) if (queryConnection.State != ConnectionState.Closed) 查询连接.关闭(); queryConnection.Dispose(); public List SqlQuery(string query, List parameters, params KeyValuePair[] options) where T : EntityDefault DbConnection queryConnection = null; 尝试 InitOrResetSqlQueryVariables(); 如果(查询.HasntValue()) 抛出新的 ArgumentNullException(nameof(query)); queryConnection = Db.Database.Connection; var connectionState = queryConnection.State; 如果(连接状态!=连接状态。打开) 查询连接.Open(); var command = queryConnection.CreateCommand(); command.CommandType = CommandType.StoredProcedure; command.CommandText = 查询; 如果(参数!= null) command.Parameters.AddRange(parameters.ToArray()); var reader = command.ExecuteReader(); var 实体 = 新列表(); 而(读者。读()) entity.Add(MapEntity(reader)); LoadNavigationProperties(实体,选项); 返回实体; 最后 InitOrResetSqlQueryVariables(); if (Db.BaseDb.AutoCloseConnection && queryConnection != null) if (queryConnection.State != ConnectionState.Closed) 查询连接.关闭(); queryConnection.Dispose(); 私人 T MapEntity(IDataRecord 阅读器) var entityObject = Activator.CreateInstance(); MapEntity(reader, 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) 。单身的() .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]; // 映射实体本身的属性 foreach(mappingFragment.PropertyMappings 中的 var propertyMapping) var valueBeforCasting = reader[((ScalarPropertyMapping)propertyMapping).Column.Name]; var value = valueBeforCasting 是 DBNull ?空值 : propertyMapping.Property.IsEnumType ? Convert.ChangeType(valueBeforCasting, 类型(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); // 映射相关的导航属性 foreach (在 NavigationProperties 中的 var navigationProperty) var propertyInfo = entityObject.GetType().GetProperty(navigationProperty.Name); // 待办事项: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 "继续;" * * Isso ajudará a evitar Consultas desnecessárias ao banco de dados。 */ var propertyType = propertyInfo.PropertyType; var 关联集映射 = 关联SetMappingCollection.First( a => a.AssociationSet.ElementType.FullName == navigationProperty.RelationshipType.FullName); // associationSetMapping.AssociationTypeMapping.MappingFragment.PropertyMappings 包含两个元素,一个用于直接关系,一个用于反向关系 var 属性映射 = 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] 为 DBNull) 继续; ///////////////////////////////////////// ///////////////////////////////////////// /////////////////////// // 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.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]))) loadPredicates.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 参数 = 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, 参数); 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] = 谓词; 谓词计数++; PredicatesCount = PredicatesCount.Replace(propertyType, predicatesCount); /// /// Carrega 通过 EagerLoading 作为 entidades associadas solicitadas /// /// 实体默认的具体说明 /// Lista de entidades que irão ter as entidades associadas carregadas /// Array de Eager Loadings a serem carregados 私人无效LoadNavigationProperties(IReadOnlyList实体, 参数 KeyValuePair[] eagerLoadings) 其中 T : EntityDefault foreach(谓词中的 var predicateItem) var newEagerLoadings = 新列表>(); var newOptions = 急切加载 .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。 前锋 ( var navigationProperty in NavigationProperties.Where( p => 实体[0].GetType().GetProperty(p.Name).PropertyType == predicateWhere.Key)) 新选项 = newOptions.Where(p => p.Value.ToString().StartsWith(navigationProperty.Name)).ToList(); if (!newOptions.Any()) 继续; // ReSharper 禁用一次 LoopCanBeConvertedToQuery foreach(newOptions 中的 var 选项) if (!option.Value.ToString().Contains(".")) 继续; var newOption = Pairing.Of(option.Key, option.Value.ToString() .RemovePrefix(navigationProperty.Name + ".") .RemovePrefix(navigationProperty.Name)); if (newOption.HasntValue() || newOption.Value.ToString().IsNullOrEmpty()) 继续; newEagerLoadings.Add(newOption); var predicateList = predicateItem.Value; var funcType = predicateItem.Value.First().InvokeMethod("Compile", true).GetType(); var newInstanceOfThis = GetInstanceOfService(funcType.GenericTypeArguments[0], Db); foreach(predicateList 中的 var 谓词) // A fim detentar evitar bugs de *** GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); var expandPredicate = typeof(Extensions).InvokeStaticGenericMethod(funcType, "Expand", predicate); var selectResponse = (IEnumerable)newInstanceOfThis.InvokeGenericMethod(predicateItem.Key, “很多”,expandedPredicate,newEagerLoadings.ToArray()); var listOfItems = selectResponse.ToList(); // Obtém o retorno // 执行一个查询 e preenche PredicateEntities NavigationEntities = NavigationEntities.AddRange(listOfItems); // 循环 nas entidades para atribuir as entidades associadas foreach(实体中的 var 实体) // Loop nas propriedades de navegação, para listar as entidades associadas foreach (在 NavigationProperties 中的 var navigationProperty) // navigationProperty é a entidade associada que será atribuída 实体 var propertyInfo = entity.GetType().GetProperty(navigationProperty.Name); var propertyType = propertyInfo.PropertyType; var propertyValue = propertyInfo.GetValue(entity); if (propertyValue == null) 继续; var idPropertyInfo = propertyType.GetProperty("Id"); var keyValue = idPropertyInfo.GetValue(propertyValue); 如果(键值 == 空) 继续; 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 继续; // Busca a entidade associada pelo Id, alimentado em "InternalMapEntity" var 关联实体实例 = associatedEntitiesOfSameType.FirstOrDefault( p => Convert.ToInt32(idPropertyInfo.GetValue(p)) == key); 如果(关联实体实例 == 空) 继续; // Não 本地化。 Removida do banco de dados? // Atribui a entidade associada a "entity" propertyInfo.SetValue(entity, associatedEntityInstance);

【讨论】:

以上是关于实体框架代码优先 - 为 SqlQuery 配置映射的主要内容,如果未能解决你的问题,请参考以下文章

实体框架代码优先 - 将两个字段合并为一个集合

来自 sql 查询执行实体框架的匿名类型结果

用于一对一识别关系的实体框架代码优先流利 API 配置

如何最小起订量实体框架 SqlQuery 调用

将实体框架从数据库优先转换为代码优先

实体框架代码优先 - 不可为空类型的默认值