4

在我的映射逻辑层(模型到 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(String 方法,SqlCommand 命令) +20
System.Data.SqlClient.SqlCommand.ValidateCommand(String method, Boolean async)
+155 System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, TaskCompletionSource`1 完成, Int32 超时, 任务&任务, 布尔 asyncWrite) +82
System.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 行为)+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 behavior) +507
System.Data.Objects.Internal.ObjectQueryExecutionPlan.Execute(ObjectContext context, ObjectParameterCollection parameterValues) +730
System.Data。 Objects.ObjectQuery 1.GetResults(Nullable1 forMergeOption)+131
System.Data.Objects.ObjectQuery 1.System.Collections.Generic.IEnumerable<T>.GetEnumerator() +36 System.Linq.Enumerable.Single(IEnumerable1 源)+179 System.Data.Objects.ELinq.ObjectQueryProvider.b_3 (IEnumerable 1 查询,表达式 queryRoot)+59 System.Data.Objects.ELinq。 ObjectQueryProvider.System.Linq.IQueryProvider.Execute(表达式表达式)+1331 sequence) +41
System.Data.Objects.ELinq.ObjectQueryProvider.ExecuteSingle(IEnumerable


System.Data.Entity.Internal.Linq.DbQueryProvider.Execute(表达式表达式)+123 System.Linq.Queryable.Single(IQueryable 1 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 product) 在 c:\Users\Matt\Documents\Visual Studio 2012\ Projects\MvcMedicalStore\MvcMedicalStore\Controllers\HomeController.cs:28 System.Linq.WhereSelectEnumerableIterator 1 续) +388 System.Web.Mvc.<>c2.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

_DisplayClass1c.b_19() +72 System.Web.Mvc.ControllerActionInvoker.InvokeActionResultWithFilters(ControllerContext controllerContext, IList 1.End() +136 System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, 对象标签) +56 System.Web. Mvc.Async.AsyncControllerActionInvoker.EndInvokeAction(IAsyncResult asyncResult) +40 System.Web.Mvc.<>c1 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


_DisplayClass1d.b_ 18(IAsyncResult asyncResult) +40
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, 对象标签) +591.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


System.Web.Mvc.Async.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.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

4

2 回答 2

5

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

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 步中的品牌构建模型
于 2013-09-06T14:26:39.847 回答
3

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

可以解决您的问题并且也是从您不打算编辑的上下文中获取数据的一个非常好的做法是明确告诉 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 调用按需填充,这通常也更有效,因为它可以在页面已经存在供用户开始使用时加载,可以在输入时使用异步搜索品牌名称等

于 2014-03-23T22:51:34.963 回答