Возвращенный объект имеет нулевую / пустую коллекцию ICollection, если к нему не обращались / не проверяли сначала - PullRequest
0 голосов
/ 14 ноября 2018

Заполнение десериализованных данных JSON в контексте и затем попытка вернуть DbSet.api / module возвращает все Модули с пустыми ICollections (ноль, если я их не создаю), когда Оценки с радостью возвращают виртуальный Модуль.

Предыдущий опыт в MVC, когда я бы обращался к объекту до негоотправляется в представление, поэтому я не сталкивался с этой проблемой раньше.

Закомментированная строка:

//Enumerable.ToList(ModuleItems.Include(mi => mi.Assessments));

Устраняет проблему, но чувствует себя очень хакерской и должна будет повторяться для каждого DbSet.Передача названий моделей в качестве параметров для включения их при выполнении вызова в хранилище также выглядит как хак.

Какова лучшая практика?

РЕДАКТИРОВАТЬ: Добавить, когда я проверяюDbSet при заполнении набора ICollections заполняется, а затем в DbSet для оценки есть 6 элементов.

Модуль

public class Module
        {
            [Key]
            public int Id { get; set; }
            public string Code { get; set; }
            public string Description { get; set; }
            public DateTime InstanceStartDate { get; set; }
            public DateTime InstanceEndDate { get; set; }

            public ICollection<UnitLeaderModules> UnitLeaderModules { get; set; } = new HashSet<UnitLeaderModules>();
            public ICollection<Assessment> Assessments { get; set; } = new HashSet<Assessment>();

        }

Оценка

public class Assessment
{
    [Key]
    public int Id { get; set; }
    [ForeignKey("Module")]
    public int ModuleId { get; set; }
    public string Description { get; set; }
    public DateTime SubmissionDateMain { get; set; }
    public DateTime SubmissionDateResit { get; set; }
    public string SubmissionMethod { get; set; }

    public virtual Module Module { get; set; }
}

Универсальный репозиторий

public class Repository<T> : IRepository<T> where T : class
    {
        protected readonly DbContext Context;
        protected DbSet<T> DbSet;

        public Repository(DbContext context)
        {
            Context = context;
            DbSet = context.Set<T>();
        }

        public T Get<TKey>(TKey id)
        {
            return DbSet.Find(id);
        }

        public IQueryable<T> GetAll()
        {
            return DbSet;
        }

        public IQueryable<T> GetWhere(Expression<Func<T, bool>> whereExpression)
        {
            return DbSet.Where(whereExpression);
        }

        public void Add(T entity)
        {
            Context.Set<T>().Add(entity);

            Save();
        }

        public void Update(T entity)
        {
            Save();
        }

        private void Save()
        {
            Context.SaveChanges();
        }
    }

Модуль контроллера

 [Route("api/module")]
 [ApiController]
    public class ModuleController : ControllerBase
    {
        private readonly IRepository<Module> _repository;

        public ModuleController(IRepository<Module> repository)
        {
            _repository = repository;
        }

        [HttpGet]
        public ActionResult<IQueryable<Module>> GetAll()
        {
            return Ok(_repository.GetAll());
        }

        [HttpGet("{id}", Name = "GetModule")]
        public ActionResult<Module> GetById(int id)
        {
            var item = _repository.Get(id);
            if (item == null)
            {
                return NotFound();
            }

            return item;
        }

    }

Context

public class UnitLeaderContext : DbContext
    {
        public DbSet<Leader> UnitLeaderItems { get; set; }
        public DbSet<UnitLeaderModules> UnitLeaderModuleItems { get; set; }
        public DbSet<Module> ModuleItems { get; set; }
        public DbSet<Assessment> AssessmentItems { get; set; }

        public UnitLeaderContext(DbContextOptions<UnitLeaderContext> options)
            : base(options)
        {
            ChangeTracker.LazyLoadingEnabled = false;

            if (!EnumerableExtensions.Any(ModuleItems))
            {
                var data =
@"[
        {
            ""id"": 1,

            ""code"": ""YEP404"",
            ""description"": ""Marine Systems"",
            ""instanceStartDate"": ""2018-09-24T00:00:00"",
            ""instanceEndDate"": ""2019-05-17T00:00:00"",
            ""assessments"": [
                {
                    ""id"": 1,
                    ""moduleId"": 1,
                    ""description"": ""Report 1 (60%)"",
                    ""submissionDateMain"": ""2019-01-15T00:00:00"",
                    ""submissionDateResit"": ""2019-07-06T00:00:00"",
                    ""submissionMethod"": ""Upload""

                }, 
                {
                    ""id"": 2,
                    ""moduleId"": 1,
                    ""description"": ""Examination (40%)"",
                    ""submissionDateMain"": ""2019-03-28T00:00:00"",
                    ""submissionDateResit"": ""2019-07-08T00:00:00"",
                    ""submissionMethod"": ""Email Lecturer""
                }
            ]
        }, 
        {
            ""id"": 2,
            ""code"": ""EEN402"",
            ""description"": ""Marine Production"",
            ""instanceStartDate"": ""2018-09-24T00:00:00"",
            ""instanceEndDate"": ""2019-05-17T00:00:00"",
            ""assessments"": [
                {
                    ""id"": 3,
                    ""moduleId"": 2,
                    ""description"": ""Report 1 (60%)"",
                    ""submissionDateMain"": ""2019-04-10T00:00:00"",
                    ""submissionDateResit"": ""2019-07-03T00:00:00"",
                    ""submissionMethod"": ""SOL""
                }, 
                {
                    ""id"": 4,
                    ""moduleId"": 2,
                    ""description"": ""Log Book 1 (40%)"",
                    ""submissionDateMain"": ""2019-04-10T00:00:00"",
                    ""submissionDateResit"": ""2019-07-03T00:00:00"",
                    ""submissionMethod"": ""SOL""
                }
            ]
        }, 
        {
            ""id"": 3,
            ""code"": ""YEP402"",
            ""description"": ""Marine Materials"",
            ""instanceStartDate"": ""2018-09-24T00:00:00"",
            ""instanceEndDate"": ""2019-05-17T00:00:00"",
            ""assessments"": [
                {
                    ""id"": 5,
                    ""moduleId"": 3,
                    ""description"": ""Report 1 (60%)"",
                    ""submissionDateMain"": ""2019-03-15T00:00:00"",
                    ""submissionDateResit"": ""2019-07-03T00:00:00"",
                    ""submissionMethod"": ""Hand-in Office""
                }, 
                {
                    ""id"": 6,
                    ""moduleId"": 3,
                    ""description"": ""Examination"",
                    ""submissionDateMain"": ""2019-04-10T00:00:00"",
                    ""submissionDateResit"": ""2019-07-03T00:00:00"",
                    ""submissionMethod"": ""In-person Exam""
                }
            ]
        }
]
";
                var aaa = JsonConvert.DeserializeObject<List<Module>>(data);

                ModuleItems.AddRange(aaa);
                SaveChanges();
            }

            //Enumerable.ToList(ModuleItems.Include(mi => mi.Assessments));
        }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            builder.Entity<Module>().HasKey(m => m.Id);
            builder.Entity<Module>().HasMany(m => m.UnitLeaderModules);
            builder.Entity<Module>().HasMany(m => m.Assessments);

            builder.Entity<Assessment>().HasKey(m => m.Id);
            builder.Entity<Assessment>().HasOne(m => m.Module);
        }


    }

Ответы [ 2 ]

0 голосов
/ 15 ноября 2018

Вы должны избегать передачи сущностей между сервером и клиентом.Объявляя эти ссылки как не виртуальные, они не будут загружаться по требованию.С точки зрения контроллеров и сериализации JSON вы не хотите запускать отложенную загрузку, так как это снизит производительность.Поэтому вам нужно загружать все дочерние ссылки, которые вы хотите включить в клиент.Однако это плохая идея по ряду причин, в том числе:

  1. Производительность. Вам нужно каждое свойство этой сущности и всех ее дочерних элементов?Передавая сущности, вам нужна база данных для загрузки всех столбцов, передачи их по проводам на сервер приложений, выделения памяти для всех этих данных, затем передачи их по проводам клиенту и выделения памяти для этого состояния в браузере.Это действительно нужно?Если у вас включена отложенная загрузка, сериализация может отключить вызовы отложенной загрузки прокси и привести к снижению производительности.Передача сущностей часто оправдана, чтобы избежать дополнительного вызова чтения при обновлении.Однако большинство систем читают гораздо больше, чем пишут.Ускорение чтения перевешивает возможную экономию дополнительного чтения при записи.

  2. Полнота - Вы хотите загрузить каждую дочернюю ссылку?Сегодня вам нужен, возможно, один набор детей, и вы не хотите загружать все, чтобы минимизировать балл № 1.Однако завтра кто-то может взглянуть на код и предположить, что у него есть полный граф сущностей для работы.Он не завершен, и это означает, что потенциальные ошибки и дополнительные включения могут ухудшить производительность.

  3. Сложность и масштабируемость. Кажется простым загрузить объект и отправить его клиенту, а затем позволитьклиент изменяет эту сущность, отправляет ее обратно на сервер, присоединяет ее к DbContext и SaveChanges.Нет необходимости загружать объект дважды.За исключением того, что повторное присоединение графиков объектов является грязным, и данные этого объекта могли измениться за это время.«Last in wins» подходит для некоторых систем, но может быть проблематичным, если пользователи этого не ожидают.

  4. Безопасность - Предоставляете ли вы все эти данные всем пользователям?Ваш пользовательский интерфейс может ограничивать данные, которые пользователи могут видеть или редактировать, однако, если ваша структура обходит сущности, и вы склонны просто прикреплять сущности, отправляемые обратно на сервер, и сохранять их в контексте, то вы открываете систему для существеннойриск взлома.Умные пользователи могут видеть данные, отправляемые клиенту в их завершенном состоянии, с помощью инструментов отладки в браузере.Они также могут изменять ответы на сервере для изменения данных, которые в противном случае они не смогли бы редактировать, даже, возможно, изменяя ссылки FK, чтобы повлиять на данные, которые они не имеют права просматривать или изменять.Вы можете смягчить это с помощью проверок базы данных перед фиксацией, но тогда ваши сущности - это не что иное, как избыточные модели представления / DTO.

Использование моделей сопоставленных представлений, материализованных из выражений EF LinqЧерез Select помогает во всех этих сценариях.Такие инструменты, как Automapper, могут уменьшить «скучный» дополнительный код для выполнения сопоставления.Использование моделей представлений:

  1. Производительность - SQL Server передает только те данные, которые необходимо отправить клиенту.Это означает более быстрые запросы на чтение и меньшую пропускную способность / использование памяти.
  2. Полнота - целью модели представления является модель представления.Ни больше ни меньше.Это не сущность, и нет никаких предположений о том, что есть, и что она недействительна / присутствует.
  3. Сложность и масштабируемость - нет грязного кода, особенно если вы используете Automapper.Код отображения скучен, но он очень прост для понимания.Нет беспорядочного повторного присоединения, и его можно легко сравнить с текущим состоянием данных до принятия обновлений.
  4. Безопасность - пользователи могут видеть только то, что находится в модели представления, и только изменять то, что находится в модели представления.Серверный код структурирован для загрузки авторизованных объектов и проверки значений перед фиксацией.

Некоторая пища для размышлений для тех, кто сталкивается с вопросами о проходящих мимо людях.

0 голосов
/ 14 ноября 2018

[Unsing a Include] решает проблему, но чувствует себя очень взволнованным и нуждается в повторении для каждого DbSet

Это не хак.Каждый Контроллер определяет форму своих возвращаемых данных, т.е. должен ли клиент получать только Модуль или все Оценки.А использование Включить в DbSet - это способ, которым ваш Контроллер определяет форму нужных ему данных.

Ваш JSON Serializer может в конечном итоге вызвать отложенную загрузку для некоторых свойств навигации, но вам следует отключить отложенную загрузку и собратьграфы объектов явно при сериализации в JSON.

...