2016-09-29 43 views
2

請考慮以下數據庫表(SQL Server 2005)。我想在EF(v6,.net 4.5.1)中使用Translate功能,但搜索後似乎不支持。動態轉換以避免C#語法錯誤

CREATE TABLE Foo 
(
    pk INT NOT NULL PRIMARY KEY, 
    Foo VARCHAR(100) 
) 

利用會展映射,將創建一個類Foo與未通過C#語法支持的特性Foo。我嘗試使用ColumnAttribute

public partial class Foo 
{ 
    [Key] 
    public virtual int pk {get;set;} 
    [Column("Foo")] 
    public virtual string Name {get;set;} 
} 

這似乎工作,但我想使初始頁面請求通過存儲過程和火星加載採空區的數據(並使用一個通用的結構,這樣我就可以重新使用它其他網頁),所以我打電話給存儲過程,並通過結果集循環,呼籲通過反射ObjectContext.Translate(類似於以下,但略):

var methTranslate = typeof(ObjectContext).GetMethod("Translate", new[] { typeof(DbDataReader), typeof(string), typeof(MergeOption) }); 

foreach (var className in classNames) 
{ 
    // ... 
    var translateGenericMethod = methTranslate.MakeGenericMethod(classType); 
    // ... 
    reader.NextResult(); 
    var enumerable = (IEnumerable)translateGenericMethod.Invoke(ObjectContext, 
     new object[] { reader, entitySet.Name, MergeOption.AppendOnly }); 
} 

multiplethings我讀過的ColumnAttribute映射不受支持。從MSDN

EF does not take any mapping into account when it creates entities using the Translate method. It will simply match column names in the result set with property names on your classes.

果然,我得到和錯誤:

The data reader is incompatible with the specified 'Namespace.Foo'. A member of the type, 'Name', does not have a corresponding column in the data reader with the same name.

的問題是,我沒有看到在映射任何替代或方式來指定/提示。我可以更改類名稱,但不如屬性名稱可取。

任何解決方法,或任何其他方式來動態加載數據,而不使用Translate

+0

讀取存儲過程+動態數據結構= [Dapper](https://github.com/StackExchange/dapper-dot-net)。 –

回答

1

有點棘手,但可行。

這個想法是利用Translate方法,通過實現和使用執行所需映射的自定義DbDataReader

在這之前,讓我們實現一個通用DbDataReader類,它只是委託給底層DbDataReader

abstract class DelegatingDbDataReader : DbDataReader 
{ 
    readonly DbDataReader source; 
    public DelegatingDbDataReader(DbDataReader source) 
    { 
     this.source = source; 
    } 
    public override object this[string name] { get { return source[name]; } } 
    public override object this[int ordinal] { get { return source[ordinal]; } } 
    public override int Depth { get { return source.Depth; } } 
    public override int FieldCount { get { return source.FieldCount; } } 
    public override bool HasRows { get { return source.HasRows; } } 
    public override bool IsClosed { get { return source.IsClosed; } } 
    public override int RecordsAffected { get { return source.RecordsAffected; } } 
    public override bool GetBoolean(int ordinal) { return source.GetBoolean(ordinal); } 
    public override byte GetByte(int ordinal) { return source.GetByte(ordinal); } 
    public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length) { return source.GetBytes(ordinal, dataOffset, buffer, bufferOffset, length); } 
    public override char GetChar(int ordinal) { return source.GetChar(ordinal); } 
    public override long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length) { return source.GetChars(ordinal, dataOffset, buffer, bufferOffset, length); } 
    public override string GetDataTypeName(int ordinal) { return source.GetDataTypeName(ordinal); } 
    public override DateTime GetDateTime(int ordinal) { return source.GetDateTime(ordinal); } 
    public override decimal GetDecimal(int ordinal) { return source.GetDecimal(ordinal); } 
    public override double GetDouble(int ordinal) { return source.GetDouble(ordinal); } 
    public override IEnumerator GetEnumerator() { return source.GetEnumerator(); } 
    public override Type GetFieldType(int ordinal) { return source.GetFieldType(ordinal); } 
    public override float GetFloat(int ordinal) { return source.GetFloat(ordinal); } 
    public override Guid GetGuid(int ordinal) { return source.GetGuid(ordinal); } 
    public override short GetInt16(int ordinal) { return source.GetInt16(ordinal); } 
    public override int GetInt32(int ordinal) { return source.GetInt32(ordinal); } 
    public override long GetInt64(int ordinal) { return source.GetInt64(ordinal); } 
    public override string GetName(int ordinal) { return source.GetName(ordinal); } 
    public override int GetOrdinal(string name) { return source.GetOrdinal(name); } 
    public override string GetString(int ordinal) { return source.GetString(ordinal); } 
    public override object GetValue(int ordinal) { return source.GetValue(ordinal); } 
    public override int GetValues(object[] values) { return source.GetValues(values); } 
    public override bool IsDBNull(int ordinal) { return source.IsDBNull(ordinal); } 
    public override bool NextResult() { return source.NextResult(); } 
    public override bool Read() { return source.Read(); } 
    public override void Close() { source.Close(); } 
    public override T GetFieldValue<T>(int ordinal) { return source.GetFieldValue<T>(ordinal); } 
    public override Task<T> GetFieldValueAsync<T>(int ordinal, CancellationToken cancellationToken) { return source.GetFieldValueAsync<T>(ordinal, cancellationToken); } 
    public override Type GetProviderSpecificFieldType(int ordinal) { return source.GetProviderSpecificFieldType(ordinal); } 
    public override object GetProviderSpecificValue(int ordinal) { return source.GetProviderSpecificValue(ordinal); } 
    public override int GetProviderSpecificValues(object[] values) { return source.GetProviderSpecificValues(values); } 
    public override DataTable GetSchemaTable() { return source.GetSchemaTable(); } 
    public override Stream GetStream(int ordinal) { return source.GetStream(ordinal); } 
    public override TextReader GetTextReader(int ordinal) { return source.GetTextReader(ordinal); } 
    public override Task<bool> IsDBNullAsync(int ordinal, CancellationToken cancellationToken) { return source.IsDBNullAsync(ordinal, cancellationToken); } 
    public override Task<bool> ReadAsync(CancellationToken cancellationToken) { return source.ReadAsync(cancellationToken); } 
    public override int VisibleFieldCount { get { return source.VisibleFieldCount; } } 
} 

沒什麼特別 - 煩人重寫所有抽象/有意義的虛擬成員和委託給底層對象。

現在執行名稱映射讀者:

class MappingDbDataReader : DelegatingDbDataReader 
{ 
    Dictionary<string, string> nameToSourceNameMap; 
    public MappingDbDataReader(DbDataReader source, Dictionary<string, string> nameToSourceNameMap) : base(source) 
    { 
     this.nameToSourceNameMap = nameToSourceNameMap; 
    } 
    private string GetSourceName(string name) 
    { 
     string sourceName; 
     return nameToSourceNameMap.TryGetValue(name, out sourceName) ? sourceName : name; 
    } 
    public override object this[string name] 
    { 
     get { return base[GetSourceName(name)]; } 
    } 
    public override string GetName(int ordinal) 
    { 
     string sourceName = base.GetName(ordinal); 
     return nameToSourceNameMap 
      .Where(item => item.Value.Equals(sourceName, StringComparison.OrdinalIgnoreCase)) 
      .Select(item => item.Key) 
      .FirstOrDefault() ?? sourceName; 
    } 
    public override int GetOrdinal(string name) 
    { 
     return base.GetOrdinal(GetSourceName(name)); 
    } 
} 

同樣,沒有任何幻想。重寫幾個方法併爲列名稱和反向映射執行一個名稱。

最後,做你所要求的一個輔助方法:

public static class EntityUtils 
{ 
    public static ObjectResult<T> ReadSingleResult<T>(this DbContext dbContext, DbDataReader dbReader) 
     where T : class 
    { 
     var objectContext = ((IObjectContextAdapter)dbContext).ObjectContext; 
     var columnMappings = objectContext.GetPropertyMappings(typeof(T)) 
      .ToDictionary(m => m.Property.Name, m => m.Column.Name); 
     var mappingReader = new MappingDbDataReader(dbReader, columnMappings); 
     return objectContext.Translate<T>(mappingReader); 
    } 

    static IEnumerable<ScalarPropertyMapping> GetPropertyMappings(this ObjectContext objectContext, Type clrEntityType) 
    { 
     var metadata = objectContext.MetadataWorkspace; 

     // Get the part of the model that contains info about the actual CLR types 
     var objectItemCollection = ((ObjectItemCollection)metadata.GetItemCollection(DataSpace.OSpace)); 

     // Get the entity type from the model that maps to the CLR type 
     var entityType = metadata 
       .GetItems<EntityType>(DataSpace.OSpace) 
         .Single(e => objectItemCollection.GetClrType(e) == clrEntityType); 

     // Get the entity set that uses this entity type 
     var entitySet = metadata 
      .GetItems<EntityContainer>(DataSpace.CSpace) 
        .Single() 
        .EntitySets 
        .Single(s => s.ElementType.Name == entityType.Name); 

     // Find the mapping between conceptual and storage model for this entity set 
     var mapping = metadata.GetItems<EntityContainerMapping>(DataSpace.CSSpace) 
         .Single() 
         .EntitySetMappings 
         .Single(s => s.EntitySet == entitySet); 

     // Find the storage property (column) mappings 
     var propertyMappings = mapping 
      .EntityTypeMappings.Single() 
      .Fragments.Single() 
      .PropertyMappings 
      .OfType<ScalarPropertyMapping>(); 


     return propertyMappings; 
    } 

ReadSingleResult是有問題的helper方法。 GetPropertyMappings方法正在使用EF6.1 Get Mapping Between Properties and Columns的部分代碼。

樣品使用類似於提供的示例:

var readMethodBase = typeof(EntityUtils).GetMethod("ReadSingleResult", new[] { typeof(DbContext), typeof(DbDataReader) }); 

foreach (var className in classNames) 
{ 
    // ... 
    var readMethod = readMethodBase.MakeGenericMethod(classType); 
    var result = ((IEnumerable)readMethod.Invoke(null, new object[] { dbContext, dbReader })) 
     .Cast<dynamic>() 
     .ToList(); 
    // ... 
    dbReader.NextResult(); 
} 

希望有所幫助。

+0

這似乎有效,但Translate做的一件事是將'result's添加到DbContext。看來這需要明確地做,當我添加'while(enumarator.MoveNext()){this.Entry(enumarator.Current).State = EntityState.Unchanged; };'對於foreach循環,加載需要30秒以上:(我會將這個標記爲解決方案,但也許我試圖完成錯誤的問題 – mlhDev

+0

@Matthew實際上一個區別是我使用了一個更簡單的'Translate'你可以從我的示例中獲取自定義數據讀取器,並使用'entitySet.Name,MergeOption.AppendOnly'中的重載作爲你的原始代碼 –

+0

AH!我的錯誤是沒有考慮到複製和粘貼。到2.3秒,謝謝! – mlhDev