2016-04-19 4 views
0

データベースを使用してASP.NET MVCでプログラムを構築しています。それはあなたの台所のレシピと成分マッチングのためです。データベーステーブルからレシピを取得し、ジャンクションテーブルを介して関連する別のテーブルからレシピを取得します。 RecipeIDIngredientIDとして設定ユニーク制約付きASP.NET MVC 2つのアクションを同時にポストして2つのテーブルに複数のエントリを書き込む

RecipeTable 
ID PK int not null 
RecipeName varchar(25) not null 
CategoryID int FK(references Cateogory(ID) not null 
Directions varchar(max) not null 

Recipe_IngredientsTable 
RecipeID int FK(references Recipe(ID) not null 
IngredientID int FK(references Ingredient(ID) not null (Ingredient table is just IDs and names) 
IngredientAmount varchar(25) not null 

今、問題は、新しいレシピを作成することで、レシピエントリストのリストを同時に保存したいと考えています。 RecipeDMには、ID、RecipeName、CategoryID、List、およびDirectionsのフィールドが含まれています。それが今座っているとして、私のDALレベルで、私はレシピを書き込むため、この方法を持っている:

public void CreateRecipeIngredients(RecipeDM recipe) 
    { 
     using (SqlConnection connection = new SqlConnection(ConnectionString)) 
     { 
      // Building single SQL query statement to reduce trips to database for multiple RecipeIngredients 
      StringBuilder queryString = new StringBuilder(); 
      int rowsAffected = 0; 
      foreach (RecipeIngredientDM ingredient in recipe.RecipeIngredients) 
      { 
       queryString.AppendFormat("Insert into Recipe_Ingredients (RecipeID, IngredientID, IngredientAmount) Values ({0}, {1}, {2});", 
        recipe.RecipeID, 
        ingredient.IngredientID, 
        ingredient.IngredientAmount); 
      } 
      try 
      { 
       using (SqlCommand command = new SqlCommand(queryString.ToString(), connection)) 
       { 
        command.CommandType = CommandType.Text; 
        rowsAffected = command.ExecuteNonQuery(); 
       } 
       logger.LogError("Event", "User was able create a list of ingredients for a recipe.", "Class: RecipeIngredientDAO -- Method: CreateRecipeIngredients"); 
      } 
      catch (Exception e) 
      { 
       logger.LogError("Error", "User was unable to create a list of ingredients for a recipe, error: " + e, "Class: RecipeIngredientDAO -- Method: CreateRecipeIngredients"); 
      } 
      finally 
      { 
       if (rowsAffected != recipe.RecipeIngredients.Count()) 
       { 
        recipeData.DeleteRecipe(recipe); 
       } 
       logger.LogError("Error", "All RecipeIngredients did not make it into the table; rolling back recipe creation.", "Class: RecipeIngredientDAO -- Method: CreateRecipeIngredients"); 
       // If the number of RecipeIngredients inserted into the table does not equal the number of ingredients the recipe has, then roll back entire creation of recipe to prevent bad data 
      } 
     } 
    } 

レシピを記述するためのこの方法:

 public void CreateRecipe(RecipeDM recipe) 
    { 
     try 
     { 
      SqlParameter[] parameters = new SqlParameter[] 
      { 
       new SqlParameter("@RecipeName", recipe.RecipeName) 
       ,new SqlParameter("@CategoryID", recipe.CategoryID) 
       ,new SqlParameter("@Directions", recipe.Directions) 
      }; 
      dataWriter.Write(parameters, "CreateRecipe"); 
      logger.LogError("Event", "User was able to create a recipe to the database", "Class: RecipeDAO -- Method: CreateRecipe"); 
     } 
     catch (Exception e) 
     { 
      logger.LogError("Error", "User was unable to create a recipe to the database, error: " + e, "Class: RecipeDAO -- Method: CreateRecipe"); 
     } 

    } 

モデル - CreateRecipeVM

public class CreateRecipeVM 
{ 
    public int RecipeID { get; set; } 

    [Required] 
    [Display(Name = "Recipe Name")] 
    [StringLength(25, ErrorMessage = "Please enter a recipe name at least {2} and no more than {1} characters long.", MinimumLength = 3)] 
    public string RecipeName { get; set; } 

    [Required] 
    [Display(Name = "Categories")] 
    public List<CategorySM> Categories { get; set; } 
    public int CategoryID { get; set; } 

    [Required] 
    [Display(Name = "Ingredients")] 
    public List<RecipeIngredientVM> Ingredients { get; set; } 

    [Required] 
    [Display(Name = "Directions")] 
    public string Directions { get; set; } 
} 

モデル - レシピエンジニアリングVM

public class RecipeIngredientVM 
{ 
    public int RecipeID { get; set; } 
    public int IngredientID { get; set; } 

    [Required] 
    [Display(Name = "Ingredient Name")] 
    public string IngredientName { get; set; } 

    [Required] 
    [Display(Name = "Quantity")] 
    public string IngredientAmount { get; set; } 
} 

今、私はです。ほぼ私はCreateRecipeIngredientsメソッドが正しく書かれていますが、わかりません。そして、私はこれが長らく残っているポストだと分かっていますが、私は基礎を整えたら、私の問題点を説明します。私のレシピコントローラで

レシピを作成するために、私が持っている:

// GET: Recipe/Create 
    public ActionResult Create() 
    { 
     CreateRecipeVM recipe = new CreateRecipeVM(); 
     recipe.Categories = catLog.GetAllCategories(); 
     recipe.Ingredients = Mapper.Map<List<RecipeIngredientVM>>(ingLog.GetAllIngredients());    
     return View(recipe); 
    } 

    // POST: Recipe/Create 
    [HttpPost] 
    public ActionResult Create(CreateRecipeVM recipe, List<RecipeIngredientVM> ingredients) 
    { 
     try 
     { 
      TempData["NewRecipeID"] = recipe.RecipeID;     
      recipe.Ingredients = (List<RecipeIngredientVM>)TempData.Peek("NewRecipeIngredients"); 
recLog.CreateRecipe(Mapper.Map<RecipeSM>(recipe)); 
      recIngLog.CreateRecipeIngredients(Mapper.Map<RecipeSM>(recipe)); 
      return RedirectToAction("Details", new { id = recipe.RecipeID }); ; 
     } 
     catch 
     { 
      return View(); 
     } 
    } 

はレシピのビューを作成します。マイ次のとおりです。

@model MyKitchen.Models.CreateRecipeVM 
@{ 
    ViewBag.Title = "Create"; 
    Layout = "~/Views/Shared/_Layout.cshtml"; 
} 

<h3>Add a New Recipe</h3> 

@using (Html.BeginForm()) 
{ 
    @Html.AntiForgeryToken() 

<div class="form-horizontal"> 

    <hr /> 
    @Html.ValidationSummary(true, "", new { @class = "text-danger" }) 
    @Html.HiddenFor(model => model.RecipeID) 

    <div class="form-group"> 
     @Html.LabelFor(model => model.RecipeName, htmlAttributes: new { @class = "control-label col-md-2" }) 
     <div class="col-md-10"> 
      @Html.EditorFor(model => model.RecipeName, new { htmlAttributes = new { @class = "form-control" } }) 
      @Html.ValidationMessageFor(model => model.RecipeName, "", new { @class = "text-danger" }) 
     </div> 
    </div> 

    <div class="form-group"> 
     @Html.LabelFor(model => model.Categories, htmlAttributes: new { @class = "control-label col-md-2"}) 
     <div class="col-md-10"> 
      @Html.DropDownList("CategoryID", new SelectList(Model.Categories, "CategoryID", "CategoryName"), "--- Select A Category ---") 
     </div> 
    </div> 

    <div class="form-group"> 
     @Html.LabelFor(model => model.Directions, htmlAttributes: new { @class = "control-label col-md-2" }) 
     <div class="col-md-10"> 
      @Html.EditorFor(model => model.Directions, new { htmlAttributes = new { @class = "form-control" } }) 
      @Html.ValidationMessageFor(model => model.Directions, "", new { @class = "text-danger" }) 
     </div> 
    </div> 

    <div class="form-group"> 
     @Html.LabelFor(model => model.Ingredients, htmlAttributes: new { @class = "control-label col-md-2" }) 
     <div class="col-md-10"> 
      <button type="button" name="AddIngredients" id="showPartial" class="btn btn-default">Click here to add ingredients for this recipe</button> 
      <div id="partialView"></div> 
     </div> 
    </div> 
    <div class="form-group"> 
     <div class="col-md-offset-2 col-md-10"> 
      <input type="submit" value="Create" class="btn btn-default" /> 
     </div> 
    </div> 
</div> 
} 

あなたがIDで「AddIngredients」というボタンをクリックしてください"showPartial"の場合、それはその下にあるそれぞれのDivという名前のPartialViewでレンダリングされます。そのための私のjqueryは、それを理解しようと何時間もした後、うまくいく(私はこれについて新しいことを言ったのだろうか?)。

は今、遠くダウン私のRecipeControllerで、私はその部分図である次のような方法があります。

// GET Recipe/CreateIngredientsForRecipe 
     public ActionResult CreateIngredientsForRecipe() 
     { 
      List<RecipeIngredientVM> ingredients = Mapper.Map<List<RecipeIngredientVM>>(ingLog.GetAllIngredients()); 
     return View(ingredients); 
    } 

    // POST Recipe/CreateIngredientsForRecipe 
    [HttpPost] 
    public ActionResult CreateIngredientsForRecipe(List<RecipeIngredientVM> ingredients) 
    { 
     List<RecipeIngredientVM> recIngredients = new List<RecipeIngredientVM>(); 
     foreach(RecipeIngredientVM food in ingredients) 
     { 
      RecipeIngredientVM recFood = new RecipeIngredientVM(); 
      if(food.IngredientAmount != null) 
      { 
       recFood.RecipeID = (int)TempData.Peek("NewRecipeID"); 
       recFood.IngredientID = food.IngredientID; 
       recFood.IngredientName = food.IngredientName; 
       recFood.IngredientAmount = food.IngredientAmount; 
       recIngredients.Add(recFood); 
      } 
     } 
     TempData["NewRecipeIngredients"] = recIngredients; 
     return RedirectToAction("Details", new { id = recIngredients[0].RecipeID }); ; 
    } 
} 
} 

と部分が正しくでレンダリングし、かつCreateIngredientsForRecipe.cshtmlがあること:

<table class="table"> 
<tr> 
    <th> 
     @Html.DisplayNameFor(model => model.IngredientName) 
    </th> 
    <th> 
     @Html.DisplayName("Is it in your kitchen?") 
    </th> 
    <th></th> 
</tr> 

@foreach (var item in Model) { 
<tr> 
    <td> 
     @Html.DisplayFor(modelItem => item.IngredientName) 
    </td> 
    <td> 
     @Html.EditorFor(modelItem => item.IngredientAmount) 
    </td> 
</tr> 
} 
<tr> 
    <td>@Html.ActionLink("Don't see the ingredients you need? Click here to add them to the ingredient database!", "Create", "Ingredient")</td> 
</tr> 

今、私の問題です。ページの下部にある[作成]ボタンをクリックすると、CreateRecipeとCreateRecipeIngredientsのアクションとメソッドが呼び出されます。私はこれを行う方法がわかりませんが、これまでに書いたことを書いています。これが現在の私のコードです。私はこの事を取得しようと、過去2日間でほぼ完全な20時間を費やしてきた私は、私が試したものをすべて覚えていませんが、今、それは私に戻って撮影しています例外は

User was unable to create a list of ingredients for a recipe, error: System.InvalidOperationException: ExecuteNonQuery requires an open and available Connection. The connection's current state is closed. 
    at System.Data.SqlClient.SqlCommand.ValidateCommand(String method, Boolean async) 
    at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource`1 completion, String methodName, Boolean sendToPipe, Int32 timeout, Boolean asyncWrite) 
    at System.Data.SqlClient.SqlCommand.ExecuteNonQuery() 
    at DAL.RecipeIngredientDAO.CreateRecipeIngredients(RecipeDM recipe) in C:\Users\Sabba\Documents\Visual Studio 2015\Projects\MyKitchen\DAL\RecipeIngredientDAO.cs:line 73 

です仕事をするが、役に立たない。私は、この1つのことを除いて、プロジェクトの残りの部分をほぼ完了しました。そして、それは私を絶対に嫌がらせをしています。

誰かがこの作品を私が望むやり方、あるいは少なくともそれが必要としているように書く方向に向けることができますか?

答えて

0

エラーに基づき、接続を開けてください。 (SqlConnectionの接続=新しいSqlConnectionオブジェクト(のConnectionString)) {

connection.open()を使用して

... }

public class Address 
{ 
    public int? AddressID { get; set; } 
    public string City { get; set; } 
} 
public class Account 
{ 
    public int? AccountID { get; set; } 
    public string Name { get; set; } 
    public List<Address> Addresses { get; set; } 
} 


public class AccountRepository 
{ 
    public void Save(Account newAccount) 
    { 
     using (var conn = new SqlConnection()) 
     { 
      conn.Open(); 
      var tran = conn.BeginTransaction(); 

      try 
      { 
       //add account 
       var cmd = new SqlCommand(); 
       cmd.Connection = conn; 
       cmd.Transaction = tran; 
       cmd.CommandType = CommandType.Text; 
       cmd.CommandText = @" 
INSERT INTO Accounts 
VALUEs (@p_account_name); 

SET @p_account_ID = scope_identity(); 
"; 

       //param to get account ID 
       var accountID = new SqlParameter("p_account_id", typeof(int)); 
       accountID.Direction = ParameterDirection.Output; 

       cmd.Parameters.Add(accountID); 
       cmd.Parameters.AddWithValue("p_account_name", newAccount.Name); 
       cmd.ExecuteNonQuery(); 

       newAccount.AccountID = (int)accountID.Value; 

       if (newAccount.Addresses.Count > 0) 
       { 
        //add address 
        foreach (var address in newAccount.Addresses) 
        { 

         cmd = new SqlCommand(); 
         cmd.Connection = conn; 
         cmd.Transaction = tran; 
         cmd.CommandType = CommandType.Text; 
         cmd.CommandText = @" 
INSERT INTO Address (account_id, city) 
VALUEs (@p_account_id, @p_city); 

SET @p_address_ID = scope_identity(); 
"; 
         //param to get address ID 
         var addressID = new SqlParameter("p_address_id", typeof(int)); 
         addressID.Direction = ParameterDirection.Output; 

         cmd.Parameters.Add(addressID); 
         cmd.Parameters.AddWithValue("p_account_id", newAccount.AccountID); 
         cmd.Parameters.AddWithValue("p_city", address.City); 

         cmd.ExecuteNonQuery(); 
         address.AddressID = (int)addressID.Value; 
        } 
       } 


       //commit transaction 
       tran.Commit(); 
      } 
      catch (Exception ex) 
      { 
       tran.Rollback(); 
       throw; 
      } 

     } 
    } 
} 

私は、コードをテストしていないが、あなたのアイデアを得ます。新しいコンソールアプリを作成してテストするだけです。

+0

また、RecipeDMとその成分を保存するときは、実際にトランザクションブロックを使用する必要があります。レシピDMをユニットとして保存します。 – dfdsfdsfsdf

+0

また、文字列置換を使用しないでください。 SQLインジェクションは依然として大きな問題です。 – dfdsfdsfsdf

+0

私はあなたの推薦でそのアドオンコードを書く方法について少し曖昧です。多分例を投稿できますか?一度のレシピで多くの成分をループすることは、今、私の背中を本当に蹴っています。 – SnarkyBird