2011-10-18 106 views
4

考慮下面的例子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到實體進行批量操作。

更新: 插入與選擇不是那麼難,除了一個未知的列屬性映射算法。可以使用元數據獲取目標ObjectSet的表和列名稱,編譯INSERT INTO tableName (column_name1, …) sql語句字符串,然後附加一些ObjectQuery.ToTraceString SELECT語句。然後使用((EntityConnection)ObjectContext.Connection).StoreConnection創建一個DbCommand,其結果文本爲((EntityConnection)ObjectContext.Connection).StoreConnection,填充命令參數ObjectQuery。所以問題是在插入和選擇的記錄中找到匹配的列順序。

回答

4

這是我的解決方案,一路下來的私人和內部。它會反射到緩存的查詢計劃中,該查詢計劃將在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"); 
    } 
} 

我認爲可以放鬆一些斷言來檢查確切的類型,但包含必要屬性的基本類型。

是否有沒有反射的解決方案?

+0

在這方面的任何更新? EF似乎有所改變,我做了調整,但它沒有搞清楚所有的列順序=( – Maslow

0

在查詢中列的別名如何不重要,它們的順序也不應該如此。實體框架處理每個結果的匿名類型的新實例,這就是你得到別名的地方,比如login

作爲一個方面說明,我認爲實體框架可能不會如你所想的那樣工作。您不能像使用普通SQL查詢那樣在單個操作中執行選擇/插入操作。實體框架將執行你的選擇,返回結果,使用這些結果來創建你的實體的新實例(或者你的情況,一個匿名類型),然後你將不得不使用每個結果來創建一個新的目標實例類型,將每個元素添加到實體/對象上下文中,最後在實體/對象上下文中調用保存更改。這將導致爲您添加的每個新實體執行單獨的插入語句。

如果您希望在單個操作中完成所有操作,而無需爲每個記錄實例化一個新實體,則需要使用您在上下文中映射的存儲過程,或者執行內聯SQL查詢使用ObjectContext.ExecuteStoreCommand

UPDATE:根據你的反應,你真正進入更接近元編程,它依賴於你的實體模型更比實際使用實體框架。我不知道你使用的是什麼版本的EF(EF 4.0?4.1 w/code first和DbContext?),但是我用EF 4.0的C#POCO模板取得了很大的成功(POCO模板是從在線視覺工作室畫廊下載)。它使用T4模板從.edmx數據模型生成POCO類。在您的T4模板中,您可以將方法添加到上下文中,本質上可以調用ExecuteStoreCommand,但區別在於您可以生成基於數據模型執行的查詢。這樣,只要您的數據模型發生變化,您的查詢就會與更改保持同步。

+0

我已更新帖子以解釋列順序的重要性。 與純SQL命令相比,LINQ的優勢之一就是強大的輸入。這意味着如果我更改列名稱然後重新生成模型代碼,我會在LINQ中遇到編譯器錯誤。這就是爲什麼我不想退回到ExecuteStoreCommand。 – Mike

+0

如果您打算使用元數據來構建查詢的'INSERT INTO ...'部分,爲什麼不爲'SELECT ...'部分做同樣的事情呢? –

+0

基本上我不知道在哪裏查找查詢列映射。我將'.msl'文件解析爲xml,以發現'ObjectSet '映射到一個表,因爲'ObjectContext.MetadataWorkspace.GetItemCollection(DataSpace.CSSpace)'幾乎是內部的,我試圖避免反射,直到它是唯一的選擇。另一方面,對象設置爲表映射是不夠的,因爲linq select可以包含常量或計算表達式,結果列在sql中被別名爲「C1」,「C2」等等。現在我正在探索CSSpace集合非公開成員,但沒有運氣。 – Mike

0

更新了反射在此爲EF 4.4(5-RC)

http://imaginarydevelopment.blogspot.com/2012/06/compose-complex-inserts-from-select.html

完整訊息使用此功能/邏輯用於從選擇做一個大容量插入與一些參數提供

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; 
} 
相關問題