如何将此Linq代码转换为内联SQL

3
我应该如何将此查询转换为内联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将它放入自定义类中呢?


你使用的是哪种 SQL?如果你使用的是 T-SQL,那么答案就不会和使用 PL/SQL 时一样。 - ALOToverflow
5个回答

1
如果您的意思是帖子和帖子评论表之间存在关系,并且两个表中都有重复的列,一个评论可能与多个帖子相关,那么您可以轻松地创建两个命令:
-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
 } 
}

如果这不是你的情况,那么你能否尝试澄清一下你的数据库结构?

对不起,我已经编辑了查询。是的,PostComments与每篇文章(Post)相关。就像评论与SO上的答案相关一样。 - Luke101
如果您只获取一个帖子及其所有评论,那么您可以创建一个命令并执行这两个SQL语句,并使用DataReader的ReadNextResult()方法来填充评论。 - Mostafa Elmoghazi

1

简要说明

你的问题中看起来比较棘手的部分似乎是如何以与 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_idC.idC.title。如果您的PostType类以相同的方式表示一个条目,则这个问题很容易解决(我并不提倡这样的设计;我只是评论它如何影响它的填充方式)。类中的层次关系改变了事情的性质。

此外,您的代码显示了一个List<PostType>,但列表并非必需,因为始终只会有一个PostType,因为您正在过滤post_id。如果删除该条件,那么您可能会得到具有不同PostIds但满足其他条件的多个匹配项。如果是这种情况,则下面的代码需要更改。

话虽如此,让我们进入一些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更方便。

您可以通过使用以下任一DataContext选项从代码中获取已翻译的T-SQL语句:

注意:我说过这是一种捷径。了解SQL是很好的,但要明确的是,我并不建议盲目使用生成的输出。尽管有时SQL可能与您预期的不同,但它仍然提供了一个不错的起点。如果需要,您可以进行微调。

使用以下任一方法并复制结果 - 您将需要它进行第二步

示例DataContext.GetCommand()用法:

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

获取结果的方法有三种:设置断点并复制其值、在即时窗口中查看或在某个位置显示(Console.WriteLine等)。

例如 DataContext.Log 的用法:

context.Log = Console.Out;

在该上下文中执行的查询将其SQL语句转储到控制台窗口。您可以从那里复制它。要将它们转储到其他位置,例如调试输出窗口,请查看以下链接:

第二步:使用手头的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 查询从未请求计数,但结果请求联接中使用的相等 ID 的计数。还要注意,列没有别名。您将基于它们的实际名称引用列(例如,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,并显示使用的lambda表达式(上面的查询语法显示为lambda /扩展等效)。除此之外,它还是一个用于通用C#/VB.NET(包括LINQ to Objects/XML)和SQL编码的优秀工具,具有数据库支持和更多功能。

以下是LINQPad的微小截图,显示了一些早期讨论的主题:

LINQPad

我不想占用更多的页面空间,所以点击此处查看原始尺寸的图片


如果你看到这里了,恭喜你!:)


这真是太棒了..不知道该感谢你多少。我希望我能多次点赞..感谢您如此详细地解释。我已经下载了linqpad,它帮了我很大的忙。我希望我能雇用您来完成我的项目。再次感谢。 - Luke101

1
使用 SQL Profiler 捕获生成的查询。将其复制到新的存储过程并修复输入参数。创建(保存)并使用它 :)

0
我没法测试这个,但是大致上的内容是:
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

我为了可读性而大写关键字,但对于您使用的数据库,您可能需要更改它。


0
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获取数据,然后将其加载到具有自定义类的列表中


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接