CS8625๊ด€๋ จ ์งˆ๋ฌธ

์•ˆ๋…•ํ•˜์„ธ์š” c# ํ•™์Šต์ค‘์ธ ํ•™์ƒ์ž…๋‹ˆ๋‹ค.
๊ฐ„๋‹จํ•œ ์ปค๋ฎค๋‹ˆํ‹ฐ ์‚ฌ์ดํŠธ ๊ตฌ์ถ• ์—ฐ์Šต์„ํ•˜๋Š” ๋„์ค‘ ๊ถ๊ธˆํ•œ ์‚ฌํ•ญ์ด ์ƒ๊ฒจ ์งˆ๋ฌธ์„ ๋‚จ๊ธฐ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์ฒซ ๋ฒˆ์งธ๋กœ, "warning CS8625: Null ๋ฆฌํ„ฐ๋Ÿด์„ null์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š๋Š” ์ฐธ์กฐ ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. "๊ฒฝ๊ณ ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š”๋ฐ ํ˜น์‹œ ํ˜„์—…์—์„œ๋Š” ํ•ด๋‹น ๊ฒฝ๊ณ ๋ฅผ ๋ฐ˜๋“œ์‹œ ๊ณ ์น˜์‹œ๋Š”์ง€ ํ˜น์€ ๋ฌด์‹œํ•˜์‹œ๋Š” ์ง€๊ฐ€ ๊ถ๊ธˆํ•ฉ๋‹ˆ๋‹ค.
๋‘ ๋ฒˆ์งธ๋กœ, ํ•ด๋‹น ๊ฒฝ๊ณ ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ์›์ธ์€ UserEntity์™€ UserDetailEntity์—์„œ lazyloading ๋ฐฉ์‹์˜
public required virtual UserDetailEntity์™€ public required virtual UserEntiy๋ฅผ ์„ ์–ธํ•ด์„œ ๊ทธ๋ ‡์Šต๋‹ˆ๋‹ค. ํšŒ์›๊ฐ€์ž… ์‹œ UserEntity๋ฅผ ๋จผ์ € db์— ์ƒ์„ฑ ํ•œ ํ›„ UserDetail์„ ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌํ•˜์—ฌ ์ƒ์„ฑํ•˜๋Š” ๋ฐฉ์‹์ธ๋ฐ, Entity์— ๋Œ€ํ•œ ์„ค๊ณ„๊ฐ€ ์ž˜๋ชป๋œ ๊ฒƒ์ธ์ง€ ๊ถ๊ธˆํ•ฉ๋‹ˆ๋‹ค. (required๊ฐ€ ์•„๋‹Œ nullable์„ ์ฃผ๋Š”๊ฒŒ ์˜ณ๊ฒŒ ๋œ ์„ค๊ณ„์ธ์ง€์— ๋Œ€ํ•œ ์—ฌ๋ถ€)

ํƒœ๊ทธ์— ef core ๊ฐ€ ์žˆ๊ธฐ์—, ef core ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋Š” ๊ฐ€์ • ํ•˜์— ๋‹ต๋ณ€์„ ๋“œ๋ฆฝ๋‹ˆ๋‹ค.

์งˆ๋ฌธ์˜ ๊ฒฝ๊ณ ๋Š” ๋ถ€์ฐจ์ ์ธ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

ef ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ, ๋ชจ๋ธ ํด๋ž˜์Šค์˜ navigation ์†์„ฑ์— required ๋ฅผ ๋ถ€๊ธฐํ•˜๋ฉด, ๋‘˜์€ ํ•„์ˆ˜์  ๊ด€๊ณ„๋กœ ์„ค์ •๋ฉ๋‹ˆ๋‹ค.

ํ•„์ˆ˜์  ๊ด€๊ณ„๋Š” Insert ํ•  ๋•Œ ๋‘ ๊ฐ์ฒด ๋ชจ๋‘ not null ์ด์–ด์•ผ ํ•˜๋ฉฐ, Read ํ•  ๋•Œ๋„ ํ•ญ์ƒ include (Join)๋˜๊ธฐ์— ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋งŒ์•ฝ, ํ•„์ˆ˜์  ๊ด€๊ณ„๊ฐ€ ์˜๋„๋œ ๊ฒƒ์ด๋ผ๋ฉด, ์•„๋ž˜์™€ ๊ฐ™์€ ์ฒ˜๋ฆฌ๋Š” ํ•„์ˆ˜์  ๊ด€๊ณ„๋ฅผ ๋ฌด์‹œํ•˜๋Š” ์ฒ˜๋ฆฌ์ž…๋‹ˆ๋‹ค.

๋Œ€์‹ ์— ๋‘ ๊ฐ์ฒด๋ฅผ ๋ชจ๋‘ ํŠธ๋ž™ํ‚น ์‹œํ‚จ ํ›„์— DB๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

var userDetails = ...;
var userEntity = new(...) { UserDetails = userDetails };

context.Users.Add(userEntity);
context.SaveChanges();

๋งŒ์•ฝ, ์‹œ๋‚˜๋ฆฌ์˜ค ์ƒ, UserDetails๋ฅผ ๊ฐ€์ง€์ง€ ์•Š์€ UserEntity ๊ฐ€ (์งง์€ ์ˆœ๊ฐ„์ด๋ผ๋„) ์กด์žฌํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด ํ•„์ˆ˜์  ๊ด€๊ณ„ ์„ค์ •์€ ์ž˜ ๋ชป๋œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

public class UserEntity
{
   public UserDetails? UserDetails { get; set; }
   // ...

๊ฐœ์ธ์ ์ธ ๊ฒฝํ—˜์œผ๋กœ๋Š”, ๋‹จ์ˆ˜ ๋„ค๋น„๊ฒŒ์ด์…˜ ์†์„ฑ์€ ๋ฌด์ง€์„ฑ์ ์œผ๋กœ nullable ๋กœ ์„ ์–ธํ•˜๊ณ , ํ•„์ˆ˜์  ๊ด€๊ณ„๊ฐ€ ํ•„์š”ํ•˜๋‹ค๋ฉด, ์„œ๋ธŒ ๊ฐ์ฒด์˜ configuration์„ ํ†ตํ•ด ๋ช…์‹œ์ ์œผ๋กœcascade ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ๋ฌด๋‚œํ•œ ๋ฐฉ์‹์ธ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ์„ค์ •ํ•˜๋ฉด, ๊ด€๋ จ ๊ฒฝ๊ณ ๋Š” ์‚ฌ๋ผ์งˆ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ฐธ๊ณ ๋กœ, ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ, warning ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, info ๋„ ๋œจ์ง€ ์•Š๋„๋ก ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

ํŠนํžˆ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๊ณผ ํ˜‘์—…ํ•  ๋•Œ๋Š” ๋”์šฑ ๊ทธ๋ ‡๊ฒŒ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
์ฐธ์กฐ๋˜๋Š” ๋ชจ๋“ˆ์˜ ๊ฒฝ๊ณ ๋Š” ์ฐธ์กฐํ•˜๋Š” ๋ชจ๋“ˆ ์ž‘์„ฑ์ž์—๊ฒŒ๋„ ํ•ญ์ƒ ๋œจ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

ํ•ด๊ฒฐํ•˜์˜€์Šต๋‹ˆ๋‹ค. ๋‹ต๋ณ€ ์ฃผ์‹ ๊ฒƒ์— ๋Œ€ํ•ด ๋‹ค์‹œํ•œ๋ฒˆ ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค.

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
๋‹ต๋ณ€ ์ •๋ง ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.
๋งŒ์•ฝ nullable ์ฒ˜๋ฆฌ๋ฅผ ํ•œ๋‹ค๋ฉด
.Select(u => new SearchUserForManageResponseDto.UserInfo

  •                {*
    
  •                    Id = u.Id,*
    
  •                    FullName = u.UserDetail != null ? $"{u.UserDetail.FirstName} {u.UserDetail.LastName}" : "N/A",*
    
  •                    Nickname = u.UserDetail != null ? u.UserDetail.Nickname : "N/A",*
    
  •                }*
    

์ด๋Ÿฐ์‹์œผ๋กœ ๋ชจ๋“  query๋ฌธ์— nullable๊ด€๋ จ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์•ผ ํ•˜๋Š”๋ฐ ์ด๋Ÿฌ๋ฉด ์„ฑ๋Šฅ๊ณผ ๊ฐ€๋…์„ฑ์— ๋ฌธ์ œ๊ฐ€ ์žˆ์ง€ ์•Š์„๊นŒ์š”?

๊ฐ€๋…์„ฑ ์ฐจ์›์˜ ๋ฌธ์ œ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค.

EF Core ์˜ ์žฅ์  ์ค‘ ํ•˜๋‚˜๋Š” ํ…Œ์ด๋ธ”์˜ ๊ด€๊ณ„๊ฐ€ ์ฝ”๋“œ๋กœ ๋“œ๋Ÿฌ๋‚œ๋‹ค, ๋‹ค์‹œ ๋งํ•˜๋ฉด ์ปดํŒŒ์ผ๋Ÿฌ์˜ ๋„์›€์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค.

๊ทธ ์˜ˆ์‹œ๊ฐ€ ์งˆ๋ฌธ์˜ ๊ฒฝ๊ณ ์ž…๋‹ˆ๋‹ค.

๋‹ˆ๊ฐ€ ํ•„์ˆ˜์  ๊ด€๊ณ„๋กœ ์„ค์ •ํ–ˆ์ž–์•„?
๊ทธ๋Ÿฐ๋ฐ, null ์ธ ์ผ€์ด์Šค๊ฐ€ ๋‚ด ๋ˆˆ์— ๋ณด์—ฌ. ํ™•์ธํ•ด ๋ด!!

๋ผ๊ณ  ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์•Œ๋ ค ์ฃผ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์•ž์„œ ๋ง์”€๋“œ๋ฆฐ ๋Œ€๋กœ ์ฟผ๋ฆฌ ์ฒ˜๋ฆฌ์— ์‹ค์ˆ˜๊ฐ€ ์žˆ์—ˆ๋˜ ๊ฒƒ์ด์ฃ .

์ด ๊ฒฝ์šฐ ์ฟผ๋ฆฌ์˜ ์ฒ˜๋ฆฌ๋ฅผ ํ•„์ˆ˜์  ๊ด€๊ณ„๋ฅผ ์ค€์ˆ˜ํ•˜๋„๋ก ๋ฐ”๊พธ๋Š” ๊ฒŒ ํ•ด๊ฒฐ์ฑ…์ด์ง€, ๊ผญ ํ•„์š”ํ•œ ํ•„์ˆ˜์  ๊ด€๊ณ„๋ฅผ ์™€ํ•ด ์‹œํ‚ค๋Š” ์ฒ˜๋ฆฌ๋Š” ์˜ณ๋‹ค๊ณ  ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

๋งŒ์•ฝ ๋Ÿฐํƒ€์ž„ ์ค‘์— ํ•„์ˆ˜์  ์†์„ฑ์ด null ์ด๋ผ์„œ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ๋‹ค๋ฉด, ์ด๋Š” DB์˜ ๋ ˆ์ฝ”๋“œ์— ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค๋Š” ์ง•ํ›„๋กœ ํ•ด์„ํ•ด์•ผ์ง€ ํ•„์ˆ˜์  ๊ด€๊ณ„๋ฅผ ์„ค์ •ํ•œ ๊ฒŒ ๋ฌธ์ œ๋ผ๊ณ  ํ•ด์„ํ•˜๋ฉด ์•ˆ๋˜๋Š” ๊ฒƒ์ด์ฃ .

๋‘ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ํ•„์ˆ˜์  ๊ด€๊ณ„๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ์—๋„, ์†์„ฑ์ด null์ธ ์ƒํ™ฉ์€ ์ •์ƒ์ ์ธ ๊ฒƒ์ž…๋‹ˆ๋‹ค.
์˜คํžˆ๋ ค ๋น„์ •์ƒ์ ์ธ ์ผ์€ nullable ๊ฐ์ฒด์— ์ ‘๊ทผํ•˜๊ธฐ ์ „์— null ํ™•์ธ์„ ์•ˆ ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ปดํŒŒ์ผ๋Ÿฌ๋Š” ์ด ๊ฒฝ์šฐ์—๋„ ๊ฒฝ๊ณ ๋ฅผ ์ฃผ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. (๊ณ ๋ง™๊ฒŒ๋„์š”)

null ํ™•์ธ ์ฝ”๋“œ๋Š” ๋งค์šฐ ์ •์ƒ์ด๊ณ , ์ƒ์‹์ ์ธ ๊ฒƒ์ด๋ผ, ๊ฐ€๋…์„ฑ๊ณผ ์•„๋ฌด๋Ÿฐ ์ƒ๊ด€์ด ์—†์Šต๋‹ˆ๋‹ค. ๊ฐ€๋…์„ฑ๊ณผ ์žฌ์‚ฌ์šฉ์„ฑ์„ ๋”ฐ์ง„๋‹ค๋ฉด, ์•„๋ž˜์˜ verbose ํ•œ ์ฝ”๋“œ๋ณด๋‹ค๋Š”

FullName = u.UserDetail != null ? $"{u.UserDetail.FirstName} {u.UserDetail.LastName}" : "N/A",

๋ฐ‘ ์ž‘์—…์„ ๋จผ์ € ํ•ด ๋†“๊ณ ,

class UserDetails
{
   public string Culture { get; set; }
   // ...
   public string FullName() => Culture switch 
   {
     "ko" or "ch"  => $"{LastName} {FirstName}",
      _ => $"{FirstName} {LastName}"
   }
// ...

์ด ๋ฐ‘์ž‘์—…์œผ๋กœ ์ธํ•ด, ํ˜ธ์ถœํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ๊ฐ„๊ฒฐํ•ด์ง€๋Š” ๊ฒŒ ๊ฐ€๋…์„ฑ์ด ์ข‹์€ ์ฝ”๋“œ์ฃ .

FullName = u.UserDetail?.FullName() ?? "N/A",
2๊ฐœ์˜ ์ข‹์•„์š”