考虑以下数据库表(SQL Server 2005)。我想在EF(v6,.net 4.5.1)中使用Translate功能,但搜索后似乎不支持。
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 });
}
从multiple things我已阅读,不支持ColumnAttribute映射。来自MSDN:
当使用Translate方法创建实体时,EF不会考虑任何映射。它只是将结果集中的列名与类上的属性名匹配。
果然,我得到了错误:
数据阅读器与指定的' Namespace.Foo'不兼容。该类型的成员名称'在数据阅读器中没有相应的列具有相同的名称。
问题是,我没有看到任何替代或方式来指定/提示映射。我可以更改类名,但这比属性名称更不可取。
任何解决方法或以其他方式动态加载数据而不使用Translate
?
答案 0 :(得分: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();
}
希望有所帮助。