简体   繁体   English

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

[英]Entity Framework Core one to many mapping from SQL

I have this simple Blog database structure with 4 tables: 我有一个简单的Blog数据库结构,其中包含4个表:

数据库图

and some sample data in each table looks like this: 每个表中的一些示例数据如下所示:

Blogs table: 博客表:

博客表

Posts table: 帖子表:

发布表

Tags table: 标签表:

标签表

PostTags table: PostTags表:

PostTags表

And I have this SQL script. 我有这个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;

There are a few ways to execute this script with EF Core. 有几种方法可以使用EF Core执行此脚本。 One is to call this SQL script directly from the code. 一种是直接从代码中调用此SQL脚本。 Another way to create a stored procedure or view and call that from the code. 创建存储过程或查看并从代码调用它的另一种方法。

Supposed I have the followings classes to map the result of executed SQL script by EF Core. 假设我具有以下类来映射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; }
}     

This is a method in a controller: 这是控制器中的一种方法:

[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);
}

How I can map the result of the SQL script to the Blog object so that I can have the following 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"
                        }
                    ]
                }
            ]
        }
    ]
}

Update August 13, 2019: 2019年8月13日更新

EF Core does not support this kind of feature yet as it has been stated here on EF Core Github page https://github.com/aspnet/EntityFrameworkCore/issues/14525 EF Core目前尚不支持这种功能,如EF Core Github页面上所述:https://github.com/aspnet/EntityFrameworkCore/issues/14525

If the version of EF Core can be 2.1 or higher, you can use DbQuery<ClassName> ClassName in DbContext class. 如果EF Core的版本可以是2.1或更高版本,则可以在DbContext类中使用DbQuery<ClassName> ClassName Then you can call var result = db.ClassName.FromSql(sql).ToList().FirstOrDefault(); 然后您可以调用var result = db.ClassName.FromSql(sql).ToList().FirstOrDefault(); or create view in database and then you can assign in OnModelCreating method to view. 或在数据库中创建视图,然后可以在OnModelCreating方法中分配视图。

Your class which you create should represent your view which you have. 您创建的课程应该代表您所拥有的视图。 I don't know how ef core parses your SQL query with your sample model which contain a list and then next list. 我不知道ef core如何将您的SQL查询与包含一个列表然后包含下一个列表的示例模型一起解析。 Probably you have to use aliases in SQL query like Blogs as B , Posts as b.Posts , but you have to try it and experiment. 可能您必须在SQL查询中使用别名,例如Blogs as BPosts as b.Posts ,但是您必须尝试并试验一下。

More about DbQuey you can read in Microsoft documentation on https://docs.microsoft.com/en-us/ef/core/modeling/query-types 您可以在https://docs.microsoft.com/zh-cn/ef/core/modeling/query-types上的 Microsoft文档中阅读有关DbQuey的更多信息

wanted to write this as comment, but since i am new here i cannot do that: 想写这个作为评论,但是由于我是新来的,所以我不能这样做:

EDIT: i wrote this answer just because you said that you have to write it in plain SQL due to the behaviour of .Include() and .ThenInclude() 编辑:我写这个答案只是因为您说由于.Include()和.ThenInclude()的行为,您必须用纯SQL编写它

you don't need .Include() or .ThenInclude() statements if you select everything and don't need it in the further process (just like you did, select and don't do anything with it further). 如果您选择了所有内容并且在后续过程中不需要它,则不需要.Include()或.ThenInclude()语句(就像您所做的那样,选择并且不对其进行任何操作)。

one way i see would be do two select without linq by adding the entity as navigationproperty and doing two db requests like this: 我看到的一种方法是通过将实体添加为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()

and another query for the other entities like above and just join into a new entity 以及对上述其他实体的另一个查询,只是加入一个新实体

or do it with linq like shown in ASP.NET Core & EntityFramework Core: Left (Outer) Join in Linq 或使用linq进行操作,如ASP.NET Core和EntityFramework Core中所示:在Linq中为Left(外部)Join

hope this seems reasonable 希望这看起来合理

The result obtained by Fromsql is a flat relationship, not nested.If you insist on using this to get data with a nested relationship , there are two ways : 1. you could customize a sql script to implement ; Fromsql获得的结果是一个平面关系,而不是嵌套关系。如果您坚持使用此关系来获取具有嵌套关系的数据,则有两种方法:1.您可以自定义一个sql脚本来实现; 2. You could use Include method to load related data in EF Core and select the prorperties that you want with nested relationships and populate it with query results. 2.您可以使用Include方法将相关数据加载到EF Core中,并使用嵌套关系选择所需的属性,然后用查询结果填充该属性。

Here is a working demo on using Include method to load related data in EF Core , you could refer to : 这是一个使用Include方法在EF Core中加载相关数据的工作演示,您可以参考:

There is a many-to-many relationship between Post model and Tag model , you should define them like below : 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 : 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);
    }
}

Controller : 控制器:

[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);
    }

Reference : 参考:

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

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

Given you want an optimised query, and also the need to do additional where if required, you'll probably have to rely on linq-to-sql style to construct your query, and do the mapping to the desired results yourself: 鉴于你想要一个优化的查询,也需要做更多的where如果需要的话,你可能不得不依靠LINQ到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();

From here on, you can either write the GroupBy statements yourself, or use some plugins. 从这里开始,您可以自己编写GroupBy语句,也可以使用一些插件。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM