动态翻译以避免 C# 语法错误

Dynamic Translate to avoid C# syntax errors

考虑以下数据库 table (SQL Server 2005)。我想在 EF(v6,.net 4.5.1)中使用它的翻译功能,但搜索后似乎不支持它。

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

使用约定映射会创建 class 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 });
}

来自multiple things I've read, the ColumnAttribute mapping is not supported. From 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.

问题是,我在映射中看不到 specify/hint 的任何替代方法或方法。我可以更改 class 名称,但这不如 属性 名称理想。

任何解决方法, 或任何其他无需使用 Translate 即可动态加载数据的方法?

有点棘手,但可行。

想法是通过实施和使用执行所需映射的自定义 DbDataReader 来利用 Translate 方法。

在这样做之前,让我们实现一个通用的 DbDataReader class,它只委托给底层 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; } }
}

没什么特别的 - 烦人地覆盖所有 abstract/meaningful 虚拟成员并委托给基础对象。

现在执行名称映射的 reader:

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();
}

希望对您有所帮助。