2016-09-29 3 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 со свойством Foo, который не поддерживается на C# синтаксис. Я попытался с помощью ColumnAttribute:

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

Это, кажется, работает, но я хотел бы сделать запрос начальной страницы загрузки уймы данных с помощью хранимых процедур и MARS (и использовать общую структуру, так что я могу использовать его на другие страницы), так что я назвал хранимую процедуру и петельные через результирующие наборы, вызывая 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 метод помощник в вопросе. Метод 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

Кажется, что это работает, но одна вещь, которую делал Перевод, заключалась в добавлении 'результата' в 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