Entity Framework如何管理映射查询结果为匿名类型?

请考虑以下示例LINQ to entity查询

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别名。 Entity Framework在哪里存储匿名类型的映射信息?

背景:我将使用LINQ to entity进行大规模操作,使用select操作开发insert。

更新:使用select进行插入并不困难,除了未知列到属性映射算法。 可以使用元数据获取目标ObjectSet表名和列名,构建INSERT INTO tableName (column_name1, …) sql语句字符串,然后附加一些ObjectQuery.ToTraceString SELECT语句。 然后使用((EntityConnection)ObjectContext.Connection).StoreConnection和填充命令来自ObjectQuery的参数创建带有结果文本的DbCommand 。 所以问题是在插入和选择的记录中找到匹配的列顺序。

这是我在私人和内部的解决方案。 它会reflection到缓存的查询计划中,该计划将在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"); } } 

我认为可以放宽一些断言来检查不是确切的类型,而是检查包含必要属性的基类型。

有没有反思的解决方案?

列中查询别名的方式无关紧要,也不应该按顺序排列。 entity framework处理用每个结果填充匿名类型的新实例,这就是获取login别名的地方。

作为旁注,我认为entity framework可能无法正常运作。 您不能像使用普通的SQL查询一样在单个操作中执行select / insert。 entity framework将执行您的选择,返回结果,使用这些结果创建实体的新实例(或者在您的情况下,是匿名类型),然后您必须使用每个结果来创建目标的新实例输入,将每一个添加到您的实体/对象上下文,最后在您的实体/对象上下文中调用保存更改。 这将导致为您添加的每个新实体执行单独的insert语句。

如果要在单个操作中完成所有操作而不为每个记录实例化一个新实体,则需要使用在上下文中映射的存储过程,或者使用ObjectContext.ExecuteStoreCommand执行内联SQL查询。

更新:根据您的回答,您真正进入的内容更接近于依赖实体模型的元编程,而不是实际使用entity framework。 我不知道你正在使用什么版本的EF(EF 4.0?4.1 w / code first和DbContext?),但是我在使用带有EF 4.0的C#POCO模板时获得了很多成功(POCO模板是一个从在线视觉工作室画廊下载)。 它使用T4模板从.edmx数据模型生成POCO类。 在您的T4模板中,您可以向您的上下文添加基本上调用ExecuteStoreCommand ,但不同之处在于您可以生成基于您的数据模型执行的查询。 这样,只要数据模型发生变化,您的查询就会与更改保持同步。

更新了EF 4.4(5-RC)的reflection

完整的post在http://imaginarydevelopment.blogspot.com/2012/06/compose-complex-inserts-from-select.html

使用此function/逻辑从提供的某些参数的select中进行批量插入

 int Insert(IQueryable query,IQueryable 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().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().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 => xs).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; }