【问题标题】:Dynamic Translate to avoid C# syntax errors动态翻译以避免 C# 语法错误
【发布时间】:2016-09-29 20:18:45
【问题描述】:

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

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 在使用 Translate 方法创建实体时不考虑任何映射。它只会将结果集中的列名与类上的属性名匹配。

果然,我得到了错误:

数据读取器与指定的“Namespace.Foo”不兼容。类型的成员“名称”在数据读取器中没有同名的对应列。

问题是,我看不到任何替代方法或方法来指定/提示映射。我可以更改类名,但这不如属性名。

任何变通方法,或任何其他不使用Translate的动态加载数据的方法?

【问题讨论】:

  • 读取存储过程+动态数据结构=Dapper.

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


【解决方案1】:

有点棘手,但可行。

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

在此之前,让我们实现一个通用的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();
}

希望对您有所帮助。

【讨论】:

  • 这似乎可行,但 Translate 正在做的一件事是将 results 添加到 DbContext。看来这需要明确地这样做,当我将 while (enumarator.MoveNext()) { this.Entry(enumarator.Current).State = EntityState.Unchanged; }; 添加到 foreach 循环时,加载需要 30 多秒 :( 我会将此标记为解决方案,但也许我正在尝试解决错误的问题。
  • @Matthew 实际上,一个区别是我使用了更简单的Translate 重载。您可以从我的示例中获取自定义数据读取器,并在原始代码中使用 entitySet.Name, MergeOption.AppendOnly 的重载。
  • 啊!不假思索地复制粘贴是我的错。这将其缩短到 2.3 秒。谢谢!
  • 所以基本上,你不能使用 Translate 来读取实体,因为它实际上并没有翻译列名,如果你尝试加载一个 table-per-hierarchy 类型,它甚至会抛出异常,因为读取 Discriminator 列并实例化正确的子类型而不是抛出关于无法构造抽象基类型的实例的错误太愚蠢了。一文不值。无法使用 EF 从存储过程中读取实体。
猜你喜欢
  • 1970-01-01
  • 2022-07-22
  • 1970-01-01
  • 2015-09-16
  • 2012-03-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-23
  • 1970-01-01
相关资源
最近更新 更多