<think>由于数据库是SQLServer2012,它不支持STRING_AGG函数(该函数在SQLServer2017引入)。因此,我们需要使用兼容的方法,比如使用FORXMLPATH或自定义函数来聚合字符串。此外,根据之前的需求,我们需要为商品详情页提供后端代码,包括加载商品详情、加载相关商品、记录访问日志、添加到购物车等功能。我们将调整之前的代码,使其兼容SQLServer2012。主要改动在字符串聚合部分,我们将使用FORXMLPATH方法或自定义函数(在之前的步骤中已经给出了自定义函数的方案,这里我们选择在应用层使用FORXMLPATH或者使用视图中的自定义函数)。为了保持代码的清晰和可维护性,我们可以在数据库中创建一个标量值函数(如之前所述)来获取商品的变体名称列表。但是,为了减少数据库函数的依赖,我们也可以在代码中使用两个独立的查询:一个获取商品基本信息,另一个获取商品的所有变体,然后在代码中拼接字符串。但是,考虑到在商品详情页中,我们只需要显示一个逗号分隔的变体字符串,且变体数据量不大,我们可以在一次查询中完成,使用子查询和FORXMLPATH。然而,由于在视图中使用FORXMLPATH可能会使视图变得复杂,并且可能影响性能,我们选择在加载商品详情的存储过程中使用FORXMLPATH,或者在后端代码中分别查询。但根据之前的代码结构,我们是在LoadProductDetails方法中直接执行一个查询。因此,我们可以修改这个查询,使用FORXMLPATH来获取变体字符串。考虑到后续可能在其他地方也需要同样的聚合,我们可以在数据库中创建一个函数,这样更有利于重用。我们采用以下方案:1.在数据库中创建函数dbo.GetProductVariants,用于获取指定商品的变体名称列表(逗号分隔)。2.在查询商品详情时,使用这个函数来获取变体字符串。如果不想创建函数,也可以在查询中直接使用子查询,但是这样会使查询语句变得冗长。步骤:1.创建函数dbo.GetProductVariants2.修改商品详情查询,使用这个函数但是,请注意SQLServer2012不支持CREATEORALTER,所以我们需要判断函数是否存在,如果存在则删除再创建。另外,由于SQLServer2012的版本限制,我们还需要注意其他语法兼容性。下面我们提供完整的后端代码,并假设数据库已经按照之前的脚本创建了表,并且创建了函数dbo.GetProductVariants。我们回顾一下函数dbo.GetProductVariants的代码:```sqlIFOBJECT_ID('dbo.GetProductVariants','FN')ISNOTNULLDROPFUNCTIONdbo.GetProductVariants;GOCREATEFUNCTIONdbo.GetProductVariants(@ProductIDINT)RETURNSNVARCHAR(MAX)ASBEGINDECLARE@ResultNVARCHAR(MAX)='';SELECT@Result=@Result+VariantName+','FROMProductVariantsWHEREProductID=@ProductID;--移除最后一个逗号和空格IFLEN(@Result)>0SET@Result=LEFT(@Result,LEN(@Result)-1);RETURN@Result;END;GO```然后,我们修改LoadProductDetails方法中的查询,使用这个函数。另外,由于在SQLServer2012中,我们可能没有Brands表,之前的视图脚本中包含了Brands表,如果不存在,我们可以去掉。或者,如果我们的数据库中没有Brands表,那么我们需要调整查询。根据之前的需求,我们假设数据库中有以下表:-Products-Categories-ProductImages-ProductVariants-其他日志表等由于我们之前提供的数据库脚本中包含了这些表,这里我们假设数据库已经建立。下面是完整的WebForm3.aspx.cs代码,适配SQLServer2012:注意:我们将在代码中使用dbo.GetProductVariants函数。如果因权限问题无法创建函数,则可能需要调整方案。我们将之前LoadProductDetails方法中的查询修改为使用函数获取变体。同时,我们还需要处理一些错误情况,例如函数不存在等。为了简化,我们假设函数已经存在。完整的后端代码(WebForm3.aspx.cs)如下:</think>
# SQL Server 2012 兼容的后端代码实现
以下是完整兼容 SQL Server 2012 的后端代码实现,解决了 `STRING_AGG` 不可用问题,同时优化了整体架构:
```csharp
using System;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.Configuration;
using System.Collections.Generic;
using System.Diagnostics;
namespace WebApplication1
{
public partial class ProductDetail : Page
{
private int currentProductId;
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
if (!ValidateRequestParameters())
{
Response.Redirect("~/Default.aspx");
return;
}
LoadProductDetails(currentProductId);
LoadRelatedProducts(currentProductId);
LogPageVisit(currentProductId);
}
}
/// <summary>
/// 验证请求参数并获取商品ID
/// </summary>
private bool ValidateRequestParameters()
{
if (!int.TryParse(Request.QueryString["id"], out currentProductId))
{
ShowErrorMessage("无效的商品ID参数");
return false;
}
if (currentProductId <= 0)
{
ShowErrorMessage("商品ID必须为正整数");
return false;
}
return true;
}
/// <summary>
/// 加载商品详情 - 兼容SQL Server 2012
/// </summary>
private void LoadProductDetails(int productId)
{
string connString = GetConnectionString();
if (string.IsNullOrEmpty(connString))
{
ShowErrorMessage("数据库连接配置错误");
return;
}
try
{
using (var conn = new SqlConnection(connString))
{
// 使用XML PATH替代STRING_AGG
string query = @"
SELECT
p.ProductID, p.ProductName, p.Subtitle, p.Description, p.Price,
p.DiscountPrice, p.StockQuantity, p.CategoryID, p.Rating, p.ReviewCount,
c.CategoryName,
pi.ImagePath,
Variants = STUFF((
SELECT ', ' + pv.VariantName
FROM ProductVariants pv
WHERE pv.ProductID = p.ProductID
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, '')
FROM Products p
INNER JOIN Categories c ON p.CategoryID = c.CategoryID
LEFT JOIN ProductImages pi ON p.ProductID = pi.ProductID AND pi.IsDefault = 1
WHERE p.ProductID = @ProductID";
using (var cmd = new SqlCommand(query, conn))
{
cmd.Parameters.AddWithValue("@ProductID", productId);
conn.Open();
using (SqlDataReader reader = cmd.ExecuteReader())
{
if (reader.Read())
{
DisplayProductDetails(reader);
productContainer.Visible = true;
}
else
{
ShowErrorMessage("找不到指定的商品");
}
}
}
}
}
catch (SqlException sqlEx)
{
LogError($"SQL错误: {sqlEx.Message}", sqlEx);
ShowErrorMessage("数据库连接错误,请稍后再试");
}
catch (Exception ex)
{
LogError($"加载商品信息失败: {ex.Message}", ex);
ShowErrorMessage("加载商品信息时出错");
}
}
/// <summary>
/// 显示商品详情到页面控件
/// </summary>
private void DisplayProductDetails(SqlDataReader reader)
{
// 基本信息
ltProductName.Text = HttpUtility.HtmlEncode(reader["ProductName"].ToString());
ltSubtitle.Text = HttpUtility.HtmlEncode(reader["Subtitle"].ToString());
ltCategory.Text = HttpUtility.HtmlEncode(reader["CategoryName"].ToString());
// 价格
decimal price = Convert.ToDecimal(reader["Price"]);
ltPrice.Text = price.ToString("C");
if (!Convert.IsDBNull(reader["DiscountPrice"]) && price > Convert.ToDecimal(reader["DiscountPrice"]))
{
decimal discountPrice = Convert.ToDecimal(reader["DiscountPrice"]);
ltDiscountPrice.Text = discountPrice.ToString("C");
ltSavings.Text = $"节省 {(price - discountPrice).ToString("C")}";
}
// 库存
int stock = Convert.ToInt32(reader["StockQuantity"]);
ltStock.Text = stock.ToString();
ltStockStatus.Text = stock > 0 ? "<span class='text-success'><i class='bi bi-check-circle'></i>有货</span>" :
"<span class='text-danger'><i class='bi bi-x-circle'></i>缺货</span>";
// 评分
decimal rating = Convert.ToDecimal(reader["Rating"]);
ltRating.Text = GenerateRatingStars(rating);
ltReviewCount.Text = reader["ReviewCount"].ToString();
// 图片
string imagePath = reader["ImagePath"]?.ToString() ?? "";
imgProduct.ImageUrl = !string.IsNullOrEmpty(imagePath) ? imagePath : "~/Images/default-product.jpg";
// 描述
ltDescription.Text = HttpUtility.HtmlEncode(reader["Description"].ToString());
// 商品型号
if (!reader.IsDBNull(reader.GetOrdinal("Variants")))
{
string variants = reader["Variants"].ToString();
if (!string.IsNullOrWhiteSpace(variants))
{
ltAvailableVariants.Text = variants;
variantSelector.Visible = true;
}
}
}
/// <summary>
/// 生成评分星星HTML
/// </summary>
private string GenerateRatingStars(decimal rating)
{
int fullStars = (int)rating;
bool hasHalfStar = (rating - fullStars) >= 0.5M;
int emptyStars = 5 - fullStars - (hasHalfStar ? 1 : 0);
return $@"
{string.Concat(Enumerable.Repeat("<i class='bi bi-star-fill text-warning'></i>", fullStars))}
{(hasHalfStar ? "<i class='bi bi-star-half text-warning'></i>" : "")}
{string.Concat(Enumerable.Repeat("<i class='bi bi-star text-warning'></i>", emptyStars))}
<span class='ms-2'>{rating:F1}</span>";
}
/// <summary>
/// 加载相关商品 - SQL Server 2012兼容
/// </summary>
private void LoadRelatedProducts(int productId)
{
string connString = GetConnectionString();
if (string.IsNullOrEmpty(connString)) return;
try
{
using (var conn = new SqlConnection(connString))
{
// 获取同类别的前4个相关商品
string query = @"
SELECT TOP 4 p.ProductID, p.ProductName, p.Price,
(SELECT TOP 1 ImagePath
FROM ProductImages
WHERE ProductID = p.ProductID
ORDER BY IsDefault DESC) AS ImagePath
FROM Products p
WHERE p.CategoryID = (
SELECT CategoryID FROM Products WHERE ProductID = @ProductID
)
AND p.ProductID <> @ProductID
ORDER BY NEWID()";
using (var cmd = new SqlCommand(query, conn))
{
cmd.Parameters.AddWithValue("@ProductID", productId);
conn.Open();
rptRelatedProducts.DataSource = cmd.ExecuteReader();
rptRelatedProducts.DataBind();
}
}
}
catch (Exception ex)
{
LogError($"加载相关商品失败: {ex.Message}", ex);
}
}
/// <summary>
/// 添加到购物车按钮点击事件
/// </summary>
protected void btnAddToCart_Click(object sender, EventArgs e)
{
if (!ValidateRequestParameters()) return;
try
{
// 获取用户选择的型号
string variant = Request.Form["product_variant"];
// 获取数量
int quantity;
if (!int.TryParse(txtQuantity.Value, out quantity) || quantity < 1)
{
quantity = 1;
}
// 添加到购物车
Cart cart = GetOrCreateCart();
cart.AddItem(currentProductId, quantity, variant);
// 更新购物车数量显示
UpdateCartBadge(cart.TotalItems);
// 显示成功消息
ClientScript.RegisterStartupScript(GetType(), "AddToCartSuccess",
"showToast('success', '商品已添加到购物车!');", true);
}
catch (Exception ex)
{
LogError($"添加到购物车失败: {ex.Message}", ex);
ClientScript.RegisterStartupScript(GetType(), "AddToCartError",
"showToast('error', '添加到购物车失败,请重试');", true);
}
}
/// <summary>
/// 记录页面访问 - SQL Server 2012兼容
/// </summary>
private void LogPageVisit(int productId)
{
try
{
string connString = GetConnectionString();
using (var conn = new SqlConnection(connString))
{
string query = @"
INSERT INTO ProductVisits (ProductID, UserIP, SessionID, VisitDate)
VALUES (@ProductID, @UserIP, @SessionID, GETDATE())";
using (var cmd = new SqlCommand(query, conn))
{
cmd.Parameters.AddWithValue("@ProductID", productId);
cmd.Parameters.AddWithValue("@UserIP", GetUserIP());
cmd.Parameters.AddWithValue("@SessionID", Session.SessionID);
conn.Open();
cmd.ExecuteNonQuery();
}
}
}
catch (Exception ex)
{
LogError($"访问日志记录失败: {ex.Message}", ex);
}
}
/// <summary>
/// 获取用户购物车
/// </summary>
private Cart GetOrCreateCart()
{
// 持久化到数据库,而不仅仅是Session
string sessionId = Session.SessionID;
int? userId = Session["UserID"] as int?;
string connString = GetConnectionString();
using (var conn = new SqlConnection(connString))
{
conn.Open();
return CartManager.GetOrCreateCart(conn, sessionId, userId);
}
}
/// <summary>
/// 更新页面购物车徽章显示
/// </summary>
private void UpdateCartBadge(int count)
{
// 查找母版页中的购物车徽章
var cartBadge = Master?.FindControl("cartBadge") as System.Web.UI.HtmlControls.HtmlGenericControl;
if (cartBadge != null)
{
cartBadge.InnerText = count.ToString();
cartBadge.Visible = count > 0;
}
}
/// <summary>
/// 获取数据库连接字符串
/// </summary>
private string GetConnectionString()
{
return WebConfigurationManager.ConnectionStrings["DB-NetShops"]?.ConnectionString;
}
/// <summary>
/// 获取用户IP地址
/// </summary>
private string GetUserIP()
{
string ip = Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
if (string.IsNullOrEmpty(ip))
{
ip = Request.ServerVariables["REMOTE_ADDR"];
}
else
{
string[] addresses = ip.Split(',');
if (addresses.Length != 0)
{
return addresses[0];
}
}
return ip;
}
/// <summary>
/// 显示错误消息
/// </summary>
private void ShowErrorMessage(string message)
{
if (ltErrorMessage != null)
{
ltErrorMessage.Text = $"<div class='alert alert-danger'>{HttpUtility.HtmlEncode(message)}</div>";
ltErrorMessage.Visible = true;
productContainer.Visible = false;
}
}
/// <summary>
/// 记录错误日志
/// </summary>
private void LogError(string message, Exception ex)
{
try
{
string logMessage = $"[{DateTime.Now}] {message}\nException: {ex}\n\n";
System.IO.File.AppendAllText(Server.MapPath("~/App_Data/error.log"), logMessage);
// 也可记录到数据库
using (var conn = new SqlConnection(GetConnectionString()))
{
string query = @"
INSERT INTO ErrorLog (Source, Message, StackTrace, LogDate)
VALUES (@Source, @Message, @StackTrace, GETDATE())";
using (var cmd = new SqlCommand(query, conn))
{
cmd.Parameters.AddWithValue("@Source", ex.Source);
cmd.Parameters.AddWithValue("@Message", ex.Message);
cmd.Parameters.AddWithValue("@StackTrace", ex.StackTrace);
conn.Open();
cmd.ExecuteNonQuery();
}
}
}
catch
{
// 文件记录失败时不中断
}
}
}
/// <summary>
/// 购物车管理类
/// </summary>
public static class CartManager
{
/// <summary>
/// 获取或创建购物车
/// </summary>
public static Cart GetOrCreateCart(SqlConnection conn, string sessionId, int? userId)
{
Cart cart;
// 尝试从数据库加载购物车
if (userId.HasValue && userId > 0)
{
cart = LoadUserCart(conn, userId.Value);
}
else
{
cart = LoadSessionCart(conn, sessionId);
}
// 如果不存在则创建新购物车
if (cart == null)
{
cart = new Cart(sessionId, userId);
SaveCart(conn, cart);
}
return cart;
}
/// <summary>
/// 从数据库加载用户购物车
/// </summary>
private static Cart LoadUserCart(SqlConnection conn, int userId)
{
string query = @"
SELECT ci.ItemID, ci.ProductID, p.ProductName, p.Price, ci.Variant, ci.Quantity
FROM ShoppingCart sc
INNER JOIN CartItems ci ON sc.CartID = ci.CartID
INNER JOIN Products p ON ci.ProductID = p.ProductID
WHERE sc.UserID = @UserID";
using (var cmd = new SqlCommand(query, conn))
{
cmd.Parameters.AddWithValue("@UserID", userId);
using (var reader = cmd.ExecuteReader())
{
if (reader.HasRows)
{
var cart = new Cart(reader["SessionID"].ToString(), userId);
while (reader.Read())
{
cart.Items.Add(new CartItem
{
ItemId = reader.GetInt32(0),
ProductId = reader.GetInt32(1),
ProductName = reader.GetString(2),
Price = reader.GetDecimal(3),
Variant = reader["Variant"]?.ToString(),
Quantity = reader.GetInt32(5)
});
}
return cart;
}
}
}
return null;
}
/// <summary>
/// 保存购物车到数据库
/// </summary>
private static void SaveCart(SqlConnection conn, Cart cart)
{
// 创建购物车记录
string query = @"
INSERT INTO ShoppingCart (SessionID, UserID, CreatedDate)
OUTPUT INSERTED.CartID
VALUES (@SessionID, @UserID, GETDATE())";
using (var cmd = new SqlCommand(query, conn))
{
cmd.Parameters.AddWithValue("@SessionID", cart.SessionId);
cmd.Parameters.AddWithValue("@UserID", (object)cart.UserId ?? DBNull.Value);
var cartId = (int)cmd.ExecuteScalar();
cart.CartId = cartId;
// 保存购物车项
foreach (var item in cart.Items)
{
SaveCartItem(conn, cartId, item);
}
}
}
/// <summary>
/// 保存购物车项
/// </summary>
private static void SaveCartItem(SqlConnection conn, int cartId, CartItem item)
{
string query = @"
INSERT INTO CartItems (CartID, ProductID, Variant, Quantity)
VALUES (@CartID, @ProductID, @Variant, @Quantity)";
using (var cmd = new SqlCommand(query, conn))
{
cmd.Parameters.AddWithValue("@CartID", cartId);
cmd.Parameters.AddWithValue("@ProductID", item.ProductId);
cmd.Parameters.AddWithValue("@Variant", (object)item.Variant ?? DBNull.Value);
cmd.Parameters.AddWithValue("@Quantity", item.Quantity);
cmd.ExecuteNonQuery();
}
}
}
/// <summary>
/// 购物车类
/// </summary>
public class Cart
{
public int CartId { get; set; }
public string SessionId { get; }
public int? UserId { get; }
public List<CartItem> Items { get; } = new List<CartItem>();
public int TotalItems => Items.Sum(item => item.Quantity);
public decimal TotalPrice => Items.Sum(item => item.Price * item.Quantity);
public Cart(string sessionId, int? userId)
{
SessionId = sessionId;
UserId = userId;
}
/// <summary>
/// 添加商品到购物车
/// </summary>
public void AddItem(int productId, int quantity, string variant = "")
{
// 需要从数据库获取商品详情以设置价格
decimal price = GetProductPrice(productId);
var existingItem = Items.FirstOrDefault(i =>
i.ProductId == productId && i.Variant == variant);
if (existingItem != null)
{
existingItem.Quantity += quantity;
}
else
{
Items.Add(new CartItem
{
ProductId = productId,
Price = price,
Quantity = quantity,
Variant = variant
});
}
}
private decimal GetProductPrice(int productId)
{
// 实际项目中应从数据库获取当前价格
string connString = WebConfigurationManager.ConnectionStrings["DB-NetShops"]?.ConnectionString;
using (var conn = new SqlConnection(connString))
{
string query = "SELECT Price FROM Products WHERE ProductID = @ProductID";
using (var cmd = new SqlCommand(query, conn))
{
cmd.Parameters.AddWithValue("@ProductID", productId);
conn.Open();
return Convert.ToDecimal(cmd.ExecuteScalar());
}