实体框架核心从SQL到一对多的映射

时间:2019-08-11 21:15:52

标签: c# entity-framework-core asp.net-core-2.0

我有一个简单的Blog数据库结构,其中包含4个表:

DB Diagram

每个表中的一些示例数据如下:

博客表:

Blog table

帖子表:

Post table

标签表:

Tags table

PostTags表:

PostTags table

我有这个SQL脚本。

SELECT b.Id, 
       b.Title, 
       p.Id, 
       p.Title, 
       p.PostContent, 
       t.Name
FROM dbo.Blogs b
     JOIN Posts p ON p.BlogId = b.Id
     LEFT JOIN PostTags pt ON pt.PostId = p.Id
     LEFT JOIN Tags t ON t.Id = pt.TagId
WHERE b.Id = 1
      AND p.IsDeleted = 0;

有几种方法可以使用EF Core执行此脚本。一种是直接从代码中调用此SQL脚本。创建存储过程或查看并从代码中调用它的另一种方法。

假设我具有以下类来映射EF Core执行的SQL脚本的结果。

public partial class Blog
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Slogan { get; set; }

    public virtual ICollection<Post> Posts { get; set; }
}

public partial class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; }
    public string Title { get; set; }
    public string PostContent { get; set; }

    public virtual ICollection<PostTag> PostTags { get; set; }
}

public partial class Tag
{
    public int Id { get; set; }
    public string Name { get; set; }

    public virtual ICollection<PostTag> PostTags { get; set; }
}   

public partial class PostTag
{
    public int Id { get; set; }
    public int PostId { get; set; }
    public int TagId { get; set; }

    public virtual Post Post { get; set; }
    public virtual Tag Tag { get; set; }
}     

这是控制器中的方法:

[Route("posts/{blogId}")]
[HttpGet]
public async Task<IActionResult> GetBlogPosts(int blogId)
{
        string sql = @"
                        SELECT b.Id, 
                            b.Title, 
                            p.Id, 
                            p.Title, 
                            p.PostContent, 
                            t.Id,
                            t.Name
                        FROM dbo.Blogs b
                            JOIN Posts p ON p.BlogId = b.Id
                            LEFT JOIN PostTags pt ON pt.PostId = p.Id
                            LEFT JOIN Tags t ON t.Id = pt.TagId
                        WHERE b.Id = 1
                            AND p.IsDeleted = 0;
                ";

    // this is not working
    var result = db.Blogs.FromSql(sql).ToList().FirstOrDefault(); 

    return Ok(result);
}

如何将SQL脚本的结果映射到Blog对象,以便获得以下结果?

{
    "Blog": [
        {
            "Id": 1,
            "Title": "Another .NET Core Guy",
            "Posts": [
                {
                    "Id": 1,
                    "Title": "Post 1",
                    "PostContent": "Content 1 is about EF Core and Razor page",
                    "Tags": [
                        {
                            "Id": 1,
                            "Name": "Razor Page"
                        },
                        {
                            "Id": 2,
                            "Name": "EF Core"
                        }
                    ]
                },
                {
                    "Id": 2,
                    "Title": "Post 2",
                    "PostContent": "Content 2 is about Dapper",
                    "Tags": [
                        {
                            "Id": 3,
                            "Name": "Dapper"
                        }
                    ]
                },
                {
                    "Id": 4,
                    "Title": "Post 4",
                    "PostContent": "Content 4",
                    "Tags": [
                        {
                            "Id": 5,
                            "Name": "SqlKata"
                        }
                    ]
                }
            ]
        }
    ]
}

更新,2019年8月13日:

如EF Core Github页面https://github.com/aspnet/EntityFrameworkCore/issues/14525

所述,EF Core不支持这种功能。

4 个答案:

答案 0 :(得分:0)

如果EF Core的版本可以是2.1或更高版本,则可以在StringList类中使用DbQuery<ClassName> ClassName。然后,您可以调用DbContext或在数据库中创建视图,然后可以在var result = db.ClassName.FromSql(sql).ToList().FirstOrDefault();方法中分配视图。

您创建的课程应该代表您所拥有的视图。 我不知道ef core如何将您的SQL查询与包含列表然后包含下一个列表的示例模型一起解析。可能您必须在SQL查询中使用别名,例如OnModelCreatingBlogs as B,但是您必须尝试一下并进行实验。

有关DbQuey的更多信息,您可以在https://docs.microsoft.com/en-us/ef/core/modeling/query-types

上的Microsoft文档中阅读。

答案 1 :(得分:0)

想写这个作为评论,但是由于我是新来的,所以我不能这样做:

编辑:我写这个答案只是因为您说由于.Include()和.ThenInclude()的行为,您必须用普通的SQL编写它。

如果您选择了所有内容并且在进一步的过程中不需要它,则不需要.Include()或.ThenInclude()语句(就像您所做的那样,选择并且不再对其进行任何操作)。

我看到的一种方法是通过将实体添加为navigationproperty并执行两个这样的数据库请求来在不使用linq的情况下进行两次选择:

public partial class Post
{

    public int Id { get; set; }
    public int BlogId { get; set; }
    public Blog Blog { get; set; }
    public string Title { get; set; }
    public string PostContent { get; set; }

    public virtual ICollection<PostTag> PostTags { get; set; }
}

var result = await db.Posts.Where(x => x.BlogId == blogId && x.Blog. ... whatever you may also need).Select(x => whatever you need from here).ToArrayAsync()

以及对上述其他实体的另一个查询,只需加入一个新实体

或使用linq进行操作,如ASP.NET Core & EntityFramework Core: Left (Outer) Join in Linq

希望这看起来合理

答案 2 :(得分:0)

Fromsql获得的结果是一个平坦的关系,而不是嵌套的。如果您坚持使用它来获取具有嵌套关系的数据,则有两种方法:1.您可以自定义一个sql脚本来实现; 2.您可以使用Include方法在EF Core中加载相关数据  并选择具有嵌套关系的所需属性,并用查询结果填充该属性。

以下是使用Include方法在EF Core中加载相关数据的工作演示,您可以参考:

Post模型和Tag模型之间存在多对多关系,您应按以下方式定义它们:

 public class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; }
    public string Title { get; set; }
    public string PostContent { get; set; }
    public bool IsDeleted { get; set; }


    public  ICollection<PostTag> PostTags { get; set; }
}

public class PostTag
{
    //public int Id { get; set; }

    public int PostId { get; set; }
    public int TagId { get; set; }

    public virtual Post Post { get; set; }
    public virtual Tag Tag { get; set; }
}

 public class Tag
{
    public int Id { get; set; }
    public string Name { get; set; }

    public bool IsDeleted { get; set; }

    public virtual ICollection<PostTag> PostTags { get; set; }
}

DbContext:

public class TestDbContext:DbContext
{
    public TestDbContext (DbContextOptions<TestDbContext> options):base(options)
    { }


    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }
    public DbSet<Tag> Tags { get; set; }


    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<PostTag>()
        .HasKey(pt => new { pt.PostId, pt.TagId });

        modelBuilder.Entity<PostTag>()
            .HasOne(pt => pt.Post)
            .WithMany(p => p.PostTags)
            .HasForeignKey(pt => pt.PostId);

        modelBuilder.Entity<PostTag>()
            .HasOne(pt => pt.Tag)
            .WithMany(t => t.PostTags)
            .HasForeignKey(pt => pt.TagId);
    }
}

控制器:

[Route("posts/{blogId}")]
    [HttpGet]
    public async Task<IActionResult> GetBlogPosts(int blogId)
    {
        var blogs = db.Blogs
            .Where(b => b.Id == blogId)
            .Include(b => b.Posts)
                .ThenInclude(p => p.PostTags).ThenInclude(pt => pt.Tag)
            .Select(b=>new {
                Id=b.Id,
                Title=b.Title,
                Posts= b.Posts.Select(p => new {
                    Id=p.Id,
                    Title=p.Title,
                    PostContent=p.PostContent,
                    Tags =p.PostTags.Select(pt=> new {
                        Id=pt.Tag.Id,
                        Name=pt.Tag.Name,
                    })
                })
            });

        return Json(blogs);
    }

参考:

https://docs.microsoft.com/en-us/ef/core/modeling/relationships#many-to-many

https://docs.microsoft.com/en-us/ef/core/querying/related-data

答案 3 :(得分:0)

鉴于您需要优化的查询,并且还需要执行其他where,因此您可能必须依靠linq-to-sql样式来构建查询,并映射到自己获得理想的结果:

var query = (
from b in db.Blogs
join p in db.Posts on b.Id equals p.BlogId
from pt in db.PostTag.Where(posttag => posttag.PostId == p.Id).DefaultIfEmpty() //LEFT OUTER JOIN
from t in db.Tags.Where(tag => tag.Id == pt.TagId).DefaultIfEmpty() //LEFT OUTER JOIN
where (...) //Your additional conditions
select new 
{
    BlogId = b.Id,
    BlogTitle = b.Title,
    PostId = p.Id,
    PostTitle = p.Title,
    p.PostContent,
    TagId = (int?) t.Id,
    TagName = t.Name
}).ToList();

从现在开始,您可以自己编写GroupBy语句,也可以使用一些插件。

相关问题