动态翻译以避免C#语法错误 [英] Dynamic Translate to avoid C# syntax errors

查看:122
本文介绍了动态翻译以避免C#语法错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Consider the following database table (SQL Server 2005). I'd like to use this in EF (v6, .net 4.5.1) with the Translate function but after searching seems this is not supported.

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

使用约定俗成的映射将创建具有属性Foo的类Foo,而C#语法不支持该属性.我尝试使用ColumnAttribute:

Using by-convention mapping that would create a class Foo with a property Foo which is not supported by C# syntax. I tried using the ColumnAttribute:

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

这似乎可行,但是我想通过存储过程和MARS来使初始页面请求加载数据(并使用通用结构,以便可以在其他页面上重用),所以我将存储过程称为并遍历结果集,并通过反射调用ObjectContext.Translate(类似于下面的内容,但这是缩写):

This appears to work, but I'd like to make the initial page request load gobs of data via stored procedure and MARS (and use a generic structure so I can reuse it on other pages), so I called the stored procedure and looped through the result sets, calling ObjectContext.Translate via reflection (similar to the below, but this is abbreviated):

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

来自多个 事物我读过,不支持ColumnAttribute映射.来自 MSDN :

From multiple things I've read, the ColumnAttribute mapping is not supported. From MSDN:

EF在使用Translate方法创建实体时不考虑任何映射.它将只将结果集中的列名与类上的属性名匹配.

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.

当然,我知道并出错:

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

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.

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

The problem is, I do not see any alternative or way to specify/hint at the mapping. I could change the class name but that is less desirable than the property names.

是否有任何变通办法,或其他任何无需使用Translate动态加载数据的方式?

Any workarounds, or any other way to dynamically load data without using Translate?

推荐答案

有些棘手,但可行.

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

The idea is to utilize the Translate method by implementing and using a custom DbDataReader that performs the required mapping.

在此之前,让我们实现一个通用的DbDataReader类,该类只是委派给基础DbDataReader:

Before doing that, let implement a generic DbDataReader class that does just delegating to the underlying 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; } }
}

没有幻想-烦人地覆盖所有抽象/有意义的虚拟成员并委托给基础对象.

Nothing fancy - annoyingly overriding all abstract/meaningful virtual members and delegate to the underlying object.

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

Now the reader that performs name mapping:

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

再次,没有幻想.覆盖一些方法,并执行名称到列名的映射,反之亦然.

Again, nothing fancy. Override a few methods and perform a name to column name and vice versa mapping.

最后,一个帮助方法可以满足您的要求:

Finally, a helper method that does what you are asking:

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获取属性和列之间的映射.

ReadSingleResult is the helper method in question. The GetPropertyMappings method is using part of the code from EF6.1 Get Mapping Between Properties and Columns.

示例用法类似于提供的示例:

Sample usage similar to the provided example:

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#语法错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆