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

标签 c# sql-server entity-framework reflection objectcontext

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

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

这似乎可行,但我想通过存储过程和 MARS 使初始页面请求加载数据 block (并使用通用结构,以便我可以在其他页面上重用它),所以我调用了存储过程并循环遍历结果集,通过反射调用 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我读过,不支持 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 即可动态加载数据的方法?

最佳答案

有点棘手,但可行。

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

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

没什么特别的 - 烦人地覆盖所有抽象/有意义的虚拟成员并委托(delegate)给底层对象。

现在执行名称映射的读取器:

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

希望对您有所帮助。

关于c# - 动态翻译以避免 C# 语法错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39779270/

相关文章:

c# - 轮询的替代方案?

c# - 如何在 Visual Studio 2015 中禁用 C# 6 支持?

c# - 两种不同签名的一种方法

c# - EntityFramework 同表多对多关系

linq - Entity Framework 表之间的多重关系

c# - 如何在运行时更改 SolidColorBrush 资源的颜色?

javascript - JSP:插入不起作用 (SQLSERVER)

sql-server - 删除触发器时出错

sql - 如何按以下别名列分组

c# - 使用 EF 数据库优先应用程序部署 ASP.NET Web API