已经存在一个必须先关闭的打开的DataReader。

4
在我的映射逻辑层(从Model到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);
    }
}

Stack Trace

[InvalidOperationException: 已经有一个与此命令关联的打开的DataReader,必须先关闭它。] System.Data.SqlClient.SqlInternalConnectionTds.ValidateConnectionForExecute(SqlCommand command) +5287423 System.Data.SqlClient.SqlConnection.ValidateConnectionForExecute(String method, SqlCommand command) +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 completion, Int32 timeout, Task& task, Boolean 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 behavior) +41 System.Data.Common.DbCommand.ExecuteReader(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.Mappers.MedicalProductMapper.GetMedicalProductViewModel(MedicalProduct source) in 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 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, 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() +155 System.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 tag) +56
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中获取的品牌和一个产品来构建你的模型

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

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(该ViewModel的名称本身应该是复数形式)。
这真的可以有很大的影响-如果有10个品牌,那么如果您的页面大小为50,则会有500个键值对,这可能接近产品索引实际所需数据的10倍。如果有100个品牌...你已经明白了。
如果您没有在ProductIndex中使用BrandName,也可以将其省略,并使其更加高效,因为查询的这部分也可以跳过。
另外,我通常只给我的视图模型构造函数参数,并从那里填充,而不是使用GetMedicalProductViewModel。
最后,像品牌查找这样的东西也可以使用Ajax调用按需填充,这通常也更加高效,因为它可以在用户开始使用页面时加载,并且可以在输入品牌名称时进行异步搜索等。

楼主已经说过他知道这个解决方案,想要另外一种解决方法。 - Ilessa
我想我错过了那个。无论如何,既然我在这里,根据情况可应用以下替代解决方案:
  • 使用多个连接(最安全的线程)
  • 使用AsNoTracking
  • 转换为列表并关闭连接
- Arwin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接