Как я могу преобразовать этот код Linq для встроенного SQL - PullRequest
3 голосов
/ 17 января 2010

Как мне преобразовать этот запрос во встроенный sql или хранимую процедуру?

var a = from arow in context.post
where arow.post_id == id && arow.post_isdeleted == false
select new
{
     arow.post_id,
     PostComments = from c in context.comment
                    where c.CommentPostID == arow.post_id
                    select new
                    {
                        c.id,
                        c.title     
                    }
}


List<PostType> pt;
foreach (var s in a)
{
     pt = new PostType();
     pt.PostID = s.post_id;

     //how would I use ADO.NET to put this in a custom class?
     foreach(var ctl in s.PostComments)
     {
         ctl.Title = ctl.title;
         pt.CommentT.Add(ctl);
     }
     ptl.Add(pt);
}

Как только встроенный запрос будет выполнен, как бы я поместил информацию в пользовательский класс? PostComments - это подзапрос - так как бы мне использовать ADO.NET, чтобы поместить его в пользовательский класс?

Ответы [ 5 ]

1 голос
/ 21 января 2010

Краткое объяснение

Кажется, что часть вашего вопроса, которая может показаться сложной, состоит в том, как заполнить пользовательский класс таким же образом, как запрос LINQ to SQL ("L2S" с этого момента), для анонимного класса.

Основываясь на вашем foreach цикле, я предполагаю, что ваши пользовательские классы похожи на эти:

public class PostType
{
    public int PostId { get; set; }
    public List<PostComment> PostComments { get; set; }
}
public class PostComment
{
    public int CommentId { get; set; }
    public string Title { get; set; }
}

Запрос LINQ должен быть эквивалентен этому выражению T-SQL:

SELECT P.post_id, C.id, C.title 
FROM post As P, comment As C
WHERE
    P.post_id = @PostId
    AND P.post_isdeleted = 0  -- 0 is false
    AND C.CommentPostID = P.post_id

В отличие от версии L2S (дополнительную информацию см. В разделе «Подробное объяснение» ниже), этот оператор возвращает плоский результат, где каждая строка содержит P.post_id, C.id и C.title. Если бы ваш класс PostType представлял запись таким же образом, это было бы легко решить (я не защищаю такой дизайн; я просто комментирую, как дизайн меняет то, как он заполняется). Иерархические отношения в классах меняют вещи.

Кроме того, ваш код показал List<PostType>, но список не требуется, поскольку всегда будет один PostType, потому что вы фильтруете по post_id. Если это условие будет удалено, , а затем вы можете получить несколько совпадений с разными идентификаторами PostId, если выполняются другие условия. В этом случае приведенный ниже код необходимо будет изменить.

Тем не менее, давайте перейдем к некоторому ADO.NET и заполним классы с помощью SqlDataReader.

int postIdInput = 42; // desired post_id to search for

// PostType delcared prior to getting the results
PostType postType = new PostType()
{
    PostId = postIdInput,
    PostComments = new List<PostComment>()
};

 // Database interaction starts here...
 // updated SQL statement to use column name aliases for clarity when used by the SqlDataReader
 string sqlStatement = @"SELECT P.post_id As PostId, C.id As CommentId, C.title As Title
                         FROM post As P, comment As C
                         WHERE
                             P.post_id = @PostId
                             AND P.post_isdeleted = 0  -- 0 is false
                             AND C.CommentPostID = P.post_id";

 string sqlConnectionString = "..."; // whatever your connection is... probably identical to your L2S context.Connection.ConnectionString
 using (SqlConnection conn = new SqlConnection(sqlConnectionString))
 {
     conn.Open();
     SqlCommand command = new SqlCommand(sqlStatement, conn);
     command.Parameters.AddWithValue("@PostId", postIdInput); // use Parameters.Add() for greater specificity

    SqlDataReader reader = command.ExecuteReader();
    while (reader.Read())
    {
        // postId was set based on input, but could be set here as well although it would occur repeatedly
        // if desired, uncomment the 2 lines below and there's no need to initialize it earlier (it'll be overwritten anyway)
        //int postId = Int32.Parse(reader["PostId"].ToString());
        //postType.PostId = postId;
        int commentId = Int32.Parse(reader["CommentId"].ToString());
        string title = reader["Title"].ToString();

        // add new PostComment to the list
        PostComment postComment = new PostComment
        {
            CommentId = commentId,
            Title = title
        };
        postType.PostComments.Add(postComment);
    }

    // done! postType is populated...
}

// use postType...

Это должно охватывать ваш сценарий. Однако, для более подробного ответа, продолжайте читать!


Подробное объяснение (он же "Научите человека ловить рыбу ..." )

Допустим, вы не могли понять, как получить эквивалентный оператор SQL. Хотя для этого есть разные способы, я сосредоточусь на том факте, что вы используете L2S, и рассмотрю некоторые связанные варианты.

Шаг 1. Преобразование запроса LINQ в SQL (путем «обмана»)

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

Вы можете получить переведенный оператор T-SQL из своего кода, используя любую из следующих опций DataContext:

ПРИМЕЧАНИЕ: Я сказал, что это был ярлык. Знание SQL полезно знать, и, чтобы быть ясным, я не предлагаю использовать сгенерированный вывод вслепую. Конечно, SQL иногда может отличаться от того, что вы ожидаете, тем не менее, он обеспечивает достойную отправную точку. При необходимости вы можете настроить его.

Используйте любой из этих методов и скопируйте результат - он понадобится вам для Шаг 2 .

Пример использования DataContext.GetCommand ():

var query = /* your L2S query here */;
string sqlStatement = context.GetCommand(query).CommandText;    // voila!

Чтобы получить результат, либо установите точку останова и скопируйте ее значение, проверьте его в «Немедленном окне» или где-нибудь отобразите (Console.WriteLine и т.

Пример использования DataContext.Log:

context.Log = Console.Out;

Запросы, выполненные в этом контексте, будут выгружать свои операторы SQL в окно консоли. Вы можете скопировать его оттуда. Чтобы вывести их в другое место, например, в окно вывода отладки, проверьте следующие ссылки:

Шаг 2: Имея в руках оператор SQL, используйте его в ADO.NET

Теперь, когда у вас есть оператор SQL, мы можем использовать его в ADO.NET. Конечно, вы также можете использовать хранимую процедуру, и ее не составит труда заменить.

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

SELECT [t0].[post_id], [t1].[id], [t1].[title], (
 SELECT COUNT(*)
 FROM [comment] AS [t2]
 WHERE [t2].[id] = [t0].[post_id]
 ) As [value]
FROM [post] As [t0]
LEFT OUTER JOIN [comment] As [t1] ON [t1].[CommentPostID] = [t0].[post_id]
WHERE ([t0].[post_id] = @p0) AND ([t0].[post_isdeleted] = 0)
ORDER BY [t0].[post_id], [t1].[id]

Обратите внимание на встроенный SELECT COUNT (*)? Запрос L2S никогда не запрашивал количество, но результат запрашивает количество равных идентификаторов, используемых в соединении. Также обратите внимание, что для столбцов нет псевдонимов. Вы должны ссылаться на столбцы на основе их фактических имен (т. Е. post_id против PostId). Кроме того, параметры SQL называются @ p0 ... @ pn и применяется порядок сортировки по умолчанию. Вы можете скопировать / вставить это в SqlDataReader, использованный ранее, но вам нужно будет переименовать столбцы и параметры для соответствия.

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

SELECT [P].[post_id] As PostId, [C].[id] As CommentId, [C].[title] As Title--, (
-- SELECT COUNT(*)
-- FROM [comment] AS [t2]
-- WHERE [t2].[id] = [t0].[post_id]
-- ) As [value]
FROM [post] As [P]
LEFT OUTER JOIN [comment] As [C] ON [C].[CommentPostID] = [P].[post_id]
WHERE ([P].[post_id] = @PostId) AND ([P].[post_isdeleted] = 0)
--ORDER BY [t0].[post_id], [t1].[id]

Выше можно теперь использовать с SqlDataReader от ранее.

Более прямой запрос мог бы быть сгенерирован, если бы запрос L2S был в формате SelectMany , например:

var query = from arow in context.post
            from c in context.comment
            where arow.post_id == id && arow.post_isdeleted == false
                  && c.CommentPostID == arow.post_id
            select new
            {
                arow.post_id,
                c.id,
                c.title    
            };

Запрос SelectMany L2S генерирует оператор SQL, подобный следующему:

SELECT [t0].[post_id], [t1].[id], [t1].[title]
FROM [post] As [t0], [comment] As [t1]
WHERE ([t0].[post_id] = @p0) AND ([t0].[post_isdeleted] = 0)
      AND ([t1].[CommentPostID] = [t0].[post_id])

LINQPad

Хотя это подробное объяснение может показаться ошеломляющим, существует простой способ получить эту информацию у вас под рукой. Если вы еще не попробовали LINQPad , тогда я настоятельно рекомендую - это тоже бесплатно! LINQPad покажет вам результаты ваших запросов L2S, будет иметь вкладку SQL для просмотра сгенерированного SQL, а также отобразит используемое лямбда-выражение (приведенный выше синтаксис запроса показан как лямбда / эквивалент расширения). Кроме того, это отличный инструмент для C # / VB.NET общего назначения (включая LINQ to Objects / XML), а также для кодирования SQL с поддержкой баз данных и многим другим.

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

LINQPad

Я не хотел занимать больше страниц, чем у меня уже есть, поэтому нажмите здесь, чтобы посмотреть изображение в оригинальном размере .


Если вы сделали это так далеко, поздравляю! :)

1 голос
/ 20 января 2010

Используйте SQL Profiler для перехвата сгенерированного запроса. Скопируйте в новую хранимую процедуру и восстановите входные параметры. Создайте (сохраните) и используйте его:)

1 голос
/ 17 января 2010

Если вы имеете в виду, что существует связь между таблицами Posts и PostComments и в обеих таблицах есть повторяющиеся столбцы, и один комментарий может относиться к более чем одному сообщению, поэтому Вы можете легко создать две команды:

-Select * from Posts where post_Id = id AND IsDeleted = 0;
-Select * from Postcomments where id = cid;

и затем выполните их с помощью Sql Command Adapters для двух таблиц данных. а затем:

foreach(DataRow dr in PostsTable.Rows)
{
 //Fill the Post Custom class
 SecondTable.DefaultView.RowFilter = string.Format("PostID = {0}",dr["postID"]);
 foreach(DataRow r in SecondTable.Rows)
 {
  //Fill the Comments Custom class
 } 
}

Если это не ваш случай, можете ли вы попытаться уточнить структуру вашей базы данных?

0 голосов
/ 21 января 2010
select post_id, id, title from postcomments pc
where post_id = @id and exists(
    select post_id form post p where p.post_id = pc.post_id and isdeleted = false
)

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

0 голосов
/ 17 января 2010

Я не могу проверить это, но что-то вроде:

SELECT 
    p.post_id
    c.id,
    c.title
FROM 
    post p 
WHERE 
    p.id == 'id' and 
    isdeleted = false
INNER JOIN comment c ON c.commentpostid = p.post_id

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

...