Reputation: 1870
I wrote a simple wrapper for my DbContext
in order to add global exception handling on data queries without having to duplicate the handling on every query. In doing so, I ran into an issues that I can't seem to wrap my head around why the query works loading directly from the DbContext
but not the wrapper. The wrapper throws an System.Data.Entity.Core.EntityCommandExecutionException
with the inner exception There is already an open DataReader associated with this Command which must be closed first.
. The thing I really don't understand is when I step through the code slowly, it works. I mimicked the way the wrapper would load the query, but I'm seeing different results. I'm sure I'm missing something very simple here, but I've been looking at this problem too long and am stuck trying the same things over and over.
I wrote out a Simple Self-Contained Example Program in order to demonstrate:
public class DataReaderTest
{
public void Begin()
{
Database.SetInitializer<Context>(new DropCreateDatabaseAlways<Context>());
// create some objects in the database
using (Context context = new Context())
{
for (int i = 0; i < 3; i++)
{
Category c = new Category();
c.Boards = new List<Board>();
for (int x = 0; x < 3; x++)
{
Board b = new Board();
b.Threads = new List<Thread>();
c.Boards.Add(b);
for (int y = 0; y < 3; y++)
{
Thread t = new Thread();
t.Posts = new List<Post>();
b.Threads.Add(t);
for (int z = 0; z < 3; z++)
{
Post p = new Post();
t.Posts.Add(p);
}
}
}
context.Categorys.Add(c);
}
context.SaveChanges();
}
// use the context to query - works
using (Context context = new Context())
{
// get all categories in the same fasion the helper does
var categories = context.Set<Category>().Where(c => true).ToList();
foreach (Category c in categories)
{
// from the categories, loop through the boards to get the earliest post associated
foreach (Board b in c.Boards)
{
// get the earliest post from any thread in the board in the same fasion the helper does
var post = context.Set<Post>().Where(p => p.Thread.BoardID == b.PrimaryKey).OrderBy(p => p.PrimaryKey).First();
Console.WriteLine(string.Format("Earliest post for Board {0}: {1}", b.PrimaryKey, post.PrimaryKey));
}
}
}
// use the ContextHelper to query - doesn't work
using (ContextHelper helper = new ContextHelper())
{
// get all categories
var categories = helper.Where<Category>(c => true).ToList();
foreach (Category c in categories)
{
// from the categories, loop through the boards to get the earliest post associated
foreach (Board b in c.Boards)
{
// get the earliest post from any thread in the board
// this is where the error happens
var post = helper.Where<Post>(p => p.Thread.BoardID == b.PrimaryKey).OrderBy(p => p.PrimaryKey).First();
Console.WriteLine(string.Format("Earliest post for Board {0}: {1}", b.PrimaryKey, post.PrimaryKey));
}
}
}
}
}
// A simple wrapper around the DbContext
public class ContextHelper : IDisposable
{
private Context _Context;
public ContextHelper()
{
_Context = new Context();
}
public IEnumerable<T> Where<T>(Func<T, bool> predicate) where T : class
{
return _Context.Set<T>().Where(predicate);
}
public void Dispose()
{
_Context.Dispose();
_Context = null;
}
}
public class Context : DbContext
{
public Context()
: base("DefaultConnection") { }
public DbSet<Category> Categorys { get; set; }
public DbSet<Board> Boards { get; set; }
public DbSet<Thread> Threads { get; set; }
public DbSet<Post> Posts { get; set; }
}
[Table("Category")]
public class Category
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int PrimaryKey { get; set; }
public virtual ICollection<Board> Boards { get; set; }
}
[Table("Board")]
public class Board
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int PrimaryKey { get; set; }
[Required, ForeignKey("Category")]
public int CategoryID { get; set; }
public virtual Category Category { get; set; }
public virtual ICollection<Thread> Threads { get; set; }
}
[Table("Thread")]
public class Thread
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int PrimaryKey { get; set; }
[Required, ForeignKey("Board")]
public int BoardID { get; set; }
public virtual Board Board { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
[Table("Post")]
public class Post
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int PrimaryKey { get; set; }
[Required, ForeignKey("Thread")]
public int ThreadID { get; set; }
public virtual Thread Thread { get; set; }
}
Upvotes: 1
Views: 595
Reputation: 291
try changing your wrapper where clause to be the same as what is happening in the DbContext: like this.
public IQueryable<T> Where<T>(Expression<Func<T, bool>> predicate) where T : class
{
return _Context.Set<T>().AsQueryable().Where(predicate);
}
Where you are actually returning IQueryable and accepting an expression as an argument. I've just tested this and it seems to work.
Upvotes: 3