除非先访问/检查返回的对象,否则它的ICollection为空/空 [英] Returned object has null/empty ICollection unless it is accessed/inspected first

查看:71
本文介绍了除非先访问/检查返回的对象,否则它的ICollection为空/空的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在Context中播种反序列化的JSON数据,然后尝试返回DbSet. api/module返回所有带有空ICollection的模块(如果我不实例化它们,则返回null),而Assessments会很高兴地返回虚拟Module.

Seeding deserialised JSON data in the Context and then attempting to return the DbSet. api/module returns all Modules with empty ICollections (null if I don't instantiate them), where as the Assessments happily return the virtual Module.

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

Previous experience in MVC whereby I would have been accessing the object before it being sent to the view, so I haven't encountered this issue before.

注释掉的行:

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

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

Resolves the issue but feels very hacky and would need repeating for each DbSet.Passing the model names as parameters to include them when making the call in the repository also feels like a hack too.

最佳做法是什么?

要添加的内容是,当我在填充ICollection的种子时检查DbSet时,稍后在评估DbSet中包含6个项目.

To add, when I inspect the DbSet when seeding the ICollections are populated and later, the Assessment DbSet has 6 items within it.

模块

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


    }

推荐答案

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

You should consider avoiding passing entities between server and client. By declaring these references as non-virtual, they will not lazy-load on demand. From the perspective of controllers and JSON serialization you don't want to trigger lazy loading anyways as this will cripple performance. So you need to eager-load all child references you want to be included on the client. However, this is a bad idea for a number of reasons including:

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

  1. Performance- Do you need every property on this entity and all of its children? By passing entities you need your database to load all columns, transmit that over the wire to the app server, allocate memory for all of that data, then transmit it over the wire to the client and allocate the memory for that state on the browser. Is it really needed? If you have lazy loading enabled, serialization can trip the lazy load proxy calls and lead to crippling performance. Passing entities is often justified to avoid an extra read call when updating. However, most systems read far more than they write. Making reads faster outweighs the possible saving of an extra read when writing.

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

Completeness - Do you eager load every child reference? Today you need maybe one set of children and don't eager load everything to help minimize point #1. However tomorrow someone may look at the code and assume that they have a complete entity graph to work with. It isn't complete, and this means potential bugs and more inclusions to worsen performance.

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

Complexity & Scaleability- It seems simple to load the entity and send it to the client, then let the client modify that entity, send it back to the server, attach it to a DbContext, and SaveChanges. No need to load the entity twice. Except reattaching entity graphs is messy, and that entity's data may have changed in that time. "Last in wins" is ok for some systems, but can be problematic if users aren't expecting that.

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

Security - Do you expose all of that data to all users? Your UI may restrict the data that users can see or edit, however if your structure passes around entities and you're inclined to simply attach entities sent back to the server and have the context save them then you're opening the system to a significant hacking risk. Clever users can see the data being sent to the client in it's complete state using debugging tools on the browser. They can also modify responses to the server to change data they otherwise wouldn't have been able to edit, even possibly changing FK references to affect data they are not authorized to see or change. You can mitigate this with checks against the database prior to committing, but then your entities are nothing more than overweight view models/DTOs.

使用通过Select从EF Linq表达式实现的映射视图模型有助于解决所有这些情况.诸如Automapper之类的工具可以减少执行映射的无聊"额外代码.通过使用视图模型:

Using mapped view models that are materialized from EF Linq expressions via Select helps with all these scenarios. Tools like Automapper can reduce the "boring" extra code to perform the mapping. By using view models:

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

对于任何在传递实体周围问题时遇到疑问的人来说,都是值得深思的.

Some food for thought for anyone coming across questions around passing entities around.

这篇关于除非先访问/检查返回的对象,否则它的ICollection为空/空的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆