ASP.NET Core 2.1实体框架核心相关的数据复制

时间:2018-09-19 06:31:52

标签: entity-framework-core asp.net-core-2.0 asp.net-core-webapi asp.net-core-2.1

我正在尝试构建一个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

  1. 急于加载
  2. 显式加载
  3. 延迟加载

但是不是。

我不知道如何配置EF Core,以不映射其中的所有对象。

*数据库架构和模型

enter image description here

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
        }
    ]
}

}

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

迁移数据库时将导致“两个相同的自定义属性”错误。