EF Core - slog

EF Core๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ ๊ณต์œ ํ•  ๋งŒํ•œ ๋‚ด์šฉ์„ ๊ณ„์† ๋Žƒ๊ธ€๋กœ ๋‹ฌ๊ฒ ์Šต๋‹ˆ๋‹ค.

record๋ฅผ ์ด์šฉํ•œ ์ผ๋ฐ˜์ ์ธ ์—”ํ‹ฐํ‹ฐ ํ˜•ํƒœ

    public record Slogger(string Id, string Name, string NickName, string Email, bool ConfirmEmail)
    {
        [Key]
        public string Id { get; init; } = Id;
        [MaxLength(32)]
        public string Name { get; init; } = Name;
        [MaxLength(32)]
        public string NickName { get; init; } = NickName;
        [MaxLength(64)]
        public string Email { get; init; } = Email;
        public bool ConfirmEmail { get; init; } = ConfirmEmail;
    }

์†Œ์Šค์ฝ”๋“œ์—์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ํ•˜๋ ค๋ฉด

dbContext.Database.Migration()

์ปด๋Ÿผ์ด ์ถ”๊ฐ€/์‚ญ์ œ ๋˜๊ฑฐ๋‚˜, FK๊ฐ€ ๋ณ€๊ฒฝ๋˜๊ฑฐ๋‚˜ ๋ฐ์ดํ„ฐํ˜•์ด ๋ณ€๊ฒฝ๋˜๋Š” ๋“ฑ ์ด๋Ÿด๋•Œ ๋ฐ์ดํ„ฐ๋„ ์ž๋™์œผ๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ•ด์ฃผ๋ฉด ์ข‹๊ฒ ์ง€๋งŒ ๋ฐ์ดํ„ฐ๋Š” ์ˆ˜๋™์œผ๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ•ด์ค˜์•ผ ํ•˜๋Š” ์ƒํ™ฉ์ด ์ข…์ข… ์ƒ๊น๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ๋น„๋‹จ ์ฝ”๋“œ ๋ฐฉ์‹ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ปค๋งจ๋“œ ๋ฐฉ์‹๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค.
(Add-Migration ํ›„ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜๋™์œผ๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ•˜์ง€ ์•Š์œผ๋ฉด Update-Database ๊ฐ€ ์‹คํŒจํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์ข…์ข… ์ƒ๊น€)

๊ทธ๋ž˜์„œ ์‹ค์ œ ํ˜„์—…์—์„œ๋Š” ์ € Migration()๋ฅผ ์“ธ์ผ์ด ๊ทธ๋‹ฅ ์—†๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. Microsoft๋ฌธ์„œ์—์„œ๋„ ์ € ๋ฐฉ๋ฒ• ๋ณด๋‹ค๋Š” SQL ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ƒ์„ฑํ•ด์„œ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ•˜๋Š”๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

  • ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ์ด ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉ ํ•˜๊ธฐ ์ „์— ์‹ ์ค‘ ํ•˜ ๊ฒŒ ๊ณ ๋ คํ•ด ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ™˜๊ฒฝ์—์„œ์ด ๋ฐฐํฌ ์ „๋žต์˜ ๋‹จ์ˆœ์„ฑ์€ ์ƒ์„ฑ ๋˜๋Š” ๋ฌธ์ œ์— ์˜ํ•ด ์–ป๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋Œ€์‹  ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์—์„œ SQL ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ƒ์„ฑ ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

์ •๋ณด๋ฅผ ์ €์žฅํ•˜๊ฑฐ๋‚˜ ๋ณ€๊ฒฝํ•  ๋•Œ ์ƒํƒœ์— ๋”ฐ๋ฅธ ๋‹ค์–‘ํ•œ ๋™์ž‘์ด ํ•„์š”ํ•˜๋Š”๋ฐ์š”,
EF Core๋Š” ๋ณ€๊ฒฝ ์ถ”์  ๊ธฐ๋Šฅ์ด ์žˆ์–ด์„œ SaveChanged๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ณ€๊ฒฝ๋‚ด์šฉ์ด ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ, API ์„œ๋น„์Šค๋ฅผ ๊ตฌ์„ฑํ•˜๊ฒŒ ๋˜๋ฉด์„œ ์งˆ์˜ ์ง€์ ๊ณผ ๋ณ€๊ฒฝ ์ง€์ ์˜ ๊ณ„์ธต์ด ๋ถ„๋ฆฌ๋˜๊ธฐ ๋•Œ๋ฌธ์— DbContext๋กœ ๊ณ„์† ๋ณ€๊ฒฝ์ง€์ ์„ ์ถ”์ฒ™ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€๊ฒฝํ•˜์˜€์„ ๋•Œ ๊ฐ์ง€ํ•˜์—ฌ ์ ์ ˆํ•˜๊ฒŒ Add ๋˜๋Š” Update๋ฅผ ํ•ด์ฃผ๋ฉด ํŽธ๋ฆฌํ•  ํ…๋ฐ์š”,

record์™€ ๊ฒฐํ•ฉํ•˜์—ฌ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

public abstract record BaseRecord : ISettableBaseRecord
    {
        [NotMapped]
        public bool IsStored { get; private set; }
        [NotMapped]
        public bool IsChanged { get; private set; }


        public BaseRecord(BaseRecord record)
        {
            IsStored = record.IsStored;
            IsChanged = true;
        }

        void ISettableBaseRecord.SetSaved()
        {
            IsStored = true;
            IsChanged = false;
        }
    }

    public interface ISettableBaseRecord
    {
        void SetSaved();
    }
    public record Slogger(string Id, string Name, string NickName, string Email, bool ConfirmEmail) : BaseRecord
    {
        [Key]
        public string Id { get; init; } = Id;
        [MaxLength(32)]
        public string Name { get; init; } = Name;
        [MaxLength(32)]
        public string NickName { get; init; } = NickName;
        [MaxLength(64)]
        public string Email { get; init; } = Email;
        public bool ConfirmEmail { get; init; } = ConfirmEmail;
    }

์ดํ›„ BaseRecord๋ฅผ ์‚ฌ์šฉ๋ฐ›์•„ ์—”ํ‹ฐํ‹ฐ๋“ค์„ ๊ตฌํ˜„ํ•˜๋ฉด, ์—”ํ‹ฐํ‹ฐ ์ธ์Šคํ„ด์Šค๋งˆ๋‹ค ์ƒํƒœ๊ด€๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•ด์ง‘๋‹ˆ๋‹ค.

var slogger = new Slogger("dimohy", "์ •์„ธ์ผ", "๋””๋ชจ์ด", "dimohy@email.com", false);

// ์•„์ง DB์— ์ €์žฅํ•˜์ง€ ์•Š์€ ์ƒํƒœ
Console.WriteLine(slogger);

// ์ €์žฅ ํ›„
(slogger as ISettableBaseRecord).SetSaved();

// ์ €์žฅ ํ›„ ์ƒํƒœ
Console.WriteLine(slogger);

// ์ˆ˜์ • ๋ฐœ์ƒ
var slogger2 = slogger with { Name = "์ •์„ธํŒ”" };
Console.WriteLine(slogger2);

์ถœ๋ ฅ๊ฒฐ๊ณผ

Slogger { IsStored = False, IsChanged = False, Id = dimohy, Name = ์ •์„ธ์ผ, NickName = ๋””๋ชจ์ด, Email = dimohy@email.com, ConfirmEmail = False }
Slogger { IsStored = True, IsChanged = False, Id = dimohy, Name = ์ •์„ธ์ผ, NickName = ๋””๋ชจ์ด, Email = dimohy@email.com, ConfirmEmail = False }
Slogger { IsStored = True, IsChanged = True, Id = dimohy, Name = ์ •์„ธํŒ”, NickName = ๋””๋ชจ์ด, Email = dimohy@email.com, ConfirmEmail = False }

EF Core์— C# 9 record๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋Š” ์‚ฌ๋žŒ์ด ์•„์ง์€ ๋งŽ์ง€๋Š” ์•Š์„ ํ…๋ฐ์š”
ํ˜„์žฌ๋Š” ์ธ์ž์— KeyAttribute๋“ฑ์˜ attribute๋ฅผ ๋ถ€์—ฌํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— record recordName([Key] string Id, ...) ํ˜•ํƒœ๋ฅผ ์“ธ ์ˆ˜ ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์ธ์ž์— [Key]๋“ฑ์˜ Attribute๋ฅผ ๋ถ€์—ฌํ•  ์ˆ˜ ์žˆ๊ฒŒ EF Core๊ฐ€ ๋ณ€ํ™”๊ฐ€ ํ•„์š”ํ•  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๊ฑฐ๊ธฐ๋‹ค๊ฐ€ <Nullable>enable</Nullable>์„ ํ•  ๊ฒฝ์šฐ ๊ธฐ์กด ๋ฐฉ์‹์ฒ˜๋Ÿผ EF Core๋ฅผ ์จ์™”์„ ๊ฒฝ์šฐ ์—”ํ‹ฐํ‹ฐ ์†์„ฑ์— ์ˆ˜๋งŽ์€ ๊ฒฝ๊ณ ๊ฐ€ ๋œจ๋Š”๊ฑธ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋””์ž์ธ ํƒ€์ž„์—์„œ DbContext ์ƒ์„ฑ

Add-Migration, Update-Database ๋ช…๋ น์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด, ๋””์ž์ธํƒ€์ž„์— ConnectionString์„ ์ง€์ •ํ•ด์„œ DB์— ์ ‘์† ๊ฐ€๋Šฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์€ DesignTimeDbContextFactory๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉด ๋””์ž์ธ ํƒ€์ž„์—์„œ DbContext ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋“ฑ์˜ ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    public class SlogContextFactory : IDesignTimeDbContextFactory<SlogContext>
    {
        public SlogContext CreateDbContext(string[] args)
        {
            var optionsBuilder = new DbContextOptionsBuilder<SlogContext>();
            var config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
            var connectionString = config["ConnectionStrings:postgresql"];
            optionsBuilder.UseNpgsql(connectionString);

            return new(optionsBuilder.Options);
        }
    }