Ядро EF: LINQ выбирает «многие-ко-многим-многим» на «многие-ко-многим»

#c# #sql #linq #.net-core #ef-core-2.1

#c# #sql #linq #.net-core #ef-core-2.1

Вопрос:

У меня есть таблица «Ссылка» и таблица «Статья», где статья ссылается на другие статьи.

У меня есть простые ссылки, такие как: A -> B

SQL:

 select ab.*
from Article a
inner join Reference ab on ab.ArticleFromId = a.Id
inner join Article b on b.Id = ab.ArticleToId
where a.ArticleNo = "1234"
  

C# LINQ:

 _context.Reference
   .Where(r => r.ArticleFromNavigation.ArticleNo.Equals("1234"));
  

У меня также есть цепочки ссылок, такие как: A -> B -> C
(Давайте предположим, что в цепочке не более 3 статей)

SQL:

 select ab.ArticleFromId, bc.ArticleToId
from Article a
inner join Reference ab on ab.ArticleFromId = a.Id
inner join Article b on b.Id = ab.ArticleToId
inner join Reference bc on bc.ArticleFromId = b.Id
inner join Article c on c.Id = bc.ArticleToId
where a.ArticleNo = "1234"
  

В SQL это просто, так как результат просто умножается на дополнительные объединения, но я не знаю, как написать это в LINQ.

Я хочу, чтобы это было что-то вроде этого (что не сработает):

 _context.Reference
   .Where(r => r.ArticleFromNavigation.ArticleNo.Equals("1234"))
   .Select(r => new Reference
   {
       ArticleFromNavigation = r.ArticleFromNavigation, //this is article "A"
       ArticleToNavigation = r.ArticleToNavigation.ReferenceArticleToNavigations //this wont work as it's a iCollection
   }).AsNoTrackable();
  

Здесь я хочу новые результаты типа «Ссылка» для «A -> C».
Я предполагаю, что я должен включить / затем включить / присоединиться / выбрать / выделить много (?) коллекции перед разделом «новая ссылка», но я понятия не имею.

Есть ли какой-либо способ, которым я могу это заархивировать?

Комментарии:

1. Не могли бы вы, пожалуйста, добавить примеры данных в статьи и справочные таблицы?

Ответ №1:

Ну, вы можете сделать это точно так же, как в SQL, но вместо соединений используйте свойства навигации.

Я буду использовать синтаксис запроса LINQ, потому что он лучше показывает сходство, а также синтаксис метода довольно запутанный и трудный для чтения для запросов такого типа:

 from a in _context.Article
from ab in a.ReferenceArticleFromNavigations
let b = ab.ArticleToNavigation
from bc in b.ReferenceArticleFromNavigations
let c = bc.ArticleToNavigation
where a.ArticleNo = "1234"
select new Reference
{
    ArticleFromNavigation = a,
    ArticleToNavigation = c,
}
  

let Инструкции не являются обязательными (вы можете напрямую использовать свойство навигации по ссылкам), я включил их только для того, чтобы сделать запрос LINQ ближе к запросу SQL.

На самом деле эквивалент метода в этом случае не так уж плох — сгладьте несколько уровней с вложенными SelectMany и спроецируйте пару (верхний, нижний), используя SelectMany перегрузку, позволяющую это:

 _context.Article
    .Where(a => a.ArticleNo = "1234")
    .SelectMany(a => a.ReferenceArticleFromNavigations
        .SelectMany(ab => ab.ArticleToNavigation.ReferenceArticleFromNavigations)
        // include as many `SelectMany` like the above as you wish until you hit the desired level of nesting
        .Select(bc => bc.ArticleToNavigation),
    (a, c) => new Reference
    {
        ArticleFromNavigation = a,
        ArticleToNavigation = c,
    });
  

Комментарии:

1. Большое спасибо, это помогло мне. .SelectMany() является ли синтаксический эквивалент на основе метода «from» в синтаксисе на основе запроса, верно?

2. Действительно. В основном сглаживание на несколько вложенных SelectMany и проецирование пары (верхняя, нижняя).

3. Спасибо даже за добавление синтаксиса на основе метода. Идеальный ответ!

Ответ №2:

Я смоделировал базу данных как классы, чтобы получить правильный синтаксис. Смотрите код ниже :

 using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication107
{
    class Program
    {
        static void Main(string[] args)
        {
            Context _context = new Context();
            string ArticleNo = "1234";

            var results = (from a in _context.article.Where(x => x.Id == ArticleNo)
                           join ab in _context.reference
                              .Where(x => (x.ArticleFromId == x.ArticleToId))
                              on a.Id equals ab.ArticleFromId
                           select new { a = a, ab = ab }
                          ).Select(r => new Reference()
                          {
                              ArticleFromNavigation = r.a,
                              ArticleToNavigation = r.a.ReferenceArticleToNavigations.ToList() 
                          }).ToList();
        }
    }

    public class Context
    {
        public List<Reference> reference { get; set; }
        public List<Article> article { get; set; }
    }
    public class Reference
    {
        public string ArticleFromId { get; set; }
        public string ArticleToId { get; set; }
        public Article ArticleFromNavigation { get; set; }
        public List<string> ArticleToNavigation { get; set; }
    }
    public class Article
    {
        public string Id { get; set; }
        public List<string> ReferenceArticleToNavigations { get; set; }
    }

}