我使用与this post中描述的方法类似的方法,使用Entity Framework将多个存储过程结果集映射到父实体和子实体。但是,我所看到的这种方法的每个实现都将后续结果集的结果(在第一个之后)直接分配给父实体的属性;我想以通用的方式实现这一目标。
到目前为止,我已经能够构建一个成功地将结果映射为一对多关系的包装器。在这种情况下,我不需要关心强制执行外键关系,因为存储过程会处理它,但是当选择多个父实体时,我需要以某种方式强制执行关系。
如何利用Entity Framework中的外键关系(可能使用反射)来强制执行映射?
这就是我所拥有的:
实体
class FooEntity
{
[Key]
int Id { get; set;}
ICollection<BarEntity> Bars { get; set; }
}
class BarEntity
{
[Key]
int Id { get; set;}
int FooId { get; set; }
[ForeignKey("FooId")]
virtual FooEntity Foo { get; set; }
}
通用存储过程转换器:
public class StoredProcedureTranslator<TDbContext> : IDisposable where TDbContext : DbContext, new()
{
private DbCommand command;
public StoredProcedureTranslator()
{
this.Context = new TDbContext();
}
public DbContext Context { get; set; }
public DbDataReader Reader { get; set; }
public StoredProcedureResult<T, TDbContext> Translate<T>(string procedureName, SqlParameter[] parameters) where T : class
{
this.command = this.Context.Database.Connection.CreateCommand();
this.command.CommandText = procedureName;
if (parameters != null)
{
this.command.Parameters.AddRange(parameters);
}
this.command.CommandType = CommandType.StoredProcedure;
this.Context.Database.Connection.Open();
this.Reader = this.command.ExecuteReader();
ObjectResult<T> results = ((IObjectContextAdapter)this.Context).ObjectContext.Translate<T>(this.Reader);
return new StoredProcedureResult<T, TDbContext>(results.ToList(), this);
}
public void Dispose()
{
this.Context.Dispose();
}
}
通用存储过程结果:
public class StoredProcedureResult<T, TDbContext> : IEnumerable<T>
where T : class where TDbContext : DbContext, new()
{
private readonly IEnumerable<T> results;
private readonly StoredProcedureTranslator<TDbContext> translator;
public StoredProcedureResult(IEnumerable<T> results, StoredProcedureTranslator<TDbContext> translator)
{
this.results = results;
this.translator = translator;
}
public StoredProcedureResult<T, TDbContext> Include<TChild>(Expression<Func<T, IEnumerable<TChild>>> member) where TChild : class
{
T result = this.results.FirstOrDefault();
if (result == null)
{
return null;
}
if (!this.translator.Reader.NextResult())
{
return new StoredProcedureResult<T, TDbContext>(new List<T> { result }, this.translator);
}
// TODO: do some stuff with the fks
ObjectResult<TChild> childResults = ((IObjectContextAdapter)this.translator.Context).ObjectContext.Translate<TChild>(this.translator.Reader);
var prop = member.Body as MemberExpression;
if (prop == null)
{
throw new Exception("blah blah");
}
var property = prop.Member as PropertyInfo;
if (property == null)
{
throw new Exception("blah blah");
}
property.SetValue(result, childResults.ToList(), null);
return new StoredProcedureResult<T, TDbContext>(new List<T> { result }, this.translator);
}
#region IEnumerable Impl
public IEnumerator GetEnumerator()
{
return this.results.GetEnumerator();
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return (IEnumerator<T>)this.GetEnumerator();
}
#endregion
}
用法
using (var spt = new StoredProcedureTranslator<DbContext>())
{
FooEntity foo = spt.Translate<FooEntity>("[dbo].[foo_get]", null).Include(f => f.Bars).FirstOrDefault();
}
答案 0 :(得分:2)
如果这只是用于实体类型,您可以让EF上下文跟踪基础架构和关系修复为您完成工作(非常匹配,如EF实体化实体查询)。 / p>
诀窍是使用以下Translate
方法重载
public virtual ObjectResult<TEntity> Translate<TEntity>(DbDataReader reader, string entitySetName, MergeOption mergeOption)
并传递实体类型的entitySetName
。
这是一个小帮助器实用程序类,它可以执行此操作(以及其他一些有用的方法):
public static class EntityUtils
{
public static string GetEntitySetName<T>(this IObjectContextAdapter dbContext) where T : class
{
return dbContext.ObjectContext.CreateObjectSet<T>().EntitySet.Name;
}
public static ObjectResult<T> ReadSingleResult<T>(this IObjectContextAdapter dbContext, DbDataReader dbReader)
where T : class
{
return dbContext.ObjectContext.Translate<T>(dbReader, dbContext.GetEntitySetName<T>(), MergeOption.AppendOnly);
}
public static void Load<T>(this ObjectResult<T> source) where T : class
{
// Consume the enumerable by iterating it
using (var en = source.GetEnumerator())
while (en.MoveNext()) { }
}
}
StoredProcedureTranslator
和StoredProcedureResult
可以修改如下(我发现设计有点瑕疵,但这不在问题范围内)。
结果:
public class StoredProcedureResult<T> : IEnumerable<T>
where T : class
{
private readonly DbContext context;
private readonly DbDataReader reader;
private readonly IEnumerable<T> results;
public StoredProcedureResult(DbContext context, DbDataReader reader)
{
this.context = context;
this.reader = reader;
this.results = this.context.ReadSingleResult<T>(this.reader).ToList();
}
public StoredProcedureResult<T> Include<TChild>() where TChild : class
{
if (this.reader.NextResult())
this.context.ReadSingleResult<TChild>(this.reader).Load();
return this;
}
public IEnumerator<T> GetEnumerator()
{
return this.results.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
请注意,它不再需要TDbContext
泛型参数,它所做的就是保持根结果集,同时提供流畅的界面来读取下一个结果。结果只是被翻译和消费,不需要存储,因为在消费部分EF将它们绑定到已经加载的相关实体。
StoredProcedureTranslator
类中的translate方法:
public StoredProcedureResult<T> Translate<T>(string procedureName, SqlParameter[] parameters) where T : class
{
this.command = this.Context.Database.Connection.CreateCommand();
this.command.CommandText = procedureName;
if (parameters != null)
this.command.Parameters.AddRange(parameters);
this.command.CommandType = CommandType.StoredProcedure;
this.Context.Database.Connection.Open();
this.Reader = this.command.ExecuteReader();
return new StoredProcedureResult<T>(this.Context, this.Reader);
}
用法:
using (var spt = new StoredProcedureTranslator<DbContext>())
{
var foo = spt.Translate<FooEntity>("[dbo].[foo_get]", null)
.Include<BarEntity>()
.FirstOrDefault();
}