首页 文章

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

提问于
浏览
3

在我的映射逻辑层(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;
    }
}

我知道通过在连接字符串中启用 Multiple Active Result Sets (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 command)5287423 System.Data.SqlClient.SqlConnection.ValidateConnectionForExecute(String method,SqlCommand command )System.Data.SqlClient.SqlCommand.ValidateCommand(String method,Boolean async)155 System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior,RunBehavior runBehavior,Boolean returnStream,String方法,TaskCompletionSource1完成,Int32超时,任务& task,Data.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior,RunBehavior runBehavior,Boolean returnStream,String method)53 System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior,String method)134 System.Data . SqlClient.SqlCommand.ExecuteDbDataReader(CommandBehavior behavior)41 System.Data.Common.DbCommand.Execut eReader(CommandBehavior behavior)10 System.Data.EntityClient.EntityCommandDefinition.ExecuteStoreCommands(EntityCommand entityCommand,CommandBehavior behavior)437 [EntityCommandExecutionException:执行命令定义时发生错误 . 有关详细信息,请参阅内部异常 . ] System.Data.EntityClient.EntityCommandDefinition.ExecuteStoreCommands(EntityCommand entityCommand,CommandBehavior behavior)507 System.Data.Objects.Internal.ObjectQueryExecutionPlan.Execute(ObjectContext context,ObjectParameterCollection parameterValues)730 System.Data.Objects . ObjectQuery1.GetResults(Nullable1 forMergeOption)131 System.Data.Objects.ObjectQuery1.System.Collections.Generic.IEnumerable <T> .GetEnumerator()36 System.Linq.Enumerable.Single(IEnumerable1 source)179 System.Data.Objects.ELinq .ObjectQueryProvider.b_3(IEnumerable1 sequence)41 System.Data.Objects.ELinq.ObjectQueryProvider.ExecuteSingle(IEnumerable1 query,Expression queryRoot)59 System.Data.Objects.ELinq.ObjectQueryProvider.System.Linq.IQueryProvider.Execute(Expression expression)133 System.Data.Entity.Internal.Linq.DbQueryProvider.Execute(Expression expression)123 System.Linq.Queryable.Single(IQueryable1 source,Expression1 predicate)287 MvcMedicalStore.Map pers.MedicalProductMapper.GetMedicalProductViewModel(MedicalProduct source)位于c:\ Users \ Matt \ Documents \ Visual Studio 2012 \ Projects \ MvcMedicalStore \ MvcMedicalStore \ Mappers \ MedicalProductMapper.cs:28 MvcMedicalStore.Controllers . <> c_DisplayClass1.b_0(MedicalProduct product)in c:\ Users \ Matt \ Documents \ Visual Studio 2012 \ Projects \ MvcMedicalStore \ MvcMedicalStore \ Controllers \ HomeController.cs:28 System.Linq.WhereSelectEnumerableIterator2.MoveNext()145 c:\ Users \ Matt中的ASP._Page_Views_Home_Index_cshtml.Execute() \ 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(争夺wContext 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,Func1 continuation)388 System.Web.Mvc . <> c_DisplayClass1c.b_19()72 System.Web.Mvc.ControllerActionInvoker.InvokeActionResultWithFilters(ControllerContext controllerContext,IList1 filters,ActionResult actionResult)303 System.Web.Mvc.Async . <> c__DisplayClass2a . <BeginInvokeAction> b__20()155System.Web.Mvc.Async . <> c__DisplayClass25 . <BeginInvokeAction> b__22(IAsyncResult asyncResult)184 System.Web.Mvc.Async.WrappedAsyncResult1.End()136 System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object标记)56 System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeAction(IAsyncResult asyncResult)40 System.Web.Mvc . <> c_DisplayClass1d.b_18(IAsyncResult asyncResult)40 System.Web.Mvc.Async . <> c_DisplayClass4.b_3( IAsyncResult ar)47 System.Web.Mvc.Async.WrappedAsyncResult1.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.WrappedAsyncResult1 .End()151 System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult,Object tag)59 System.Web.Mvc.Asyn c.AsyncResultWrapper.End(IAsyncResult asyncResult,Object tag)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 . <> c_DisplayClass8.b_3(IAsyncResult asyncResult)45 System.Web.Mvc.Async . <> c_DisplayClass4.b__3(IAsyncResult ar)47 System.Web.Mvc.Async.WrappedAsyncResult1.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.MvcHandler.EndProcessRequest(IAsyncResult) asyncResult)40 System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result)38 System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()9628700 System.Web.HttpApplication.ExecuteStep(IExecutionStep step,Boolean&completedSynchronously)155

2 回答

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

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

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

    • 加载您的产品

    • 加载您的品牌

    • 使用一个产品和从步骤2中获取的品牌构建模型

  • 2

    编辑:正如你已经知道多个结果集标志,正如评论中指出的那样,我想我会把这个答案改成一个更有用的答案 .

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

    这很容易做到:只需使用'AsNoTracking()' . 你基本上需要的是:

    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,您也可以省略它,并使其更高效,因为也可以跳过查询的这一部分 .

    此外,我通常只给出我的viewmodel构造函数参数并从那里填充,而不是GetMedicalProductViewModel .

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

相关问题