除非首先访问/检查返回的对象,否则它的ICollection为空/空

时间:2018-11-14 14:45:00

标签: .net entity-framework entity-framework-core asp.net-core-webapi eager-loading

在Context中播种反序列化的JSON数据,然后尝试返回DbSet。 api / module返回带有空ICollection的所有Modules(如果我不实例化它们,则返回null),在评估过程中,愉快地返回虚拟Module。

以前在MVC中的经验,据此我可以在将对象发送到视图之前对其进行访问,因此我以前从未遇到过此问题。

注释掉的行:

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

解决了这个问题,但感觉很棘手,并且需要为每个DbSet重复操作。在存储库中进行调用时,将模型名称作为参数传递给它们以包括它们也感觉很hack。

最佳做法是什么?

编辑:要添加的内容是,当我在填充ICollection的种子时检查DbSet时,随后在评估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;
        }

    }

上下文

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 :(得分:0)

  

[取消包含]解决了该问题,但感觉很hacky,因此需要为每个DbSet重复

这不是黑客。每个控制器定义其返回数据的形状,即客户端是否应仅获取模块或同时获取所有评估。并且在DbSet上使用Include是Controller指定所需数据形状的方式。

您的JSON序列化程序可能最终会触发某些导航属性的延迟加载,但是当序列化为JSON时,您应该禁用延迟加载并显式构建对象图。

答案 1 :(得分:0)

您应该考虑避免在服务器和客户端之间传递实体。通过将这些引用声明为非虚拟引用,它们将不会按需延迟加载。从控制器和JSON序列化的角度来看,您无论如何都不想触发延迟加载,因为这会降低性能。因此,您需要急于加载要包含在客户端中的所有子引用。但是,由于许多原因,这是一个坏主意,

  1. 性能-您是否需要该实体及其所有子代的所有资产?通过传递实体,您需要数据库加载所有列,通过网络将其传输到应用服务器,为所有数据分配内存,然后通过网络将其传输到客户端,并在浏览器上为该状态分配内存。真的需要吗?如果启用了延迟加载,则序列化可能会使延迟加载代理调用失败,并导致性能下降。通常有理由认为传递实体是为了避免在更新时进行额外的读取调用。但是,大多数系统读取的内容远远超过写入的内容。更快地进行读取胜过编写时可能节省的额外读取。

  2. 完整性-您是否渴望加载每个儿童参考?今天,您可能需要一组孩子,并且不急于加载所有内容以帮助最小化第1点。但是,明天某人可能会看一下代码,并假设他们有一个完整的实体图可以使用。它还不完整,这意味着潜在的错误和更多的包含物,从而降低性能。

  3. 复杂性和可伸缩性-加载实体并将其发送给客户端,然后让客户端修改该实体,将其发送回服务器,将其附加到DbContext,然后保存更改,看起来很简单。无需两次加载实体。除了重新附加实体图是凌乱的,而且那个实体的数据在那时可能已经改变了。在某些系统上,“最后取胜”是可以的,但如果用户不希望这样,则可能会出现问题。

  4. 安全性-您是否将所有这些数据公开给所有用户?您的UI可能会限制用户可以查看或编辑的数据,但是,如果您的结构绕过实体,并且您倾向于仅将发送回的实体附加到服务器上,并保存上下文,那么您将向骇客风险。聪明的用户可以使用浏览器上的调试工具,以完整的状态查看发送到客户端的数据。他们还可以修改对服务器的响应,以更改否则将无法编辑的数据,甚至可能更改FK引用以影响他们无权查看或更改的数据。您可以通过在提交之前对数据库进行检查来减轻这种情况,但是您的实体不过是超重视图模型/ DTO。

使用通过Select从EF Linq表达式中实现的映射视图模型有助于解决所有这些情况。像Automapper这样的工具可以减少执行映射的“无聊”额外代码。通过使用视图模型:

  1. 性能-SQL Server仅将您需要发送给客户端的数据完全传回。这意味着读取查询更快,带宽/内存使用量更轻。
  2. 完整性-视图模型的目的是视图模型。仅此而已。它不是一个实体,也没有关于什么是什么以及它是否有效/存在的假设。
  3. 复杂性和可伸缩性-没有凌乱的代码,尤其是在使用Automapper的情况下。映射代码很无聊,但是很难理解。没有麻烦的重新连接,可以在提交更新之前轻松将其与当前数据状态进行比较。
  4. 安全性-用户只能查看视图模型中的内容,并且只能更改视图模型中的内容。服务器端代码的结构是加载授权实体,并在提交之前检查值。

对于任何在传递周围事物时遇到问题的人来说,都是值得深思的。