已经有一个必须先关闭的开放DataReader

时间:2013-09-06 14:23:41

标签: c# asp.net asp.net-mvc linq asp.net-mvc-4

在我的映射逻辑层(Model to ViewModel)中,我尝试在编辑视图中填充SelectListItem以与HTML.DropDownListFor帮助器一起使用。

我尝试使用以下代码示例中的查询来检索品牌名称列表以填充SelectListItem,但触发了以下异常:

  

已经有一个与此命令关联的开放DataReader   必须先关闭。

映射

public class MedicalProductMapper
{
    private MvcMedicalStoreDb _db; // DataContext class

    public MedicalProductMapper(MvcMedicalStoreDb db)
    {
        _db = db;
    }    
    public MedicalProductViewModel GetMedicalProductViewModel(MedicalProduct source)
    {
        MedicalProductViewModel viewModel = new MedicalProductViewModel();

        viewModel.ID = source.ID; 
        viewModel.Name = source.Name;
        viewModel.Price = source.Price;
        viewModel.BrandID = source.BrandID;

        // This following line produces the exception
        viewModel.BrandName = _db.Brands.Single(b => b.ID == source.BrandID).Name;

        var queryBrands = from b in _db.Brands
                          select b;

        viewModel.BrandSelectListItem = queryBrands as IEnumerable<SelectListItem>;

        return viewModel;
    }
}

我知道通过在连接字符串中启用多个活动结果集(MARS),可以轻松解决问题,但我想知道是否有办法在不修改连接字符串的情况下做我想做的事。

以下是一些课程,以防它们有助于解决这个问题:

编辑视图

@model MvcMedicalStore.Models.MedicalProductViewModel

@{
    ViewBag.Title = "Edit";
}

<h2>Edit</h2>

@using (Html.BeginForm()) {
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>MedicalProduct</legend>

        @Html.HiddenFor(model => model.ID)

        <div class="editor-label">
            @Html.LabelFor(model => model.Name)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Name)
            @Html.ValidationMessageFor(model => model.Name)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Price)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Price)
            @Html.ValidationMessageFor(model => model.Price)
        </div>

        // BRAND NAME
        <div class="editor-label">
            @Html.LabelFor(model => model.BrandName)
        </div>
        <div class="editor-field">
            @Html.DropDownListFor(model => model.BrandName, Model.BrandSelectListItem)
            @Html.ValidationMessageFor(model => model.BrandName)
        </div>

        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

控制器:

public class MedicalProductController : Controller
{
    private MvcMedicalStoreDb _db = new MvcMedicalStoreDb();

    //
    // GET: /MedicalSupply/

    public ActionResult Index()
    {
        var viewModel = _db.Products.AsEnumerable()
            .Select(product => GetMedicalProductViewModel(product));
        return View(viewModel);
    }

    public MedicalProductViewModel GetMedicalProductViewModel(MedicalProduct product)
    {
        var mapper = new MedicalProductMapper(_db);

        return mapper.GetMedicalProductViewModel(product);            
    }
    public MedicalProduct GetMedicalProduct(MedicalProductViewModel viewModel)
    {
        var mapper = new MedicalProductMapper(_db);

        return mapper.GetMedicalProduct(viewModel);
    }

    //
    // GET: /MedicalSupply/Edit/5

    public ActionResult Edit(int id = 0)
    {
        MedicalProduct medicalProduct = _db.Products.Find(id);
        if (medicalProduct == null)
        {
            return HttpNotFound();
        }

        var viewModel = GetMedicalProductViewModel(medicalProduct);
        return View(viewModel);
    }

    //
    // POST: /MedicalSupply/Edit/5

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit(MedicalProduct medicalProduct)
    {
        if (ModelState.IsValid)
        {
            _db.Entry(medicalProduct).State = EntityState.Modified;
            _db.SaveChanges();
            return RedirectToAction("Index");
        }

        var viewModel = GetMedicalProductViewModel(medicalProduct);
        return View(viewModel);
    }
}

堆栈跟踪

  

[InvalidOperationException:已经有一个打开的DataReader   与此命令关联,必须先关闭。]
  System.Data.SqlClient.SqlInternalConnectionTds.ValidateConnectionForExecute(SqlCommand的   命令)+5287423
  System.Data.SqlClient.SqlConnection.ValidateConnectionForExecute(字符串   方法,SqlCommand命令)+20
  System.Data.SqlClient.SqlCommand.ValidateCommand(String方法,   布尔异步)+155
  System.Data.SqlClient.SqlCommand.RunExecuteReader(的CommandBehavior   cmdBehavior,RunBehavior runBehavior,Boolean returnStream,String   方法,TaskCompletionSource`1完成,Int32超时,任务&amp;任务,   Boolean asyncWrite)+82
  System.Data.SqlClient.SqlCommand.RunExecuteReader(的CommandBehavior   cmdBehavior,RunBehavior runBehavior,Boolean returnStream,String   方法)+53
  System.Data.SqlClient.SqlCommand.ExecuteReader(的CommandBehavior   行为,字符串方法)+134
  System.Data.SqlClient.SqlCommand.ExecuteDbDataReader(的CommandBehavior   行为)+41
  System.Data.Common.DbCommand.ExecuteReader(CommandBehavior behavior)   +10 System.Data.EntityClient.EntityCommandDefinition.ExecuteStoreCommands(EntityCommand)   entityCommand,CommandBehavior行为)+437

     

[EntityCommandExecutionException:执行时发生错误   命令定义。有关详细信息,请参阅内部异常。]
  System.Data.EntityClient.EntityCommandDefinition.ExecuteStoreCommands(EntityCommand   entityCommand,CommandBehavior行为)+507
  System.Data.Objects.Internal.ObjectQueryExecutionPlan.Execute(ObjectContext的   context,ObjectParameterCollection parameterValues)+730
  System.Data.Objects.ObjectQuery 1.GetResults(Nullable 1   forMergeOption)+131
  System.Data.Objects.ObjectQuery 1.System.Collections.Generic.IEnumerable<T>.GetEnumerator() +36 System.Linq.Enumerable.Single(IEnumerable 1 source)+179 System.Data.Objects.ELinq.ObjectQueryProvider.b_ 3(IEnumerable 1 sequence) +41
System.Data.Objects.ELinq.ObjectQueryProvider.ExecuteSingle(IEnumerable
1   查询,表达式queryRoot)+59
  System.Data.Objects.ELinq.ObjectQueryProvider.System.Linq.IQueryProvider.Execute(表达式   表达式)+133
  System.Data.Entity.Internal.Linq.DbQueryProvider.Execute(表达式   表达式)+123 System.Linq.Queryable.Single(IQueryable 1 source, Expression 1谓词)+287
  MvcMedicalStore.Mappers.MedicalProductMapper.GetMedicalProductViewModel(MedicalProduct   源代码)在c:\ Users \ Matt \ Documents \ Visual Studio中   2012 \项目\ MvcMedicalStore \ MvcMedicalStore \映射器\ MedicalProductMapper.cs:28   MvcMedicalStore.Controllers&LT;。&以及c
_DisplayClass1.b_ 0(MedicalProduct   产品)在c:\ Users \ Matt \ Documents \ Visual Studio中   2012 \项目\ MvcMedicalStore \ MvcMedicalStore \ \控制器HomeController.cs:28   System.Linq.WhereSelectEnumerableIterator 2.MoveNext() +145
ASP._Page_Views_Home_Index_cshtml.Execute() in c:\Users\Matt\Documents\Visual Studio 2012\Projects\MvcMedicalStore\MvcMedicalStore\Views\Home\Index.cshtml:25 System.Web.WebPages.WebPageBase.ExecutePageHierarchy() +197
System.Web.Mvc.WebViewPage.ExecutePageHierarchy() +119
System.Web.WebPages.StartPage.RunPage() +17
System.Web.WebPages.StartPage.ExecutePageHierarchy() +62
System.Web.WebPages.WebPageBase.ExecutePageHierarchy(WebPageContext pageContext, TextWriter writer, WebPageRenderingBase startPage) +76
System.Web.Mvc.RazorView.RenderView(ViewContext viewContext, TextWriter writer, Object instance) +743
System.Web.Mvc.BuildManagerCompiledView.Render(ViewContext viewContext, TextWriter writer) +382
System.Web.Mvc.ViewResultBase.ExecuteResult(ControllerContext context) +431 System.Web.Mvc.ControllerActionInvoker.InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult) +39
System.Web.Mvc.<>c__DisplayClass1a.<InvokeActionResultWithFilters>b__17() +74 System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func
1续)+388
  System.Web.Mvc&LT;。&以及c
_DisplayClass1c.b_ 19()   +72 System.Web.Mvc.ControllerActionInvoker.InvokeActionResultWithFilters(ControllerContext   controllerContext,IList 1 filters, ActionResult actionResult) +303
System.Web.Mvc.Async.<>c__DisplayClass2a.<BeginInvokeAction>b__20() +155 System.Web.Mvc.Async.<>c__DisplayClass25.<BeginInvokeAction>b__22(IAsyncResult asyncResult) +184 System.Web.Mvc.Async.WrappedAsyncResult
1.End()   +136 System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult,Object tag)+56
  System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeAction(IAsyncResult的   asyncResult)+40
  System.Web.Mvc&LT;。&以及c
_DisplayClass1d.b_ 18(IAsyncResult的   asyncResult)+40
  System.Web.Mvc.Async&LT;。&以及c
_DisplayClass4.b_ 3(IAsyncResult的   ar)+47 System.Web.Mvc.Async.WrappedAsyncResult 1.End() +151
System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +59
System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +40
System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult) +44 System.Web.Mvc.Async.<>c__DisplayClass4.<MakeVoidDelegate>b__3(IAsyncResult ar) +47 System.Web.Mvc.Async.WrappedAsyncResult
1.End()+151
  System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult,   对象标签)+59
  System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult,   对象标签)+40 System.Web.Mvc.Controller.EndExecute(IAsyncResult   asyncResult)+39
  System.Web.Mvc.Controller.System.Web.Mvc.Async.IAsyncController.EndExecute(IAsyncResult的   asyncResult)+39
  System.Web.Mvc&LT;。&以及c
_DisplayClass8.b_ 3(IAsyncResult的   asyncResult)+45
  System.Web.Mvc.Async&LT;。&以及c
_DisplayClass4.b__3(IAsyncResult的   ar)+47 System.Web.Mvc.Async.WrappedAsyncResult`1.End()+151
  System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult,   对象标签)+59
  System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult,   对象标签)+40
  System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult)   +40 System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult)   结果)+38
  System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()   +9628700 System.Web.HttpApplication.ExecuteStep(IExecutionStep step,Boolean&amp; completedSynchronously)+155

2 个答案:

答案 0 :(得分:5)

您在每个产品的选择中提出另一个请求。但是您的产品被枚举,因此第一个datareader没有关闭。这就是您打开多个数据加载器的原因。

public ActionResult Index()
{
    var products = _db.Products.ToArray() // force loading the results from database 
                                           // and close the datareader

    var viewModel = products.Select(product => GetMedicalProductViewModel(product));

    return View(viewModel);
}

附加:我认为您应该优化模型创建:您为数据库中的每个产品提出相同的请求(选择品牌)。

为避免不必要的多个数据库往返,您应该:

  1. 加载您的产品
  2. 加载您的品牌
  3. 使用一种产品和从第二步采购的品牌
  4. 构建模型

答案 1 :(得分:2)

编辑:正如您已经知道多个结果集标志,正如评论中指出的那样,我认为我将此答案更改为更有用的答案。

可以解决您的问题的东西,也是从您不打算编辑的上下文中获取数据的一个非常好的做法,是明确告诉EntityFramework不跟踪实体,在上下文中有效地呈现它们永远不会更新回数据库的只读对象。

这很容易做到:只需使用&#39; AsNoTracking()&#39;。你基本上只需要:

var brands = _db.Brands.AsNoTracking().ToList();

现在,您可以使用此列表将其设置为产品视图模型上的查找,您也可以使用它来获取该特定产品的viewmodel的brandName。只需使用以下品牌列表扩展您的GetMedicalProductViewModel:

GetMedicalProductViewModel(MedicalProduct source, IEnumerable<Brand> brands)

然后使用品牌而不是你的_db.Brands,你会很高兴:

var brands = _db.Brands.AsNoTracking().ToList();
var viewModel = _db.Products.AsNoTracking().Select(product => GetMedicalProductViewModel(product, brands));

return View(viewModel);

此外,请注意您使用相同的viewmodel进行编辑和列表。在这种情况下,您可以看到效率非常低,因为索引页面上的每个产品都有自己的品牌列表副本,这最终会成为索引视图中的大量额外数据,而您不会实际需要。因此,我强烈建议使用没有BrandSelectListItem的MedicalProductIndexViewModel(并且它本身可能应该复制到Items)。

这真的可以产生很大的不同 - 如果有10个品牌,那么如果您的页面大小为50,那么500个键值对,这可能接近产品数据的10倍指数真的需要。如果有100个品牌......你就能看到它。

如果您没有在ProductIndex中使用BrandName,您也可以省略它,并使其更高效,因为也可以跳过该部分查询。

此外,我没有使用GetMedicalProductViewModel,而是通常只提供我的viewmodel构造函数参数并从那里填充。

最后,认为像品牌查询也可以使用Ajax调用按需填充,这通常也更有效,因为它可以加载,而页面已经存在供用户开始使用,可以使用异步在键入品牌名称等时搜索