我正在尝试构建一个ASP.NET Core API,该API返回一个包含一些相关的,联接的数据的对象。
但是,当我使用Include()调用Article模型时,它是由EF Core自动映射的。这是预期的,但是..正在发生意外的映射。
EF Core为所有对象生成完全映射的对象,从而生成太多数据。
例如,用户模型具有Article属性,当我调用Article模型时,User属性还包含Article对象信息。结果,它返回了大量重复的数据。
第一次,我认为它来自
https://docs.microsoft.com/en-us/ef/core/querying/related-data
但是不是。
我不知道如何配置EF Core,以不映射其中的所有对象。
*数据库架构和模型
public class Topic
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[HiddenInput(DisplayValue = false)]
public int TopicId { get; set; }
[Required]
[StringLength(20, ErrorMessage = "Title cannot be longer than 20 characters.")]
public string Title { get; set; }
[StringLength(200, ErrorMessage = "Description cannot be longer than 200 characters.")]
public string Description { get; set; }
public byte[] Picture { get; set; }
public string PictureMimeType { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:MM-dd-yyyy}", ApplyFormatInEditMode = true)]
public DateTime PostDate { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:MM-dd-yyyy}", ApplyFormatInEditMode = true)]
public DateTime ModifyDate { get; set; }
[Display(Name = "Show")]
public bool ShowFlag { get; set; }
public int UserId { get; set; }
public virtual User User { get; set; }
public virtual ICollection<Article> Articles { get; set; }
}
public class Article
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[HiddenInput(DisplayValue = false)]
public int ArticleId { get; set; }
[Required]
[StringLength(100, ErrorMessage = "Title cannot be longer than 100 characters.")]
public string Title { get; set; }
public string Content { get; set; }
[StringLength(10, ErrorMessage = "Category cannot be longer than 10 characters.")]
public string Category { get; set; } = "Free";
public byte[] Picture { get; set; }
public string PictureMimeType { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:MM-dd-yyyy}", ApplyFormatInEditMode = true)]
public DateTime PostDate { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:MM-dd-yyyy}", ApplyFormatInEditMode = true)]
public DateTime ModifyDate { get; set; }
public int ReadCount { get; set; }
[Required]
[Display(Name = "Show")]
public bool ShowFlag { get; set; }
public int TopicId { get; set; }
public virtual Topic Topic { get; set; }
public int UserId { get; set; }
public virtual User User { get; set; }
}
public class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[HiddenInput(DisplayValue = false)]
public int UserId { get; set; }
[StringLength(20, ErrorMessage = "Name cannot be longer than 20 characters.")]
public string Name { get; set; }
[StringLength(255, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
public string Password { get; set; }
[DataType(DataType.EmailAddress)]
[EmailAddress]
public string Email { get; set; }
[StringLength(20, ErrorMessage = "Title cannot be longer than 20 characters.")]
public string Title { get; set; }
[StringLength(20, ErrorMessage = "Phone number cannot be longer than 20 characters.")]
[DataType(DataType.PhoneNumber)]
public string Phone { get; set; }
[StringLength(100, ErrorMessage = "Address cannot be longer than 100 characters.")]
public string Address { get; set; }
[StringLength(100, ErrorMessage = "Introduction cannot be longer than 100 characters.")]
public string Introduction { get; set; }
[DataType(DataType.Date)]
public DateTime? Birthdate { get; set; }
public byte[] Picture { get; set; }
public string PictureMimeType { get; set; }
public virtual ICollection<Topic> Topics { get; set; }
public virtual ICollection<Article> Articles { get; set; }
}
*控制器代码
var article = await this._context.Articles
.Include(a => a.User)
.Include(a => a.Topic)
.SingleOrDefaultAsync(a => a.ArticleId == id);
*重复数据
{
"articleId": 5,
"title": "Does TaskAll() really wait for all in any case?",
"content": null,
"category": "Threading",
"picture": null,
"pictureMimeType": null,
"postDate": "2018-06-18T07:00:00",
"modifyDate": "2018-06-20T07:47:42.1854485",
"readCount": 101,
"showFlag": true,
"topicId": 1,
"topic": {
"topicId": 1,
"title": "C#",
"description": "About C#",
"picture": null,
"pictureMimeType": null,
"postDate": "2018-05-28T07:00:00",
"modifyDate": "2018-05-28T23:36:04.701311",
"showFlag": true,
"userId": 1,
"user": {
"userId": 1,
"name": "",
"password": null,
"email": "",
"title": "Junior Programmer!!",
"phone": "",
"address": "",
"introduction": "Hey, I'm Jason, most motivated person to be a fullstack programmer! ",
"birthdate": null,
"picture": null,
"pictureMimeType": null,
"permissionId": 0,
"permission": null,
"topics": [],
"articles": [
{
"articleId": 5,
"title": "Does TaskAll() really wait for all in any case?",
"content": null,
"category": "Threading",
"picture": null,
"pictureMimeType": null,
"postDate": "2018-06-18T07:00:00",
"modifyDate": "2018-06-20T07:47:42.1854485",
"readCount": 101,
"showFlag": true,
"topicId": 1,
"userId": 1
}
]
},
"articles": [
{
"articleId": 5,
"title": "Does TaskAll() really wait for all in any case?",
"content": null,
"category": "Threading",
"picture": null,
"pictureMimeType": null,
"postDate": "2018-06-18T07:00:00",
"modifyDate": "2018-06-20T07:47:42.1854485",
"readCount": 101,
"showFlag": true,
"topicId": 1,
"userId": 1,
"user": {
"userId": 1,
"name": "",
"password": null,
"email": "",
"title": "Junior Programmer!!",
"phone": "",
"address": "",
"introduction": "Hey, I'm Jason, most motivated person to be a fullstack programmer! ",
"birthdate": null,
"picture": null,
"pictureMimeType": null,
"topics": [],
"articles": []
}
}
]
},
"userId": 1,
"user": {
"userId": 1,
"name": "",
"password": null,
"email": "",
"title": "Junior Programmer!!",
"phone": "",
"address": "",
"introduction": "Hey, I'm Jason, most motivated person to be a fullstack programmer! ",
"birthdate": null,
"picture": null,
"pictureMimeType": null,
"topics": [
{
"topicId": 1,
"title": "C#",
"description": "About C#",
"picture": null,
"pictureMimeType": null,
"postDate": "2018-05-28T07:00:00",
"modifyDate": "2018-05-28T23:36:04.701311",
"showFlag": true,
"userId": 1,
"articles": [
{
"articleId": 5,
"title": "Does TaskAll() really wait for all in any case?",
"content": null,
"category": "Threading",
"picture": null,
"pictureMimeType": null,
"postDate": "2018-06-18T07:00:00",
"modifyDate": "2018-06-20T07:47:42.1854485",
"readCount": 101,
"showFlag": true,
"topicId": 1,
"userId": 1
}
]
}
],
"articles": [
{
"articleId": 5,
"title": "Does TaskAll() really wait for all in any case?",
"content": null,
"category": "Threading",
"picture": null,
"pictureMimeType": null,
"postDate": "2018-06-18T07:00:00",
"modifyDate": "2018-06-20T07:47:42.1854485",
"readCount": 101,
"showFlag": true,
"topicId": 1,
"topic": {
"topicId": 1,
"title": "C#",
"description": "About C#",
"picture": null,
"pictureMimeType": null,
"postDate": "2018-05-28T07:00:00",
"modifyDate": "2018-05-28T23:36:04.701311",
"showFlag": true,
"userId": 1,
"articles": []
},
"userId": 1
}
]
}
}
答案 0 :(得分:0)
实际上,没有“重复的”数据。
执行以下查询时:
var article = await this._context.Articles
.Include(a => a.User)
.Include(a => a.Topic)
.SingleOrDefaultAsync(a => a.ArticleId == id);
幕后的SQL将是:
SELECT TOP(1)
[a].[ArticleId], [a].[Category], [a].[Content], [a].[ModifyDate], [a].[Picture], [a].[PictureMimeType], [a].[PostDate],[a].[ReadCount], [a].[ShowFlag], [a].[Title],
[a].[TopicId], [a].[UserId],
[a.Topic].[TopicId], [a.Topic].[Description], [a.Topic].[ModifyDate], [a.Topic].[Picture], [a.Topic].[PictureMimeType], [a.Topic].[PostDate], [a.Topic].[ShowFlag], [a.Topic].[Title],[a.Topic].[UserId],
[a.User].[UserId], [a.User].[Address], [a.User].[Birthdate], [a.User].[Email], [a.User].[Introduction], [a.User].[Name], [a.User].[Password], [a.User].[Phone], [a.User].[Picture], [a.User].[PictureMimeType], [a.User].[Title]
FROM [Article] AS [a]
INNER JOIN [Topics] AS [a.Topic] ON [a].[TopicId] = [a.Topic].[TopicId]
INNER JOIN [Users] AS [a.User] ON [a].[UserId] = [a.User].[UserId]
WHERE [a].[ArticleId] = @__id_0
SQL将不会加载任何额外的记录。假设一个用户有两篇文章:
Article1 : { ArticleId=1, UserId=1}
Article3 : { ArticleId=3, UserId=1}
上面的查询将仅加载一篇文章。
当记录从服务器加载到内存中时,EFCore知道User与Articles之间存在1-to-many
的关系,并且刚刚开始将user对象设置为article的导航属性。用户和主题实体之间也会发生相同的情况。所有这些都在内存中完成。
如您所见,在数据库中没有多余的记录,并且在内存中也没有重复的数据。
让您感到困惑的是序列化到client的数据。但是,这确实是有道理的。由于纯字符串不知道引用/指针,因此很难用json表示对象之间的关系。
顺便说一句,不需要用两个几乎相同的属性来修饰User的Email属性:
[DataType(DataType.EmailAddress)]
[EmailAddress]
public string Email { get; set; }
迁移数据库时将导致“两个相同的自定义属性”错误。