You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

开发外卖应用创建订单时Food的Guid主键重复报错如何解决

问题根源

你遇到的重复键异常和主键使用Guid类型完全无关,不需要修改主键类型。异常的本质是EF Core的变更跟踪机制将你前端传入的Food实例识别为需要新增到Food表的新实体,而这些实例已经带上了数据库中已存在的FoodId,插入时触发主键冲突。

你直接将前端传入的、未被EF上下文跟踪的Food集合赋值给了Order的导航属性,EF在保存Order时,会尝试将关联的Food也作为新实体插入Food表,而非仅关联已有Food生成多对多中间表记录。


最优解决方案

  1. 优化CreateOrderCommand结构,前端仅需要提交食品ID集合即可,无需提交完整Food实体,既减少传输体积也避免数据被篡改:
public class CreateOrderCommand : IRequest<Guid>
{
    public string UserName { get; set; }
    // 替换Food集合为食品ID集合
    public ICollection<Guid> FoodIds { get; set; }
}
  1. 调整Handler逻辑,从数据库查询已存在的Food实体(会被EF上下文正常跟踪,识别为已有实体),再关联到Order:
public async Task<Guid> Handle(CreateOrderCommand request, CancellationToken cancellationToken)
{
    var order = _mapper.Map<Order>(request);
    order.OrderTime = DateTime.Now;
    var user = await _userRepository.GetByUserNameAsync(request.UserName);
    GeoCoordinate userLocation = new GeoCoordinate(user.Latitude, user.Longitude);

    List<Restaurant> restaurants = (List<Restaurant>)await _restaurantRepository.ListAllAsync();
    List<double> distances = new List<double>();

    double minValue = 0;
    int minValuePosition = -1;

    //pronadji udaljenosti
    for (int i = 0; i < restaurants.Count; i++)
    {
        TimeSpan ts = DateTime.Now - restaurants[i].LastOrdered;
        if(ts.TotalMinutes >= 15)
        {
            GeoCoordinate restaurantCoords = new GeoCoordinate(restaurants[i].Latitude, restaurants[i].Longitude);
            distances.Add(userLocation.GetDistanceTo(restaurantCoords));
            minValue = distances[i];
            minValuePosition = i;
        }
        else
        {
            distances[i] = -1;
        }
    }

    //pronadji najmanju udaljenost koja je na listi
    for (int i = 0; i < distances.Count; i++)
    {
        if (distances[i] < 0)
            continue;
        if (distances[i] < minValue)
        {
            minValue = distances[i];
            minValuePosition = i;
        }
    }

    if(minValuePosition == -1)
    {
        //nema slobodnih restorana
        throw new Exception("当前无可用餐厅");
    }

    order.UserName = user.UserName;
    // 新增逻辑:批量查询已存在的食品
    var existingFoods = await _foodRepository.ListAsync(f => request.FoodIds.Contains(f.FoodId), cancellationToken);
    if (existingFoods.Count != request.FoodIds.Count)
    {
        throw new ArgumentException("包含无效的食品ID");
    }
    order.Foods = existingFoods;
    // 用数据库查询的真实价格计算总价,避免前端篡改价格
    order.OrderTotal = existingFoods.Sum(f => f.Price);

    order = await _orderRepository.AddAsync(order);

    return order.OrderId;
}

临时兼容方案(不推荐)

如果你坚持要在Command中传递完整Food实体,可以在关联到Order前手动将这些Food附加到EF上下文,标记为已有实体:

foreach (var food in request.Foods)
{
    // 附加到上下文,标记为未修改,EF就不会尝试插入Food表
    _context.Attach(food).State = EntityState.Unchanged;
}
order.Foods = request.Foods;

该方案的缺点是前端可以篡改Food的价格、名称等字段,附加后EF如果被配置为保存关联实体的修改,会更新Food表的原有数据,存在安全风险。


内容的提问来源于stack exchange,提问作者Nikola Nikolic

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 12:24:03