Присоединиться / Где с LINQ и Lambda

458

У меня проблемы с запросом, написанным на LINQ и Lambda. Пока что я получаю много ошибок, вот мой код:

int id = 1;
var query = database.Posts.Join(database.Post_Metas,
                                post => database.Posts.Where(x => x.ID == id),
                                meta => database.Post_Metas.Where(x => x.Post_ID == id),
                                (post, meta) => new { Post = post, Meta = meta });

Я новичок в использовании LINQ, поэтому я не уверен, что этот запрос правильный.

Дэвид
источник
11
Что вы пытаетесь достичь?
Герман Родригес
4
что вы хотите, чтобы запрос делал в предложении?
охотник
6
Ваши ключевые селекторы путь слишком сложен. Если вы хотите выбрать по id, просто x => x.ID.
Эрик Липперт
1
Я хотел получить пост из базы данных и метаданные для этого поста.
Дэвид

Ответы:

1057

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

var id = 1;
var query =
   from post in database.Posts
   join meta in database.Post_Metas on post.ID equals meta.Post_ID
   where post.ID == id
   select new { Post = post, Meta = meta };

Если вы действительно застряли на использовании лямбда-выражений, ваш синтаксис немного нарушен. Вот тот же запрос, использующий методы расширения LINQ:

var id = 1;
var query = database.Posts    // your starting point - table in the "from" statement
   .Join(database.Post_Metas, // the source table of the inner join
      post => post.ID,        // Select the primary key (the first part of the "on" clause in an sql "join" statement)
      meta => meta.Post_ID,   // Select the foreign key (the second part of the "on" clause)
      (post, meta) => new { Post = post, Meta = meta }) // selection
   .Where(postAndMeta => postAndMeta.Post.ID == id);    // where statement
Даниэль Шаффер
источник
10
@Emanuele Greco, относительно вашего редактирования: «Равенство полей ID установлено в условии JOIN; вам не нужно использовать предложение WHERE!»: Предложение WHERE не проверяет равенство между полями идентификатора, оно проверяет равенство между идентификатором записи столбец и параметр id объявлены вне запроса.
Даниэль Шаффер
9
Потрясающий кусок lambdaи цитата проста в использовании и понимании
Петр Кула
1
офигенный пример
игрушка
1
Иногда объяснения лямбды пишутся в лямбде. Объяснил хорошо.
Pinch
80

Вы можете пойти двумя путями с этим. Используя LINQPad (неоценимо, если вы новичок в LINQ) и фиктивную базу данных, я построил следующие запросы:

Posts.Join(
    Post_metas,
    post => post.Post_id,
    meta => meta.Post_id,
    (post, meta) => new { Post = post, Meta = meta }
)

или

from p in Posts
join pm in Post_metas on p.Post_id equals pm.Post_id
select new { Post = p, Meta = pm }

В этом конкретном случае, я думаю, что синтаксис LINQ чище (я переключаюсь между двумя в зависимости от того, какой из них легче всего читать).

Однако я хотел бы отметить, что если в вашей базе данных есть соответствующие внешние ключи (между post и post_meta), то вам, вероятно, не понадобится явное соединение, если вы не пытаетесь загрузить большое количество записей. , Ваш пример показывает, что вы пытаетесь загрузить один пост и его метаданные. Предполагая, что для каждого поста существует много записей post_meta, вы можете сделать следующее:

var post = Posts.Single(p => p.ID == 1);
var metas = post.Post_metas.ToList();

Если вы хотите избежать проблемы n + 1, то вы можете явно указать LINQ to SQL, чтобы загружать все связанные элементы за один раз (хотя это может быть сложной темой, когда вы более знакомы с L2S). В приведенном ниже примере говорится, что «когда вы загружаете сообщение, также загружайте все его записи, связанные с ним, через внешний ключ, представленный свойством Post_metas»:

var dataLoadOptions = new DataLoadOptions();
dataLoadOptions.LoadWith<Post>(p => p.Post_metas);

var dataContext = new MyDataContext();
dataContext.LoadOptions = dataLoadOptions;

var post = Posts.Single(p => p.ID == 1); // Post_metas loaded automagically

Можно сделать много LoadWithвызовов на одном наборе DataLoadOptionsдля одного и того же типа или для множества разных типов. Если вы делаете это много, вы, возможно, просто захотите рассмотреть кеширование.

Дамиан Пауэлл
источник
1
LinqPad и CRM 2016 ?
Kiquenet
50

У Даниэля есть хорошее объяснение синтаксических взаимосвязей, но я собрал этот документ для своей команды, чтобы сделать его немного более простым для понимания. Надеюсь, это поможет кому-товведите описание изображения здесь

Talspaugh27
источник
Это не сработает, когда вы просто имеете дело со списком значений, как мы здесь. У объекта нет свойства id.
Talspaugh27
Я нашел это действительно полезным, но я получил ошибку, которая потребовала, чтобы я добавил колонку присоединения. Также, глядя на ответ, опубликованный @Mark Byers, в столбце присоединения есть Post_IDполе во втором псевдониме meta => meta.Post_ID. В примере на этом рисунке g.idчасть исходного оператора select JOIN gStatus g on g.idне реплицируется в конечном лямбда-выражении.
SausageFingers
3
Я не пытался опубликовать это как ссылку на фактический linq, требуемый для ответа, опубликованного OP, это было скорее ссылкой на то, как перевести SQL в формат Linq, поэтому мои входные данные немного отличались от исходного вопроса. Если бы я создал класс для значений gStatus, я бы поместил в него свойство id, а затем, да, оно бы соединилось с g => g.id. Я использовал список значений, чтобы сделать код как можно более простым.
Talspaugh27
@ Talspaugh27 Так почему в запросе SQL он присоединяется к gStatus на g.id? Это ошибка или намеренная?
Drammy
@Drammy в таблице sql каждый столбец должен иметь имя, поэтому, поскольку это была таблица из 1 столбца, строго для хранения этих идентификаторов, я просто использовал столбец с именем id, у List <int> такой проблемы нет. Если бы я настроил его как таковой, public class IdHolder{ int id } то использовал бы этот объект в gStatus, List<IdHolder> gStatus = new List<IdHolder>(); gStatus.add(new IdHolder(){id = 7}); gStatus.add(new IdHolder(){id = 8}); тогда он изменил бы Linq, чтобы быть t =>t.value.TaskStatusId, g=>g.id , это изменение имеет смысл?
Talspaugh27
37

Ваши ключевые селекторы неверны. Они должны взять объект типа рассматриваемой таблицы и вернуть ключ для использования в соединении. Я думаю, что вы имеете в виду это:

var query = database.Posts.Join(database.Post_Metas,
                                post => post.ID,
                                meta => meta.Post_ID,
                                (post, meta) => new { Post = post, Meta = meta });

Вы можете применить предложение where позже, а не как часть ключевого селектора.

Марк Байерс
источник
9

Публикация, потому что, когда я начал LINQ + EntityFramework, я смотрел на эти примеры в течение дня.

Если вы используете EntityFramework, и у вас есть свойство навигации, названное Metaв вашем Postобъекте модели, это очень просто. Если вы используете сущность и у вас нет этого свойства навигации, чего вы ждете?

database
  .Posts
  .Where(post => post.ID == id)
  .Select(post => new { post, post.Meta });

Если вы сначала делаете код, вы должны настроить свойство следующим образом:

class Post {
  [Key]
  public int ID {get; set}
  public int MetaID { get; set; }
  public virtual Meta Meta {get; set;}
}
Энди V
источник
5

Я сделал что-то вроде этого;

var certificationClass = _db.INDIVIDUALLICENSEs
    .Join(_db.INDLICENSECLAsses,
        IL => IL.LICENSE_CLASS,
        ILC => ILC.NAME,
        (IL, ILC) => new { INDIVIDUALLICENSE = IL, INDLICENSECLAsse = ILC })
    .Where(o => 
        o.INDIVIDUALLICENSE.GLOBALENTITYID == "ABC" &&
        o.INDIVIDUALLICENSE.LICENSE_TYPE == "ABC")
    .Select(t => new
        {
            value = t.PSP_INDLICENSECLAsse.ID,
            name = t.PSP_INDIVIDUALLICENSE.LICENSE_CLASS,                
        })
    .OrderBy(x => x.name);
Mahib
источник
4

Это может быть что-то вроде

var myvar = from a in context.MyEntity
            join b in context.MyEntity2 on a.key equals b.key
            select new { prop1 = a.prop1, prop2= b.prop1};
pepitomb
источник
1

1 равняется 1 разному соединению таблицы

var query = from post in database.Posts
            join meta in database.Post_Metas on 1 equals 1
            where post.ID == id
            select new { Post = post, Meta = meta };
mtngunay
источник
1

Этот запрос linq должен работать для вас. Он получит все сообщения, которые имеют мета-пост.

var query = database.Posts.Join(database.Post_Metas,
                                post => post.postId, // Primary Key
                                meta => meat.postId, // Foreign Key
                                (post, meta) => new { Post = post, Meta = meta });

Эквивалентный SQL-запрос

Select * FROM Posts P
INNER JOIN Post_Metas pm ON pm.postId=p.postId
Ахамед Ишак
источник
вы закрыли скобки, где после третьего параметра ... «перегрузка для Join не требует трех аргументов»
LastTribunal
3
Это совпадает с принятым ответом и 7 лет спустя -1
реггигитар