我有一个简单的Blog数据库结构,其中包含4个表:
每个表中的一些示例数据如下:
博客表:
帖子表:
标签表:
PostTags表:
我有这个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不支持这种功能。答案 0 :(得分:0)
如果EF Core的版本可以是2.1或更高版本,则可以在StringList
类中使用DbQuery<ClassName> ClassName
。然后,您可以调用DbContext
或在数据库中创建视图,然后可以在var result = db.ClassName.FromSql(sql).ToList().FirstOrDefault();
方法中分配视图。
您创建的课程应该代表您所拥有的视图。
我不知道ef core如何将您的SQL查询与包含列表然后包含下一个列表的示例模型一起解析。可能您必须在SQL查询中使用别名,例如OnModelCreating
,Blogs as B
,但是您必须尝试一下并进行实验。
有关DbQuey的更多信息,您可以在https://docs.microsoft.com/en-us/ef/core/modeling/query-types
答案 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
语句,也可以使用一些插件。