解决 内存列表中的EF VS和导航属性的初始化

马修

会员
已加入
2020年8月15日
留言内容
23
编程经验
Beginner
我对EF中的导航属性及其初始化存在疑问。我创建了一个小案例来澄清我要处理的问题。抱歉,我的帖子太长了,但我想明确一点 :).

在我的InMemoryRepository类下,我创建了2个列表,分别是“ Tickets”和“ TicketResponses”。静态方法“种子”填充了这两个列表。您会注意到,我没有初始化对象“ t1”的“响应”集合。此外,您可以找到查找方法“ ReadTicket”,该方法根据其ID返回票证;方法“ ReadTicketRepsonse”,也根据其ID返回票证响应。

InMemoryRepository:
public class InMemoryRepository : IRepository
    {
        private static readonly List<Ticket> Tickets = new List<Ticket>();
        private static readonly List<TicketResponse> TicketResponses = new List<TicketResponse>();

        static InMemoryRepository()
        {
            Seed();
        }
        
        public Ticket ReadTicket(int tickedId)
        {
            return Tickets.Find(t => t.TicketId == tickedId);
        }

        public TicketResponse ReadTicketResponse(int ticketResponseId)
        {
            return TicketResponses.Find(tr => tr.TicketResponseId == ticketResponseId);
        }

        private static void Seed()
        {
            Ticket t1 = new Ticket()
            {
                TicketId = 1,
                Name = "Ticket 1"
            };
            Tickets.Add(t1);

            TicketResponse tr1 = new TicketResponse()
            {
                TicketResponseId = 1,
                Name = "Ticket response 1",
                Ticket = t1
            };
            TicketResponses.Add(tr1);
        }
    }

在我的TicketManager类下,我有一个InMemoryRepository类的实例,并且还定义了两个方法,即“ GetTicket”和“ GetTicketResponse”。两种方法都使用InMemoryRepository的实例根据ID返回票证或票证响应。

TicketManager:
 public class TicketManager : ITicketManager
    {
        private readonly IRepository _repository = new InMemoryRepository();
        
        public Ticket GetTicket(int ticketId)
        {
            return _repository.ReadTicket(ticketId);
        }
        
        public TicketResponse GetTicketResponse(int ticketResponseId)
        {
            return _repository.ReadTicketResponse(ticketResponseId);
        }
    }

在我的Program类下,我创建了TicketManager类的实例。我正在使用该实例来获取ID = 1的票证。从该票证中询问姓名,并且还要对票证中的“响应”集合进行计数。后者将提供System.NullReferenceException,因为我在使用“种子”方法创建票证时并未初始化“响应”集合。

Program:
class Program
    {
        private static readonly ITicketManager TicketManager = new TicketManager();
        
        static void Main(string[] args)
        {
            Ticket ticket = TicketManager.GetTicket(1);

            Console.WriteLine(ticket.Name);
            Console.WriteLine(ticket.Responses.Count);
        }
    }

现在,我将执行相同的操作,但是将使用实体框架,而不是在内存列表中使用。

我创建了一个DbContext(EfTestPartFourDbContext),其中有2个DbSet(Tickets和TicketResponses)。此外,我有一个EfTestPartFoutDbInitializer类,其中包含一个“ Seed”方法,EfTestPartFourDbContext类用于填充其DbSet(仅用于测试)。

EfTestPartFourDbContext:
internal class EfTestPartFourDbContext : DbContext
    {
        public DbSet<Ticket> Tickets { get; set; }
        public DbSet<TicketResponse> TicketResponses { get; set; }

        public EfTestPartFourDbContext()
        {
            EfTestPartFourDbInitializer.Initialize(this, true);
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder
                .UseSqlite("Data Source = EfTestPartFour.db")
                .UseLoggerFactory(LoggerFactory.Create(b => b.AddDebug()));
        }
    }

EfTestPartFourDbInitializer:
internal static class EfTestPartFourDbInitializer
    {
        public static void Initialize(EfTestPartFourDbContext context, bool dropCreateDatabase)
        {
            if (dropCreateDatabase)
            {
                context.Database.EnsureDeleted();
            }

            if (context.Database.EnsureCreated())
            {
                Seed(context);
            }
        }

        private static void Seed(EfTestPartFourDbContext context)
        {
            Ticket t1 = new Ticket()
            {
                TicketId = 1,
                Name = "Ticket 1"
            };
            context.Tickets.Add(t1);

            TicketResponse tr1 = new TicketResponse()
            {
                TicketResponseId = 1,
                Name = "Ticket response 1",
                Ticket = t1
            };
            context.TicketResponses.Add(tr1);
            
            context.SaveChanges();

            foreach (var entry in context.ChangeTracker.Entries())
            {
                entry.State = EntityState.Detached;
            }
        }
    }

我创建了一个与InMemoryRepository完全相同的Repository类,不同的是它不再具有2个列表(Tickets和TicketResponses),而是一个EfTestPartFourDbContext对象,可在“ ReadTicket”和“ ReadTicketResponse”方法中使用。此外,不再存在“种子”方法,因为播种现在由EfTestPartFourDbInitializer类完成。

Repository:
public class Repository : IRepository
    {
        private readonly EfTestPartFourDbContext _context = new EfTestPartFourDbContext();
        
        public Ticket ReadTicket(int tickedId)
        {
           return _context.Tickets.Include(t=>t.Responses).FirstOrDefault(t=>t.TicketId == tickedId);
        }
        
        public IEnumerable<TicketResponse> ReadResponsesOfTicket(int tickedId)
        {
            return _context.TicketResponses.Where(tr => tr.Ticket.TicketId == tickedId);
        }

        public TicketResponse ReadTicketResponse(int ticketResponseId)
        {
            return _context.TicketResponses.Find(ticketResponseId);
        }

        public IEnumerable<TicketResponse> ReadTicketResponses()
        {
            return _context.TicketResponses;
        }
    }

现在,TicketManager类不再使用InMemoryRepository的实例,而是使用Repository类的实例。

TicketManager:
public class TicketManager : ITicketManager
    {
        private readonly IRepository _repository = new Repository();
        
        public Ticket GetTicket(int ticketId)
        {
            return _repository.ReadTicket(ticketId);
        }
        
        public TicketResponse GetTicketResponse(int ticketResponseId)
        {
            return _repository.ReadTicketResponse(ticketResponseId);
        }
    }

我的程序类也保持不变。当您运行它时,当我对故障单中的“响应”集合进行计数时,我不再得到System.NullReferenceException。

所以现在我的问题是:为什么这不抛出NullReferenceExcption?我也没有在播种期间初始化票证的“回复”集合。 EF是否以某种方式初始化了它的导航属性?

非常感谢你!
 
Solution
如果没有引发异常,返回的计数是多少?是零还是一?

如果是的话,我怀疑您曾告诉EF票证和TicketResponse之间的关系,因此EF就像好ORM一样会填充Response集合。

如果您向我们展示了如何声明Ticket和TicketResponse类以及向EF告知EF这两个类之间的关系(也称为外键),这将对我们有帮助。

您将我带入正确的轨道,并在对其进行了更详细的研究之后,确实是ORM(在本例中为EF)的任务之一,即在尚未初始化导航属性时对其进行初始化。
我还知道即使你不...

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,497
地点
弗吉尼亚州切萨皮克
编程经验
10+
如果没有引发异常,返回的计数是多少?是零还是一?

如果是的话,我怀疑您曾告诉EF票证和TicketResponse之间的关系,因此EF就像好ORM一样会填充Response集合。

如果您向我们展示了如何声明Ticket和TicketResponse类,并且告诉EF有关这两个类之间的关系(即外键)的信息,对我们会有所帮助。
 

马修

会员
已加入
2020年8月15日
留言内容
23
编程经验
Beginner
如果没有引发异常,返回的计数是多少?是零还是一?

如果是的话,我怀疑您曾告诉EF票证和TicketResponse之间的关系,因此EF就像好ORM一样会填充Response集合。

如果您向我们展示了如何声明Ticket和TicketResponse类以及向EF告知EF这两个类之间的关系(也称为外键),这将对我们有帮助。

您将我带入正确的轨道,并在对其进行了更详细的研究之后,确实是ORM(在本例中为EF)的任务之一,即在尚未初始化导航属性时对其进行初始化。
我还了解到,即使您没有为其中一个域模型创建DbSet,但在其中一个域模型中将其用作导航属性时,EF也会检测到该实体并为此实体创建表。
因此,与我们在内存存储库中以列表形式创建的“虚表”相比,EF能够检测关系并为我初始化这些关系,但是在内存存储库中,初始化关系是我的工作,...
这就解释了为什么在使用内存中存储库时会得到空引用异常,而在使用EF存储库时却没有得到空引用异常。

谢谢您的帮助。
 
最佳 底部