【问题标题】:Guid problem while attempting to write data in database尝试在数据库中写入数据时出现 Guid 问题
【发布时间】:2021-12-28 12:47:00
【问题描述】:

我正在尝试编写一个具有实体FoodOrder 的送餐应用程序。

public class Order
{
    [Key]
    public Guid OrderId { get; set; }
    public string UserName { get; set; }
    public DateTime OrderTime { get; set; }
    public ICollection<Food> Foods { get; set; }
    public decimal OrderTotal { get; set; }
}

public class Food
{
    [Key]
    public Guid FoodId { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public string PictureUrl { get; set; }
    public double Rating { get; set; }
    public string Comments { get; set; }
}

在创建新的Order 时会出现问题,因为我在ICollection&lt;Food&gt; Foods 中从Food 传递Guid Id。我正在使用 MediatR 处理用户操作,以及 CreateOrderCommand 类和 Order 类本身之间的映射。

捕获的异常声称问题是来自ICollection&lt;Food&gt; 的Guid 已经存在。否则我应该如何映射食物集合,或者我应该将 Guid 更改为我的 Id 的简单 int 或字符串?

“已添加具有相同密钥的项目。密钥:36708fa9-71df-4141-95d6-a73a65cd5dce”

这是邮递员在尝试在 OrderRepository 中添加新订单时捕获的异常,其中 Key 是作为参数传递的 Food 的 Guid。

    public class CreateOrderCommand : IRequest<Guid>
{
    public string UserName { get; set; }
    public ICollection<Food> Foods { get; set; }
}

这是从前端应用接收的数据

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;
        order.Foods = request.Foods;

        foreach(Food f in request.Foods)
        {
            order.OrderTotal += f.Price;
        }

        order = await _orderRepository.AddAsync(order);

        return order.OrderId;
    }

这是数据的处理程序。

【问题讨论】:

  • “捕获的异常声称问题是来自 ICollection 的 Guid 已经存在” - 请逐字发布异常消息,而不是您的解释。实际错误是否类似于 “保存更改时发生错误。错误详细信息:无法跟踪实体类型 'Food' 的实例,因为已经在跟踪具有相同键值 {'Id'} 的另一个实例” ?然后确保您的代码不会跨 DbContext 运行相同实体的两个实例。
  • 我添加了错误代码,但正如我所说,错误与 Guid 有关。就是不知道为什么
  • 然后提供minimal reproducible example。您的映射代码中可能有一些东西将相同的食物添加了两次,或者其他什么。
  • 请使用您提供的课程生成minimal reproducible example
  • 希望这个编辑有帮助

标签: c# .net asp.net-core .net-5 in-memory-database


【解决方案1】:

我假设您的 Food 实体代表了一份可用食物的目录,每个都有自己的 Guid 唯一标识符。

现在,我认为,您正在尝试在 Order 上下文中创建一个“使用”在您的 Food 上下文中标识的食品项目的订单。

在这种情况下,如果相同的食物项目在同一个订单中出现两次,或者出现在其他订单中,那么由于您使用食物资源的唯一标识符作为订单聚合中某个项目的唯一键,您将得到类似上述的 PK 约束错误。

我认为您需要两个独立的实体:

  • 食物
  • OrderItem(指食物)

OrderItem 应该有自己的唯一键,并且有一列包含食物的 id。

public class Order
{
    [Key]
    public Guid OrderId { get; set; }
    public string UserName { get; set; }
    public DateTime OrderTime { get; set; }
    public ICollection<OrderItem> OrderItems { get; set; }
    public decimal OrderTotal { get; set; }
}

public class OrderItem
{
    [Key]
    public Guid OrderItemId { get; set; }
    public Guid FoodId { get; set; }
    public int Quantity { get; set; }
    public decimal TotalPrice { get; set; }
}

public class Food
{
    [Key]
    public Guid FoodId { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public string PictureUrl { get; set; }
    public double Rating { get; set; }
    public string Comments { get; set; }
}

【讨论】:

  • 如果我用原始的 Food Guid no 填充包含食物 id 的列仍然会是一个问题吗?
  • 您目前的问题是因为它是主键(必须是唯一的)。如果 PK 是作为订单项目的唯一标识符创建的另一个值,则此问题得到解决。然后,您将 Food Id 移动到不是主键的另一列,因此不是隐式唯一的。如果您愿意,可以为其添加索引,但请确保该索引没有唯一约束。
  • 已添加建议的实体模型。请注意,Order 现在有一个 OrderItem 集合,每个都指向一种食物。
  • Guid 本身是独一无二的吧?问题不在于键的唯一性,而是 Guid 值的唯一性
  • 是的,Guid.NewGuid() 将生成一个唯一的密钥。如果您正在尝试插入具有相同 ID 的另一份 Food 副本,则违反 PK。我的回答假设在您尝试将其添加到订单之前,您的数据库中已经存在 Food 记录。当您在 Order 上下文中将其添加到订单时,EF 将尝试插入新记录。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-10-17
  • 1970-01-01
  • 2018-10-14
  • 2021-10-17
  • 1970-01-01
  • 1970-01-01
  • 2021-11-20
相关资源
最近更新 更多