Coding Memo

Entity Framework - virtual 키워드 본문

Language/C#

Entity Framework - virtual 키워드

minttea25 2023. 12. 31. 11:31

Entity Framework에서 데이터 모델 클래스를 작성할 때, Navigation Property (네비게이션 속성)에 virtual 키워드를 붙일 수 있다. 

 

virtual 키워드는 Entity Framework가 이 속성(property)를 Lazy Loading 하도록 한다.

Lazy Loading은 해당 클래스나 객체를 로드시에 같이 바로 로드되는 것이 아니라, 관련된 데이터가 실제로 필요한 시점에만 데이터베이스로부터 로드하도록 한다.

 

아래와 같은 모델이 있다고 사정하자.

[Table("User")]
public class UserDb {
    [Key]
    public ulong UserId { get; set; }
    public string UserName { get; set; }
}

[Table("Room")]
public class RoomDb {
    [Key]
    public ulong RoomId { get; set; }
    public virtual ICollection<UserDb> Users { get; set; }
}

 

예를 들어 위 데이터모델에서, RoomDb는 로드 시에 RoomId만을 로드한다. 이때 추가적인 로드가 없다면 Users는 null 값으로 로드된다.

 

 

virtual 속성 로드 방법

Linq의 Include를 사용하여 로드시 포함시킨다.

using (AppDbContext db = new AppDbContext())
{
    // find room
    RoomDb room = db.Rooms
        .Include(r => r.Users)
        .FirstOrDefault();

    // room에 있는 Users 관련 작업 가능
}

 

include를 이용하지 않고 단순히 로드한다면 virtual로 지정된 속성은 로드가 되지 않고 null값이 들어가 있는 것을 볼 수 있을 것이다.


테스트 코드

 

AppDbContext

public class AppDbContext : DbContext
{
    const string ConnString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=Test;Integrated Security=True;Connect Timeout=30;Encrypt=False;Trust Server Certificate=False;Application Intent=ReadWrite;Multi Subnet Failover=False";

    public DbSet<UserDb> Users { get; set; }
    public DbSet<RoomDb> Rooms { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(ConnString);
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<UserDb>()
            .HasIndex(u => u.UserName)
            .IsUnique();
    }
}

 

Model

[Table("User")]
public class UserDb
{
    [Key]
    public ulong UserId { get; set; }
    public string UserName { get; set; }
}

[Table("Room")]
public class RoomDb
{
    [Key]
    public ulong RoomId { get; set; }
    public virtual ICollection<UserDb> Users { get; set; }
}

 

 

Main

class Program
{

    static void Main()
    {
        using (AppDbContext db = new AppDbContext())
        {
            // add data codes

            //db.Rooms.Add(
            //    new()
            //    {
            //        RoomId = 1,
            //        Users = new[] 
            //        { 
            //            new UserDb() { UserId = 1, UserName = "Test1" },
            //            new UserDb() { UserId = 2, UserName = "Test2" },
            //            new UserDb() { UserId = 3, UserName = "Test3" }
            //        }
            //    });
            //db.SaveChanges();


			// 해당 코드는 room의 Users를 로드하지 않음 => null
            //RoomDb room = db.Rooms
            //    .FirstOrDefault(r => r.RoomId == 1);

            RoomDb room = db.Rooms
                .Include(r => r.Users)
                .FirstOrDefault(r => r.RoomId == 1);

            // room에 있는 Users 관련 작업 가능
            if (room.Users == null) // include 하지 않았을 경우
            {
                Console.WriteLine("The Users in room is not loaded. [room.Users == null]");
            }
            else
            {
                foreach (var user in room.Users)
                {
                    Console.WriteLine(user.UserName);
                }
            }

        }
    }
}