【问题标题】:How to select all parent objects into DataContext using single LINQ query?如何使用单个 LINQ 查询将所有父对象选择到 DataContext 中?
【发布时间】:2010-03-11 14:10:28
【问题描述】:

我正在寻找使用单个 SELECT 获取整个 LINQ 对象层次结构的特定问题的答案。

起初我试图使用 LoadOptions 填充尽可能多的 LINQ 对象,但 AFAIK 这种方法只允许使用 LoadWith 在一个查询中链接单个表。因此,我发明了一种解决方案,强制设置要获取列表的实体的所有父对象,尽管存在多个 SELECTS 进入数据库的问题 - 单个查询会导致在同一 LINQ 上下文中具有相同参数的两个 SELECTS .

对于这个问题,我已将此查询简化为流行的发票示例:

public static class Extensions
{
        public static IEnumerable<T> ForEach<T>(this IEnumerable<T> collection, Action<T> func)
        {
            foreach(var c in collection)
            {
                func(c);
            }
            return collection;
        }
}

public IEnumerable<Entry> GetResults(AppDataContext context, int CustomerId)
{
    return
    (
        from entry in context.Entries
        join invoice in context.Invoices on entry.EntryInvoiceId equals invoice.InvoiceId
        join period in context.Periods on invoice.InvoicePeriodId equals period.PeriodId
        // LEFT OUTER JOIN, store is not mandatory
        join store in context.Stores on entry.EntryStoreId equals store.StoreId into condStore
        from store in condStore.DefaultIfEmpty()
        where
            (invoice.InvoiceCustomerId = CustomerId)
        orderby entry.EntryPrice descending
        select new
        {
            Entry = entry,
            Invoice = invoice,
            Period = period,
            Store = store
        }
    ).ForEach(x =>
        {
            x.Entry.Invoice = Invoice;
            x.Invoice.Period = Period;
            x.Entry.Store = Store;
        }
    ).Select(x => x.Entry);
}

调用该函数并遍历结果集时,例如:

var entries = GetResults(this.Context);
int withoutStore = 0;
foreach(var k in entries)
{
    if(k.EntryStoreId  == null)
        withoutStore++;
}

对数据库的查询结果如下所示(获取单个结果):

SELECT
    [t0].[EntryId], 
    [t0].[EntryInvoiceId], 
    [t0].[EntryStoreId],
    [t0].[EntryProductId],
    [t0].[EntryQuantity],
    [t0].[EntryPrice],
    [t1].[InvoiceId], 
    [t1].[InvoiceCustomerId],
    [t1].[InvoiceDate],
    [t1].[InvoicePeriodId],
    [t2].[PeriodId], 
    [t2].[PeriodName], 
    [t2].[PeriodDateFrom],
    [t4].[StoreId],
    [t4].[StoreName]
FROM
    [Entry] AS [t0]
    INNER JOIN [Invoice] AS [t1] ON [t0].[EntryInvoiceId] = [t1].[InvoiceId]
    INNER JOIN [Period] AS [t2] ON [t2].[PeriodId] = [t1].[InvoicePeriodId]
LEFT OUTER JOIN (
    SELECT 1 AS [test], [t3].[StoreId], [t3].[StoreName]
    FROM [Store] AS [t3]
    ) AS [t4] ON [t4].[StoreId] = ([t0].[EntryStoreId])
WHERE (([t1].[InvoiceCustomerId]) = @p0)
ORDER BY [t0].[InvoicePrice] DESC
-- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [186]
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 3.5.30729.1

SELECT
    [t0].[EntryId], 
    [t0].[EntryInvoiceId], 
    [t0].[EntryStoreId],
    [t0].[EntryProductId],
    [t0].[EntryQuantity],
    [t0].[EntryPrice],
    [t1].[InvoiceId], 
    [t1].[InvoiceCustomerId],
    [t1].[InvoiceDate],
    [t1].[InvoicePeriodId],
    [t2].[PeriodId], 
    [t2].[PeriodName], 
    [t2].[PeriodDateFrom],
    [t4].[StoreId],
    [t4].[StoreName]
FROM
    [Entry] AS [t0]
    INNER JOIN [Invoice] AS [t1] ON [t0].[EntryInvoiceId] = [t1].[InvoiceId]
    INNER JOIN [Period] AS [t2] ON [t2].[PeriodId] = [t1].[InvoicePeriodId]
LEFT OUTER JOIN (
    SELECT 1 AS [test], [t3].[StoreId], [t3].[StoreName]
    FROM [Store] AS [t3]
    ) AS [t4] ON [t4].[StoreId] = ([t0].[EntryStoreId])
WHERE (([t1].[InvoiceCustomerId]) = @p0)
ORDER BY [t0].[InvoicePrice] DESC
-- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [186]
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 3.5.30729.1

问题是为什么有两个查询,我怎样才能在没有这种 hack 的情况下获取 LINQ 对象?

【问题讨论】:

    标签: c# sql-server linq


    【解决方案1】:

    为什么不多次拨打LoadWith

    DataLoadOptions 文档中它说:

    每次调用 LoadWith 都会检查循环是否有 [...]

    (在避免循环的部分。)

    【讨论】:

    • LoadWith 确实有效 - 我无法重复为什么我之前尝试使用多个 LoadWith 失败了,感谢您为我节省了不必要的徘徊。
    • 为了记录 - 为了使 LoadWith 正常工作,查询必须包括所有需要获取的表,即使它们由上下文的 LoadOptions 中的 LoadWith 指令指定,它也不会自动连接所有父表.
    【解决方案2】:

    嗯...我认为您正在遍历您的“可查询”两次:在 ForEach 扩展和“命令式” foreach () 块中... 您是否尝试过将 ForEach 的实现更改为...

        public static IEnumerable<T> ForEach<T>(this IEnumerable<T> collection, Action<T> func)
        {
            foreach (var c in collection)
            {
                func(c);
                yield return c;
            }
        }
    

    【讨论】:

      【解决方案3】:

      对于寻求此问题的确切解决方案的其他人,请考虑遵循用于在单个 SELECT 中获取 LINQ 对象层次结构的代码的缩短和工作版本。我已将 GetResults 函数的返回类型更改为 IQueryable,因为 LINQ 更改跟踪机制可以正确跟踪集合,从而允许使用集合中的更改更新数据库。

      public void InitContext(AppDataContext context)
      {
          DataLoadOptions options = new DataLoadOptions();
          options.LoadWith<Entry>(x => x.Invoice);
          options.LoadWith<Entry>(x => x.Store);
          options.LoadWith<Invoice>(x => x.Period);
          context.DataLoadOptions = options;
      }
      
      public IQueryable<Entry> GetResults(AppDataContext context, int customerId)
      {
          return
          (
              from entry in context.Entries
              join invoice in context.Invoices on entry.EntryInvoiceId equals invoice.InvoiceId
              join period in context.Periods on invoice.InvoicePeriodId equals period.PeriodId
              // LEFT OUTER JOIN, store is not mandatory
              join store in context.Stores on entry.EntryStoreId equals store.StoreId into condStore
              from store in condStore.DefaultIfEmpty()
              where
                  (invoice.InvoiceCustomerId == customerId)
              orderby entry.EntryPrice descending
              select entry
          );
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-05-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-03-03
        相关资源
        最近更新 更多