2016-09-29 39 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