Дублирование данных, связанных с ядром ASP.NET Core 2.1 Entity Framework Core - PullRequest
0 голосов
/ 19 сентября 2018

Я пытаюсь создать API-интерфейс ASP.NET Core, который возвращает объект, содержащий некоторые связанные, объединенные данные.

Но когда я вызываю модель Article с помощью Include (), он автоматически отображаетсяEF Core.Предполагается, но ... происходит непредвиденное отображение.

EF Core создает полностью сопоставленный объект для всех объектов, так что создается слишком много данных.

Например, модель пользователя имеет свойство Article икогда я вызываю модель Article, свойство User также содержит информацию об объекте Article.В результате он возвращает много дублированных данных.

В первый раз, я думаю, что это происходит из

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

  1. Стремительная загрузка
  2. Явная загрузка
  3. Ленивая загрузка

Но это не так.

Я не знаю, как настроить 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
        }
    ]
}

}

1 Ответ

0 голосов
/ 19 сентября 2018

На самом деле «дублированных» данных нет.

Когда будет выполнен следующий запрос:

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 знает, что существует связь 1-to-many между пользователем и статьями, и просто начинает устанавливать объект пользователя в качестве свойства навигации статьи.То же самое происходит между сущностями пользователя и темы.Все это завершается в памяти.

Как видите, из базы данных не извлекаются дополнительные записи и нет дублированных данных в памяти.

Что смущает вас, так это данные, сериализованные для клиента.Тем не менее, это имеет смысл.Поскольку простая строка не имеет представления о ссылке / указателе, трудно представить отношения между объектами с помощью json.

Кстати, вам не нужно украшать ваше свойство Email пользователя с двумя почти одинаковыми атрибутами:

[DataType(DataType.EmailAddress)]
[EmailAddress]
public string Email { get; set; }

Это приведет к ошибке «двух одинаковых пользовательских атрибутов» при переносе базы данных..

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...