当默认值具有多个多对多关系时,在 LINQ 中为 DefaultIfEmpty 方法设置默认值?

Setting default for DefaultIfEmpty method in LINQ when the default has multiple with-many relationships?

我有三个对象:

  1. Event(关系:有很多EventProducts)

  2. Product(关系:有很多EventProducts)

  3. EventProduct(关系:有许多事件,有许多产品,具有 quantity_sold、quantity_allocated 等属性,应按产品和事件存储)

在我的应用程序中,当有人点击一个事件时,一个新的 window 初始化为 所有 产品的列表。他们可以从那里修改我需要在 WPF 中填充数据网格的单元格,这样当有人单击一个事件时。因此,在这段代码的上下文中,事件是已知的且不变的。

我遇到的问题是创建 EventProduct 的默认类型。

我需要一个查询,如果数据库中没有 EventProduct,它将使用 EventProduct.Event = currentEvent 实例化一个 EventProduct(对于使用此查询创建的所有 EventProduct,currentEvent 将保持不变)和 EventProduct.Product = product(每行产品都会改变)

当数据库中有 关联的 EventProduct 时,此代码运行良好。但如果没有,我的 selection returns 我的 Product 就好了,但整个 EventProduct 是空的。

var query2 = from product in dbContext.Products
             join eventProduct in dbContext.EventProducts
                 on new { pIndex = product.index, eIndex = currentEvent.index }
                 equals new { pIndex = eventProduct.Product.index, eIndex = eventProduct.Event.index } into temp
             from eventProduct in temp.DefaultIfEmpty() // this is my problem line
             select new { 
                    Product = product,
                    EventProduct = eventProduct
              };

我尝试为 EventProduct(Event e, Product p) 创建一个构造函数,并在我的 DefaultIfEmpty() 方法中将值传递给构造函数,但我收到错误消息,我的构造函数必须有 0 个参数才能在其中使用方法。我不能那样做,因为如果我那样做,就没有办法告诉我的 EventProduct() 对象它应该与哪个事件和产品相关联。

我也尝试过不使用构造函数,只是创建一个新的 EventProduct 并设置其属性,但我收到错误 "The entity or complex type ...EventProduct cannot be constructed in a LINQ to Entites query"。

最后的结果是,我想 select 我的 ProductEventProduct。如果没有 EventProductProductEvent 相关联,那么我的 EventProduct selection 应该设置为具有 currentEvent 的默认值,即当前行的产品,并将所有属性设置为默认值(均为小数,在本例中应为 0)。

编辑:我刚刚试过这个查询,它也给了我一个不受支持的错误:

var query2 = from product in dbContext.Products
             join eventProduct in dbContext.EventProducts
                 on new { pIndex = product.index, eIndex = currentEvent.index }
                 equals new { pIndex = eventProduct.Product.index, eIndex = eventProduct.Event.index } into temp
             from eventProduct in temp.DefaultIfEmpty()
             select new { 
                 Product = product,
                 EventProduct = eventProduct != null ?
                 eventProduct : new EventProduct
                 {
                     Product = product,
                     Event = currentEvent,
                     quantity_allocated = 0,
                     quantity_sold = 0,
                     quantity_sampled = 0
                 }
             };

编辑:使用此技术解决:

1) 创建一个对象,因为匿名对象是只读的:

    class Associations
    {
        public class ProductEventProduct
        {
            public Product Product { get; set; }
            public EventProduct EventProduct { get; set; }
        }
    }

2) Foreach 数据集中的空对象,替换为默认对象

var query = from product in dbContext.Products
                         join eventProduct in dbContext.EventProducts
                             on new { pIndex = product.index, eIndex = currentEvent.index }
                                equals new { pIndex = eventProduct.Product.index, eIndex = eventProduct.Event.index } into temp
                         from eventProduct in temp.DefaultIfEmpty()
                         select new Associations.ProductEventProduct { 
                             Product = product,
                             EventProduct = eventProduct
                         };
            var dataSource = query.ToList();

            foreach (Associations.ProductEventProduct entry in dataSource)
            {
                if (entry.EventProduct == null)
                {
                    entry.EventProduct = new EventProduct
                    {
                        Product = entry.Product,
                        Event = currentEvent,
                        quantity_allocated = 0,
                        quantity_sold = 0,
                        quantity_sampled = 0
                    };
                }
            }

您遇到的问题似乎是您要求 Entity Framework 创建一个它不知道如何创建的查询。

请记住,Linq 使用延迟执行,因此当您编写查询时,它只是获取数据的位置和方式 的占位符,而不是实际数据。直到其他一些代码 询问 查询实际 运行 和您的信息才填充数据。

对于 Linq to Entities,这意味着它将使用数据库语言构建一个查询并保留它直到您需要数据,然后按照您期望的方式通过您在使用时使用的任何数据库提供程序执行查询调用它,并将其存储在内存中以进行处理。

因此,当您尝试在查询中构造一个新的 EventProduct 来填充您拥有的空值时,数据库不知道那是什么,您会收到错误消息,因为您仍在数据库负责对象的查询部分。 DB Provider 不知道如何构造该新对象,因此 EF 无法转换查询,您会收到错误消息。

解决方案是在尝试构建新的 EventProduct 之前 "hydrate" 您的信息,方法是 运行 通过调用 ToList() 或 [=12] 使您的查询没有 EventProduct 构造函数=] 或类似的,这会强制查询 运行 。 然后,在数据水合(在内存中)之后,遍历您在查询中创建的所有对象,如果 EventProduct 为空,则构造一个新对象并动态添加它,但是您想要这样做。 (可能是带有 Select 语句的第二个查询?)这应该可以解决您提到的错误。

我有点不了解生成匿名对象的原始查询是否会被 DBContext 跟踪,但请记住这一点并对其进行测试。如果你添加一堆 EventProduct 绑定到那些匿名对象的实例,你 可以 将它们默认包含在跟踪中,并且调用 SaveChanges() 可能当您不打算这样做时,无意中将它们全部写入数据库。要记住的事情。

希望这对您有所帮助,请告诉我!

您是否尝试过在 select 子句中为 null 设置默认值?

......
select new 
{ 
    Product = product,
    EventProduct = eventProduct != null 
        ? eventProduct 
        : new EventProduct { .... set default values}
}

UPD

这个问题有可能的解决方法,要么引入 DTO(或其他匿名类型)作为 this 答案建议:

select new 
{ 
    Product = product,
    EventProduct = new 
    {
         Product = product,
         EventId = currentEvent.Id, // possibly you will need to copy every field by hand 
         quantity_allocated = eventProduct == null ? 0 : eventProduct .quantity_allocated ,
         ....
    }
}

或者在查询 db 之后替换空值,因为您应该已经拥有所有需要的数据:

var query2 = from product in dbContext.Products
         join eventProduct in dbContext.EventProducts
             on new { pIndex = product.index, eIndex = currentEvent.index }
             equals new { pIndex = eventProduct.Product.index, eIndex = eventProduct.Event.index } into temp
         from eventProduct in temp.DefaultIfEmpty()
         select new { 
             Product = product,
             EventProduct = eventProduct
         };
var results = query2.ToList();
foreach(var r in results)
{
      r.EventProduct = r.EventProduct != null 
            ? r.EventProduct 
            : new EventProduct { .... set default values}
}