【问题标题】:There is Already an open DataReader that must be closed first已经有一个打开的 DataReader 必须先关闭
【发布时间】:2013-09-10 16:32:40
【问题描述】:

在我的映射逻辑层(模型到 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 命令)+5287423
System.Data.SqlClient.SqlConnection.ValidateConnectionForExecute(字符串 方法,SqlCommand 命令)+20
System.Data.SqlClient.SqlCommand.ValidateCommand(字符串方法, 布尔异步)+155
System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior、RunBehavior、runBehavior、布尔返回流、字符串 方法,TaskCompletionSource`1 完成,Int32 超时,Task& 任务, 布尔异步写入)+82
System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior、RunBehavior、runBehavior、布尔返回流、字符串 方法)+53
System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior 行为,字符串方法)+134
System.Data.SqlClient.SqlCommand.ExecuteDbDataReader(CommandBehavior 行为)+41
System.Data.Common.DbCommand.ExecuteReader(CommandBehavior 行为) +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 上下文,ObjectParameterCollection 参数值)+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 来源)+179 System.Data.Objects.ELinq.ObjectQueryProvider.b_3(IEnumerable1 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(IQueryable1 source, Expression1 谓词)+287
MvcMedicalStore.Mappers.MedicalProductMapper.GetMedicalProductViewModel(MedicalProduct 源)在 c:\Users\Matt\Documents\Visual Studio 2012\Projects\MvcMedicalStore\MvcMedicalStore\Mappers\MedicalProductMapper.cs:28 MvcMedicalStore.Controllers.c
_DisplayClass1.b_0(MedicalProduct 产品)在 c:\Users\Matt\Documents\Visual Studio 2012\Projects\MvcMedicalStore\MvcMedicalStore\Controllers\HomeController.cs:28 System.Linq.WhereSelectEnumerableIterator2.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.c
_DisplayClass1c.b_19() +72 System.Web.Mvc.ControllerActionInvoker.InvokeActionResultWithFilters(ControllerContext 控制器上下文,IList1 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, 对象标签) +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.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.c
_DisplayClass8.b_3(IAsyncResult asyncResult) +45
System.Web.Mvc.Async.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& completedSynchronously) +155

【问题讨论】:

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


    【解决方案1】:

    您在您的选择中为您的每个产品提出了另一个请求。但是您的产品已被枚举,因此第一个数据读取器未关闭。这就是您打开多个数据读取器的原因。

    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. 使用一种产品和第 2 步中的品牌构建模型

    【讨论】:

    • 我理解你所说的一切,直到你描述我应该采取哪些步骤来避免不必要的往返,我理解你所说的概念。但我不知道我应该在哪里加载产品和品牌。映射逻辑中的模型创建是否存在问题?
    • @ArmorCode :您应该在函数 GetMedicalProductViewModel 中将品牌作为参数传递
    【解决方案2】:

    编辑:由于您已经知道多个结果集标志,正如 cmets 中所指出的那样,我想我会将这个答案更改为更有用的答案。

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

    这很容易做到:只需使用“AsNoTracking()”。您基本上只需要:

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

    现在您可以使用此列表将其设置为对您的产品视图模型的查找,并且您还可以使用它来获取该特定产品的视图模型的品牌名称。只需使用以下品牌列表扩展您的 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);
    

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

    这确实可以产生很大的不同 - 如果有 10 个品牌,那么如果页面大小为 50,那么这就是 500 个键值对,这很可能接近产品索引实际需要的数据量的 10 倍。如果有 100 个品牌……你就明白了。

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

    另外,我通常只提供我的 viewmodel 构造函数参数并从那里填充,而不是 GetMedicalProductViewModel。

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

    【讨论】:

    • OP 已经说过他知道这个修复并想要一个替代解决方案。
    • 我想我错过了。无论如何,当我在这里时,根据我所了解的情况,适用的替代解决方案是: - 使用多个连接(大多数线程安全) - 使用 AsNoTracking - 强制列出并关闭连接
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-17
    • 2011-08-29
    • 2020-09-25
    • 2011-12-26
    • 1970-01-01
    相关资源
    最近更新 更多