实体框架如何管理映射查询结果到匿名类型
本文关键字:结果 查询 类型 映射 框架 何管理 管理 实体 | 更新日期: 2023-09-27 18:11:48
考虑以下示例:LINQ对实体查询
from history in entities.foreignuserhistory
select new { history.displayname, login=history.username, history.foreignuserid }
ToTraceString()
返回字符串如下:
SELECT "Extent1"."foreignuserid" AS "foreignuserid",
"Extent1"."displayname" AS "displayname",
"Extent1"."username" AS "username"
FROM "integration"."foreignuserhistory" AS "Extent1"
对我来说,问题是列从查询中以不同的顺序出现,并且在示例中没有使用login
这样的别名。实体框架在哪里存储匿名类型的映射信息?
背景:我将使用LINQ来开发插入和选择操作,用于批量操作。
更新:除了未知列到属性映射算法之外,使用select插入并不难。可以使用元数据获取目标ObjectSet
的表名和列名,构建INSERT INTO tableName (column_name1, …)
sql语句字符串,然后附加ObjectQuery.ToTraceString
SELECT语句。然后使用((EntityConnection)ObjectContext.Connection).StoreConnection
和ObjectQuery
中填充命令的参数创建一个DbCommand
,其中包含结果文本。因此,问题是在插入和选择的记录中找到匹配的列顺序。
这是我的解决方案,一直到私有和内部。它与反射一起传播到缓存的查询计划中,该计划将在ToTraceString
调用或查询执行之后存在,以获得所谓的_columnMap
。列映射包含ScalarColumnMap
对象,按照匿名对象的属性顺序,并指向具有ColumnPos
属性的相应列位置。
using System;
using System.Data.Objects;
using System.Reflection;
static class EFQueryUtils
{
public static int[] GetPropertyPositions(ObjectQuery query)
{
// get private ObjectQueryState ObjectQuery._state;
// of actual type internal class
// System.Data.Objects.ELinq.ELinqQueryState
object queryState = GetProperty(query, "QueryState");
AssertNonNullAndOfType(queryState, "System.Data.Objects.ELinq.ELinqQueryState");
// get protected ObjectQueryExecutionPlan ObjectQueryState._cachedPlan;
// of actual type internal sealed class
// System.Data.Objects.Internal.ObjectQueryExecutionPlan
object plan = GetField(queryState, "_cachedPlan");
AssertNonNullAndOfType(plan, "System.Data.Objects.Internal.ObjectQueryExecutionPlan");
// get internal readonly DbCommandDefinition ObjectQueryExecutionPlan.CommandDefinition;
// of actual type internal sealed class
// System.Data.EntityClient.EntityCommandDefinition
object commandDefinition = GetField(plan, "CommandDefinition");
AssertNonNullAndOfType(commandDefinition, "System.Data.EntityClient.EntityCommandDefinition");
// get private readonly IColumnMapGenerator EntityCommandDefinition._columnMapGenerator;
// of actual type private sealed class
// System.Data.EntityClient.EntityCommandDefinition.ConstantColumnMapGenerator
object columnMapGenerator = GetField(commandDefinition, "_columnMapGenerator");
AssertNonNullAndOfType(columnMapGenerator, "System.Data.EntityClient.EntityCommandDefinition+ConstantColumnMapGenerator");
// get private readonly ColumnMap ConstantColumnMapGenerator._columnMap;
// of actual type internal class
// System.Data.Query.InternalTrees.SimpleCollectionColumnMap
object columnMap = GetField(columnMapGenerator, "_columnMap");
AssertNonNullAndOfType(columnMap, "System.Data.Query.InternalTrees.SimpleCollectionColumnMap");
// get internal ColumnMap CollectionColumnMap.Element;
// of actual type internal class
// System.Data.Query.InternalTrees.RecordColumnMap
object columnMapElement = GetProperty(columnMap, "Element");
AssertNonNullAndOfType(columnMapElement, "System.Data.Query.InternalTrees.RecordColumnMap");
// get internal ColumnMap[] StructuredColumnMap.Properties;
// array of internal abstract class
// System.Data.Query.InternalTrees.ColumnMap
Array columnMapProperties = GetProperty(columnMapElement, "Properties") as Array;
AssertNonNullAndOfType(columnMapProperties, "System.Data.Query.InternalTrees.ColumnMap[]");
int n = columnMapProperties.Length;
int[] propertyPositions = new int[n];
for (int i = 0; i < n; ++i)
{
// get value at index i in array
// of actual type internal class
// System.Data.Query.InternalTrees.ScalarColumnMap
object column = columnMapProperties.GetValue(i);
AssertNonNullAndOfType(column, "System.Data.Query.InternalTrees.ScalarColumnMap");
//string colName = (string)GetProp(column, "Name");
// can be used for more advanced bingings
// get internal int ScalarColumnMap.ColumnPos;
object columnPositionOfAProperty = GetProperty(column, "ColumnPos");
AssertNonNullAndOfType(columnPositionOfAProperty, "System.Int32");
propertyPositions[i] = (int)columnPositionOfAProperty;
}
return propertyPositions;
}
static object GetProperty(object obj, string propName)
{
PropertyInfo prop = obj.GetType().GetProperty(propName, BindingFlags.NonPublic | BindingFlags.Instance);
if (prop == null) throw EFChangedException();
return prop.GetValue(obj, new object[0]);
}
static object GetField(object obj, string fieldName)
{
FieldInfo field = obj.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
if (field == null) throw EFChangedException();
return field.GetValue(obj);
}
static void AssertNonNullAndOfType(object obj, string fullName)
{
if (obj == null) throw EFChangedException();
string typeFullName = obj.GetType().FullName;
if (typeFullName != fullName) throw EFChangedException();
}
static InvalidOperationException EFChangedException()
{
return new InvalidOperationException("Entity Framework internals has changed, please review and fix reflection code");
}
}
我认为有些断言可以放宽,不检查确切的类型,而是检查包含必要属性的基本类型。
有不反思的解决方案吗?
列在查询中的别名不重要,它们的顺序也不重要。实体框架处理用每个结果填充匿名类型的新实例,这就是您获得别名的地方,如login
。
作为旁注,我认为实体框架可能不完全像你想象的那样工作。您不能像使用普通SQL查询那样在单个操作中执行选择/插入操作。实体框架将执行你的选择,返回结果,使用这些结果来创建实体的新实例(或者在你的情况下,一个匿名类型),然后你必须使用每个结果来创建一个目标类型的新实例,将每个结果添加到你的实体/对象上下文中,最后在你的实体/对象上下文中调用保存更改。这将导致为您添加的每个新实体执行单独的插入语句。
如果您想在单个操作中完成所有操作,而不为每条记录实例化一个新实体,您需要使用您在上下文中映射的存储过程,或者使用ObjectContext.ExecuteStoreCommand
执行内联SQL查询
UPDATE:根据你的回答,你真正进入的是更接近于依赖于你的实体模型的元编程,而不是实际使用实体框架。我不知道你用的是什么版本的EF (EF 4.0?4.1 w/code优先和DbContext?),但我在EF 4.0中使用c# POCO模板(POCO模板是从在线visual studio库下载的)取得了很多成功。它使用T4模板从.edmx数据模型生成POCO类。在T4模板中,您可以向上下文添加调用ExecuteStoreCommand
的方法,但不同之处在于,您可以生成基于数据模型执行的查询。这样,无论何时数据模型更改,您的查询都将与更改保持同步。
更新了EF 4.4 (5-RC)的反射
网址:http://imaginarydevelopment.blogspot.com/2012/06/compose-complex-inserts-from-select.html
使用此功能/逻辑从select中执行批量插入,并提供一些参数
int Insert<T>(IQueryable query,IQueryable<T> targetSet)
{
var oQuery=(ObjectQuery)this.QueryProvider.CreateQuery(query.Expression);
var sql=oQuery.ToTraceString();
var propertyPositions = GetPropertyPositions(oQuery);
var targetSql=((ObjectQuery)targetSet).ToTraceString();
var queryParams=oQuery.Parameters.ToArray();
System.Diagnostics.Debug.Assert(targetSql.StartsWith("SELECT"));
var queryProperties=query.ElementType.GetProperties();
var selectParams=sql.Substring(0,sql.IndexOf("FROM "));
var selectAliases=Regex.Matches(selectParams,@"'sAS '[([a-zA-Z0-9_]+)']").Cast<Match>().Select(m=>m.Groups[1].Value).ToArray();
var from=targetSql.Substring(targetSql.LastIndexOf("FROM [")+("FROM [".Length-1));
var fromAlias=from.Substring(from.LastIndexOf("AS ")+"AS ".Length);
var target=targetSql.Substring(0,targetSql.LastIndexOf("FROM ["));
target=target.Replace("SELECT","INSERT INTO "+from+" (")+")";
target=target.Replace(fromAlias+".",string.Empty);
target=Regex.Replace(target,@"'sAS '[[a-zA-z0-9]+']",string.Empty);
var insertParams=target.Substring(target.IndexOf('('));
target = target.Substring(0, target.IndexOf('('));
var names=Regex.Matches(insertParams,@"'[([a-zA-Z0-9]+)']");
var remaining=names.Cast<Match>().Select(m=>m.Groups[1].Value).Where(m=>queryProperties.Select(qp=>qp.Name).Contains(m)).ToArray(); //scrape out items that the anonymous select doesn't include a name/value for
//selectAliases[propertyPositions[10]]
//remaining[10]
var insertParamsOrdered = remaining.Select((s, i) => new { Position = propertyPositions[i], s })
.OrderBy(o => o.Position).Select(x => x.s).ToArray();
var insertParamsDelimited = insertParamsOrdered.Aggregate((s1, s2) => s1 + "," + s2);
var commandText = target + "(" + insertParamsDelimited + ")" + sql;
var result=this.ExecuteStoreCommand(commandText,queryParams.Select(qp=>new System.Data.SqlClient.SqlParameter{ ParameterName=qp.Name, Value=qp.Value}).ToArray());
return result;
}