【问题标题】:EntityFramework: How to load List of objects from database?EntityFramework:如何从数据库中加载对象列表?
【发布时间】:2018-02-12 20:26:21
【问题描述】:

我有两个实体:

public class Booking
{
    [Key]
    public int Id { get; set; }

    public int RoomId { get; set; }
    [ForeignKey("RoomId")]
    public Room Room { get; set; }

    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }

    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string DocumentNumber { get; set; }
    public string ContactPhone { get; set; }
}

public class Room
{
    [Key]
    public int RoomId { get; set; }
    public int Number { get; set; }

    public int Size { get; set; }
    public bool HasBalcony { get; set; }

    public int Beds_1 { get; set; }
    public int Beds_2 { get; set; }
    public double DayPrice { get; set; }

    public List<Booking> Bookings { get; set; }
    ...

    public int BookingsCount()
    {
        return Bookings.Count;
    }

    public bool IsFree(DateTime dateTime)
    {
       MessageBox.Show(BookingsCount().ToString());
       return true;
    }
}

和 DbContext:

public class HotelContext : DbContext
{
    public DbSet<Employee> Employees { get; set; }
    public DbSet<Room> Rooms { get; set; }
    public DbSet<Booking> Bookings { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Booking>()
        .HasRequired(b => b.Room)
        .WithMany(r => r.Bookings)
        .HasForeignKey(b => b.RoomId);
    }
}

调用 MessageBox.Show 时出现异常:Hotel.exe 中发生“System.NullReferenceException”类型的未处理异常

当我尝试访问 Room::Bookings 时,列表始终为空。 Bookings 表中有一行,Rooms 表中有多行。 如何将所有 Bookings 加载到 Room 对象中?

【问题讨论】:

  • 你能添加你用来访问数据的代码吗?

标签: c# entity-framework


【解决方案1】:

取决于你在学习曲线的哪个阶段,但有些事情很突出

首先

您希望通过 FluentApiAnnotations 创建关系,而不是两者都

即。你的房间实体上有这个

[ForeignKey("RoomId")]

这很流利

 modelBuilder.Entity<Booking>()
    .HasRequired(b => b.Room)
    .WithMany(r => r.Bookings)
    .HasForeignKey(b => b.RoomId);

您需要选择其中一个,否则您最终可能会在 Booking 中出现多个 ID,即 RoomIdRoom_Id

其次

如果您希望能够延迟加载 bookings,您需要制作Bookings 集合Virtual

public virtual List<Booking> Bookings { get; set; }

最后

访问您的数据(假设您的连接字符串是正确的)

using(var db = new HoteContext())
{
    var rooms = db.Rooms.Include(x => x.Bookings).ToList();
}

注意:虽然 EF Lazy 会加载关系,但您可能需要确保已包含 Room-&gt;Booking 关系

【讨论】:

    【解决方案2】:

    考虑下面的代码。

    using System;
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Linq;
    
    namespace ConsoleApp
    {
        class Program
        {
            static void Main(string[] args)
            {
                using (MyDbContext dbContext = new MyDbContext())
                {
                    dbContext.Departments.Add(new Department()
                    {
                        Name = "Some Department1",
                        Employees=new List<Employee>()
                        {
                            new Employee() { Name = "John Doe" }
                        }
                    });
    
                    dbContext.SaveChanges();
    
                    var department = dbContext.Departments.FirstOrDefault(d => d.Name == "Some Department1");
    
                    if (department.Employees != null)
                    {
                        foreach (var item in department.Employees)
                        {
                            Console.WriteLine(item.Name);
                        }
                    }
                }
            }
        }
    
        public class Department
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public List<Employee> Employees { get; set; }
        }
    
        public class Employee
        {
            public int Id { get; set; }
            public string Name { get; set; }
        }
    
        public class MyDbContext : DbContext
        {
            public DbSet<Department> Departments { get; set; }
            public DbSet<Employee> Employees { get; set; }
        }
    }
    

    如果你有上面的代码,控件不会进入if条件,因为department.Employees是null。现在,按如下方式更改 Department 实体。

    public class Department
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public virtual List<Employee> Employees { get; set; }
    }
    

    现在您应该能够看到控制进入 if 条件并输出员工姓名。

    这称为延迟加载

    如果您想立即加载,则不必将 virtual 放入属性中。您可以包括以下属性。

     var department = dbContext.Departments.Include(d => d.Employees).FirstOrDefault(d => d.Name == "Some Department1");
    

    现在您可以看到正在输出员工姓名。

    【讨论】:

      【解决方案3】:

      在这里,您的设计绝对会遇到性能问题。

      使用 EF 的诱惑是将您的对象模型完全映射到数据库,并让 EF 在幕后为您完成所有魔法。但是您需要考虑一下,仅在任何时间点从数据库中获取您需要的具体内容。否则你会遇到各种笛卡尔积问题。我强烈建议您获取 Hibernating Rhino 的 EF Profiler 或类似工具的副本,以便您可以静态分析代码并在运行时分析 EF 性能问题(并查看它生成的 SQL)。为此,您想要的是对数据库进行专门构建的调用以获取计数。否则会发生什么,您将拉出整个 Bookings 表,然后让 C# 为您提供计数。仅当您想对整个列表执行某些操作时,这才有意义。两种选择是:

      1) 针对 Bookings 表创建一个 VIEW 并将其映射到 EF。该视图看起来类似于SELECT ROOMS.ROOMID, COUNT(*) - 您将此视图映射到您的模型, 现在您有一个按房间 (id) 列出的计数列表,您可以单独使用它们或将其汇总以获得您所有房间的总数。如果您在 10 个房间中有 1,000 个预订,那么您只能从数据库中返回 10 行。而在您的设计中,您将撤回所有 1,000 个预订及其所有字段,然后在 C# 中进行过滤。坏juju。

      2) 架构和概念上更简单的方法是直接进行查询(显然这仅从数据库返回一个 int):

      public int BookingsCount()
      {
          int count = 0;
      
          try
          {
              using (var context = new HotelContext()) 
              { 
                  var sql ="SELECT COUNT(*) FROM Bookings WHERE ROOMID=" + this.RoomId;    
                  count = context.Database.SqlQuery<int>(sql).First(); 
              }
          } catch (Exception ex)
          {
              // Log your error, count will be 0 by default
          }
      
          return count;
      }
      

      【讨论】:

        【解决方案4】:

        一个简单的解决方案是将 Bookings 属性设为虚拟。

        public class Room
        {
            [Key]
            public int RoomId { get; set; }
            public int Number { get; set; }
        
            public int Size { get; set; }
            public bool HasBalcony { get; set; }
        
            public int Beds_1 { get; set; }
            public int Beds_2 { get; set; }
            public double DayPrice { get; set; }
        
            public virtual List<Booking> Bookings { get; set; }
        }
        

        更多关于实体框架加载相关实体的信息, https://msdn.microsoft.com/en-us/library/jj574232(v=vs.113).aspx

        【讨论】:

        • false: virtual 表示要对集合使用延迟加载,然后在查询中需要使用 .Include() 指令加载实体实体
        • 在 EF 中,当我们使用 Include() 时,它是 Eagerly Loading。
        • 是的,但这不是添加虚拟的解决方案,virtual + .Include() 可能是
        • 如果你有虚拟,你不需要包含。我已经用示例代码添加了另一个描述性答案,你可以运行它来查看。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-03-13
        • 1970-01-01
        • 1970-01-01
        • 2020-05-09
        • 1970-01-01
        相关资源
        最近更新 更多