ASP.NET MVC一次发布两个动作,将多个条目写入两个表

时间:2016-04-19 15:40:03

标签: c# sql-server asp.net-mvc asp.net-mvc-3

我正在使用数据库在ASP.NET MVC中构建程序。可以这么说,它适用于厨房配方和配料。它从数据库表中提取配方,从另一个表中提取成分,通过联结表进行关联。

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

将唯一约束设置为RecipeIDIngredientID

现在,我遇到的问题是创建一个新配方,我想继续并同时保存RecipeIngredients列表。 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; }
}

模型 - RecipeIngredientVM

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方法,但我不确定。而且我知道这是一个冗长的帖子,但我保证,一旦我得到基础,我会解释我的问题是什么。

在我的Recipe控制器上,我有Create Recipe:

 // 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>
}

当您点击名为&#34; AddIngredients&#34;的按钮时使用&#34; showPartial&#34;的id,它在PartialView中呈现在它下面的相应命名的Div中。我的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。我不知道该怎么做,但我已经写了我到目前为止的内容,这就是我目前的代码。我不记得我曾经尝试过的东西,但是现在,它背叛了我的例外

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

在过去的两天里,我花了差不多整整20个小时试图让这件事情发挥作用,但无济于事。我已经完成了几乎整个项目的其余部分,除了这一件事,它让我绝对疯狂。

有人可以指点我的写作方向,让我按照我想要的方式工作,或者至少是它需要的方式吗?

1 个答案:

答案 0 :(得分:0)

根据错误,请打开连接。

使用(SqlConnection connection = new 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;
            }

        }
    }
}

我没有测试代码,但你明白了。只需创建一个新的控制台应用程序并进行测试。