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

スタックトレース

[InvalidOperationException: このコマンドに関連付けられている開いている DataReader が既に存在します。これを最初に閉じる必要があり
ます
。 ) +20
System.Data.SqlClient.SqlCommand.ValidateCommand(String メソッド、Boolean async) +155
System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior、RunBehavior runBehavior、Boolean returnStream、String メソッド、TaskCompletionSource`1 完了、Int32 タイムアウト, Task& task, Boolean asyncWrite) +82
System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior、RunBehavior runBehavior、Boolean returnStream、String メソッド) +53
System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior 動作、String メソッド) +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ソース) in c:\Users\ Matt\Documents\Visual Studio 2012\Projects\MvcMedicalStore\MvcMedicalStore\Mappers\MedicalProductMapper.cs:28 MvcMedicalStore.Controllers.<>c
_DisplayClass1.b_ 0(MedicalProduct 製品) in 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、Object タグ) +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、Object タグ) +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 タグ) +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 システム.Web.Mvc.Async.WrappedAsyncResult`1.End() +151
System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult、Object タグ) +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. 1 つの製品とステップ 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 を使用していない場合は、それも省略して、クエリのその部分もスキップできるため、さらに効率的にすることができます。

また、GetMedicalProductViewModel を使用する代わりに、通常はビューモデル コンストラクターに引数を与え、そこから値を設定します。

そして最後に、ブランド ルックアップは Ajax 呼び出しを使用してオンデマンドで入力することもできると考えています。これは、ユーザーが作業を開始するためにページが既にそこにある間にロードでき、入力中に非同期検索で作業できるため、一般的により効率的です。ブランド名など

于 2014-03-23T22:51:34.963 に答える