从存储过程结果

时间:2017-01-19 14:58:45

标签: c# entity-framework

我使用与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();
}

1 个答案:

答案 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()) { }
    }
}

StoredProcedureTranslatorStoredProcedureResult可以修改如下(我发现设计有点瑕疵,但这不在问题范围内)。

结果:

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