【问题标题】:ASP.NET MVC Posting two Actions at once to write multiple entries into two tablesASP.NET MVC 一次发布两个操作以将多个条目写入两个表
【发布时间】:2016-04-19 15:40:02
【问题描述】:

我正在用 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

现在,我遇到的问题是创建新食谱,我想继续保存食谱成分列表。 RecipeDM 包含 ID、RecipeName、CategoryID 字段、列表和字段方向。就目前而言,在我的 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 方法,但我不确定。我知道这是一篇冗长的文章,但我保证,一旦我奠定了基础,我会解释我的问题是什么。

在我的食谱控制器上,我有创建食谱:

 // 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 为“showPartial”的名为“AddIngredients”的按钮时,它会在其正下方分别命名的 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>

现在,我的问题。当我单击页面底部的 Create 按钮时,我希望它触发 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 个小时试图让这个东西工作,但无济于事。我已经完成了几乎整个项目的其余部分,除了这件事,它让我非常疯狂。

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

【问题讨论】:

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


    【解决方案1】:

    根据错误,请打开连接。

    使用 (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;
                }
    
            }
        }
    }
    

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

    【讨论】:

    • 另外,在保存 RecipeDM 及其成分时,您应该真正使用事务块。将 RecipeDM 保存为一个单元。
    • 另外,请避免使用字符串替换。 sql注入仍然是个大问题。
    • 我对如何根据您的建议编写该 addcode 有点模糊。你可以发布一个例子吗?为一次食谱循环遍历许多成分现在真的让我很反感。
    • 我已经更新了代码但我没有测试它,因为我不想创建一个新的数据库。
    • 非常感谢您提供了一个我可以使用的示例。我在弄清楚如何按照最初添加的方式编写它时遇到了很大的困难,因为我们学会了以不同的方式设置数据库调用(例如在 CreateRecipe 中,它设置连接和命令并在完全独立的类,通过接口依赖注入调用)。
    猜你喜欢
    • 2014-08-05
    • 2018-03-09
    • 2016-03-26
    • 1970-01-01
    • 2011-10-23
    • 2021-03-23
    • 1970-01-01
    • 1970-01-01
    • 2021-02-14
    相关资源
    最近更新 更多