ADO.NET과 ORM에 대한 비교의 대한 정보가 필요하던 중 아래 포스팅을 가져오게 되었다.

잊을만하면 한 번씩 읽어보기로 하며 정리.

 

 

ADO.NET vs an ORM (Dapper & EF) | The Machine Spirit

A case for an ORM compared to traditional ADO.NET and/or raw SQL scripts.

www.bensampica.com

Introduction

현재 매일 Entity Framework Core를 사용합니다. 이 주제에 대한 StackOverflow에 대한 질문을 보았고 기존의 ADO.NET 및 SQL 스크립트를 사용하여 ORM(객체 관계형 매퍼)이없이 작업 한 여러 프로젝트에 대해서 많은 대화를 나누며, ORM을 사용하는 것과 비교해 보았습니다. 이 게시물에서는 Entity Framework를 주로 다루었지만, Dapper도 포함한 차이점에 대해서 이야기를 하도록 하겠습니다.

The Theoretical

보안 고려사항(Security Considerations)

Microsoft가 이야기하는 EF에 대한 포괄적인 보안 고려 사항 목록이 있습니다. 다음은 Dapper 또는 EF를 안전하게 사용하기 위해 조직에서 고려해야 할 몇 가지 사항입니다.

ORM Usage

  • 원시 SQL 쿼리 문자열 빌드를 방지하여 외부 사용자 입력에서 SQL 주입을 방지합니다. Dapper와 Entity Framework는 모두 매개변수화된 쿼리를 빌드하고 삭제된 매개변수를 저장 프로시저에 전달하는 방법을 제공하며 가장 드물고 통제되는 경우를 제외하고는 이러한 매개변수를 선호해야 합니다.
  • 매우 큰 결과 세트를 피하십시오. 5백만 개의 결합된 레코드를 메모리로 선택하는 큰 결과 집합으로 인해 응용 프로그램/시스템이 충돌할 수 있습니다. 애플리케이션에 필요한 것만 쿼리합니다.
  • (EF만 해당) - 서비스 계정이 필요 [db_datareader] [db_datawriter]하고 [db_ddladmin]데이터베이스에 권한이 적용됩니다.

Performance Considerations

성능이 뛰어나고 강력하며 확장 가능한 엔터프라이즈 애플리케이션을 구축 및 유지 관리하는 것이 ORM의 핵심입니다. 다음은 100,000개 이상의 레코드와 평균 <10ms의 SQL 응답 시간이 있는 Entity Framework를 사용하는 관계형 데이터베이스 구현의 라이브 예입니다 . 생성되는 쿼리와 속도에 대한 로그를 확인하십시오.

Materializing

Materialization을 이해하는 것은 ORM 사용의 중요한 부분입니다. 가능한 가장 적은 수의 개체를 구체화하는 쿼리를 작성하는 것은 고성능 응용 프로그램을 유지 관리하는 데 중요합니다. EF 및 Dapper에는 쿼리를 메모리로 구체화하는 일반적인 C# 식이 있습니다. 둘 다 그렇게 하는 몇 가지 고유한 방법과 명시적인 호출 없이 자동으로 구체화되는 몇 가지 제약 조건이 있습니다.

Generated SQL (EF Only)

Entity Framework는 구체적으로 Entities SQL(EF 6+ 및 EF Core)이라는 SQL 언어를 작성한 다음 대상 데이터 원본 SQL(MSSQL, MYSQL 등)로 변환합니다. 따라서 애플리케이션 변경 없이 모든 유형의 데이터베이스에 애플리케이션 계층을 이식할 수 있지만 잠재적인 성능 문제가 있는 쿼리를 유지하는 것이 더 쉽기 때문에 더 복잡한 개체에 대한 쿼리를 생성할 때 약간의 생각이 필요합니다.

실용적인 (The Practical)

보안 및 성능 고려 사항을 염두에 두고 다음은 ORM이 실제로 빛을 발하고 애플리케이션 개발 수명 주기가 개선된 몇 가지 범주의 예입니다.

 

직원을 유지 관리하는 응용 프로그램을 고려하십시오. 애플리케이션은 호출된 테이블 dbo.Employee과 domain.EmployeeType다음 속성 을 사용하여 데이터베이스에 연결 합니다.

EmployeeId (int) [PK] EmployeeTypeId (int) [PK]
EmployeeTypeId (int) [FK] Value [nvarchar]
Name (nvarchar) Active (bit)
Active (bit)  

새로운 개발 가속화 (Speeding Up New Development)

새로운 쿼리를 실행하고 새로운 데이터베이스 테이블과 관련 애플리케이션 모델을 지속적으로 추가하는 데 걸리는 시간을 줄이는 것은 ORM의 가장 큰 이점 중 하나입니다.

 

Comparison: Select all columns from all employees

 

ORM (Entity Framework)

public IEnumerable<Employee> GetEmployees() 
{ 
	return _entityFrameworkContext.Employees; 
}

ORM (Dapper)

public IEnumerable<Employee> GetEmployees()
{
    var employeeProperties = typeof(Employee).GetProperties().Select(prop => prop.Name);

    var sqlQuery = new StringBuilder("SELECT ")
        .AppendJoin(", ", employeeProperties)
        .Append($" FROM [dbo].[{nameof(Employee)}]")
        .ToString();

    using (var databaseConnection = new SqlConnection(_applicationOptions.ConnectionString))
    {
        return await databaseConnection.QueryAsync<Employee>(
            sqlQuery,
            commandType: CommandType.Text);
    }
}

 

ADO.NET SQL Script

USE [Employees]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[GetEmployees]
SELECT * FROM [dbo].[Employee]
GO
USE [Employees]
GRANT EXECUTE ON OBJECT::[dbo].[GetEmployees]
TO rl_Employee_ServiceAccount;

 

애플리케이션 계층 (Application Layer)

public List<Employee> GetEmployees())
{
    using (var connection = new SqlConnection())
    {
        using (SqlCommand cmd = new SqlCommand("[dbo].[GetEmployees]", connection))
        {
            cmd.CommandType = CommandType.StoredProcedure;
            connection.Open();
            SqlDataReader reader = cmd.ExecuteReader();
            return HydrateData(reader);

        }
    }
}
public List<Employee> HydrateData(SqlDataReader reader)
{
    var data = new List<Employee>();
    using (reader)
    {
        if(reader.HasRows)
        {
              while (reader.Read())
              {
                   data.Add(
                       new Employee()
                       {
                            EmployeeId = reader.GetInt32(0),
                            EmployeeTypeId = reader.GetInt32(1),
                            Name = reader.GetString(2),
                            Active= reader.GetBoolean(3),
                       }
                   );
              }
              reader.Close();
          }
      }
      return data;
}

표시된 바와 같이 이것은 매우 단순한 것에 대해 매우 장황합니다. 가독성이 떨어지고 저장 프로시저에서 응용 프로그램으로의 데이터 흐름을 따를 수 있는 능력이 약해집니다. SQL 디버깅은 훨씬 더 많은 시간이 소요되고 실수하기 쉽습니다.

 

 

이러한 자세한 정보 차이는 쿼리가 더 복잡해짐에 따라 길어집니다. 몇 가지 예:

  • 필터링된 쿼리와 함께 카운트를 수행하려는 것과 같은 일반적인 접선 쿼리는 기존 SQL에서 번거롭고(중복성이 필요하지만) EF에서는 단순히 .Count()입니다. EF의 . Where() 절을 통해 응용 프로그램 내부에서 선택적 매개 변수를 사용하여 쿼리를 작성하는 경우에도 마찬가지입니다.
public Tuple<IEnumerable<Employee>, int> GetEmployeeByTypeAndStatus(int employeeTypeId, bool? isActive)
{
    // 모든 레코드를 가져오거나 선택적 매개 변수와 일치하는 레코드를 선택합니다.
    var allRecords = _entityFrameworkContext.Employees
        .Where(employee => isActive == null || employee.IsActive == isActive);
    // Get the total count of the base query.
    var totalRecordsCount = allRecords.Count();

    // 데이터를 필터링하고 결과를 반환합니다.
    var filteredData = allRecords
        .Where(employee => employee.EmployeeTypeId == employeeTypeId);
    return new { filteredData , totalRecordsCount };
}
  • 레코드를 추가하거나 업데이트하려면 추가 또는 삽입을 분리하는 논리가 있는 하나의 저장 프로시저가 필요하며 거의 전체 명령문이 두 번 복제되거나 별도로 유지 관리되어야 하는 두 개의 별도 저장 프로시저가 필요합니다. 둘 다 여전히 열 불일치의 대상이 됩니다. _entityFrameworkContext.Employees.AddOrUpdate(employee)

이러한 쿼리가 존재하고 직원이 새 열을 받은 경우 필요한 작업의 차이를 상상해 보십시오(그리고 실수할 가능성도 있음).

유지보수 감소(Maintenance Reduction)

이전 예에서 현재 아키텍처 파일은 인덱스별로 바인딩되지 않은 배열을 읽고 있으며 새로운 변경 사항에 매우 취약합니다. 열 유형이 변경되면 어떻게 됩니까? 저장 프로시저가 내부에 새 열을 추가하고 인덱스를 엉망으로 만든다면 어떻게 될까요? 또한 dbo.Employee테이블과 상호 작용 하고 새 정보가 필요한 모든 단일 저장 프로시 저도 업데이트해야 합니다.

ORM을 사용하면 모델에 새 속성을 추가하는 것만큼 쉽고 새 속성을 필요로 하는 전체 응용 프로그램의 모든 단일 쿼리에 즉시 사용할 수 있습니다.

public class Employee
{
    public int EmployeeId { get; set; }
    public int EmployeeTypeId { get; set; }
    public string Name { get; set; }
    public string Alias { get; set; } // 새 속성 추가.
    public bool Active { get; set; }
}

직원 정보를 필요로 하는 유사한 쿼리가 많이 있다고 상상해 보십시오. 애플리케이션이 성장함에 따라 발생하는 데이터베이스 열 변경은 Employee모델 의 속성을 추가/업데이트하기만 하면 됩니다 . 모든 단일 쿼리는 새 정보를 포함하도록 자동으로 업데이트됩니다. 한정자를 통해 특정 매장 정보를 선택하는 모든 쿼리 .Select()는 제대로 변경되지 않은 상태로 유지됩니다.

버그를 줄이기 위해 할 말이 있습니다. 강력한 형식의 모델을 통해 데이터베이스와 응용 프로그램 간의 결합된 관계로 인덱스, 열 형식 불일치 또는 저장 프로시저 결과 집합 불일치로 인한 사고가 없습니다. 수정되는 파일이 적고 오류 가능성이 적어 변경 영향이 줄어듭니다.

 

EF 전용

전역 쿼리 필터링은 실행 쿼리에 추가되는 '미들웨어' 쿼리 역할을 합니다. 엔터티의 일시 삭제를 허용하는 위의 직원 및 직원 유형 테이블을 고려하십시오. 전역 쿼리 필터는 .Where(employee => employee.IsActive)필요한 경우 재정의할 수 있는 모든 쿼리에 추가 할 수 있습니다. 이는 개발자 실수를 방지하여 유지 관리를 줄이고 개발 속도를 높입니다.

소유 엔터티는 상위 개체에 연결된 경우에만 생성되도록 설정할 수 있습니다. 자체 테이블이 필요하지 않은 공통 엔터티 그룹 속성을 줄이는 데 자주 사용됩니다. 아래 모델을 고려하십시오.

public class Employee {
    public int PersonId { get; set; }
    public Name Name { get; set; }
}

public class ExternalNewsletterSubscriber {
    public int ExternalNewsletterSubscriberId { get; set; }
    public Name Name { get; set; }
    public string EmailAddress { get; set; }
}

[Owned]
public class Name {
    public string FirstName { get; set; }
    public string MiddleName { get; set; }
    public string LastName { get; set; }
}​

 

Name클래스는 모두 재사용 Employee과 ExternalNewsletterSubscriber와 Name속성은 데이터베이스에서의 적절한 소유자 테이블에 매핑됩니다. 여기 에 대해 자세히 알아보십시오 .

 

통합 테스트 (Integration Testing)

EF는 복잡한 비즈니스 논리에 대한 통합 테스트를 가능한 한 쉽게 작성할 수 있도록 하는 메모리 내 데이터베이스 기능을 제공합니다. 전통적으로 테스트를 위해 데이터베이스가 필요할 때 SSDT 프로젝트에서 수작업으로 구축했습니다. EF의 메모리 내 데이터베이스를 사용하면 테스트가 실행되는 동안 각 통합 테스트가 고유한 원자성 데이터베이스를 가질 수 있으므로 조롱이나 기존 데이터가 필요 없이 기존 애플리케이션 코드와 원활하게 작업할 수 있습니다.

public class TestsDbContextFixture
{
    public readonly DbContext DbContext { get; set; }

    public TestsDbContextFixture()
    {
        var options = new DbContextOptionsBuilder<DbContext>()
            // 각 테스트에 고유한 "로컬" 데이터베이스가 있도록 메모리 내 데이터베이스 사용.
            .UseInMemoryDatabase(Guid.NewGuid().ToString())
            .Options;
        DbContext = new DbContext (options);
    }
}

public class IntegrationTests
{
      [Fact]
      public void Test_DbContext()
      {
          // 메모리 내 데이터베이스 초기화
          var context = new TestsDbContextFixture().DbContext;
          // 테스트 소스..
      }
}

Dapper는 Moq와 같은 모의 프레임워크 및/또는 SQLLite 또는 SSDT 프로젝트와 같은 가벼운 데이터베이스를 활용하여 쉽게 결합할 수 있습니다.

 

몇 가지 참고 사항 (Some Notes)

보안과 성능을 염두에 둘 때 Entity Framework와 Dapper는 모두 애플리케이션 개발 수명 주기를 개선할 수 있는 엄청난 기회를 제공합니다. 새로운 개발 속도를 높이고 유지 관리 및 버그 수정 시간을 줄이며 강력한 통합 테스트를 제공함으로써 ORM은 기존 및 신규 개발의 애플리케이션 수명 주기에서 크게 고려해야 합니다.

Additional Resources

'C#' 카테고리의 다른 글

C# Single or array JSON converter.  (0) 2021.10.07
C# Task WaitAll 와 WhenAll  (0) 2021.09.16
C# 정규식을 활용한 계좌번호 ,휴대폰 번호 마스킹 처리.  (0) 2021.09.08
c# Entity Framework  (0) 2021.08.29
[C#] Garbage Collection  (0) 2021.07.08

공공데이터 API 수집 하고 있는 중에

JSON 데이터 매개변수 중 하나가 단일 또는 배열이 나오는 경우를 경험하게 되었다.

아래 이미지에서 items 매개변수를 보면 경우의 따라 단일 또는 배열로 데이터를 리턴하고 있는 것을

확인 할 수 있다;;

 

 

 

기존 item 배열 기준으로 직렬화 하기 위해 작성 한 타입 때문에

데이터 수집 과정 중 단일 객체가 나올경우 직렬화 과정에서 에러가 나기 시작했다.

 

아래 코드는 JSON 데이터중 객체의 타입이 단일 또는 배열 함께 처리 될 수 있도록

item 필드에 커스텀 어트리뷰트 작성 하여 올바르게 직렬화 할 수 있도록 처리 한다.

작성하는 부분을 스택 오버플로 기사 에서 차용했다 .

 

확인

public class SingleOrArrayConverter<T> : JsonConverter
{
  public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
  {
      JToken token = JToken.Load(reader);
      if (token.Type == JTokenType.Array)
      {
          return token.ToObject<List<T>>();
      }
      return new List<T> { token.ToObject<T>() };
  }
}

//-----------------------------------------------------------------------------//
//-----------------------------------------------------------------------------//


public class Items
{
	[JsonConverter(typeof(SingleOrArrayConverter<Item>))]
	public List<Item> item { get;set;}
}

JsonConverter 추상 메서드 ReadJson에서
객체의 type 확인 하여 ToObject<List<T>> 또는 ToObject<T> 리턴 타입 여부를 결정 한다.

 

전체코드

public class TradeData
{
	public Response response { get; set; }
}
public class Response
{
	public Header header { get; set; }
	public Body body { get; set; }
}
public class Header
{
	public string resultCode { get; set; }
	public string resultMsg { get; set; }
}
public class Body
{
	public Items items { get; set; }
	public int numOfRows { get; set; }
	public int pageNo { get; set; }
	public int totalCount { get; set; }
}
public class Items
{
	[JsonConverter(typeof(SingleOrArrayConverter<Item>))]
	public List<Item> item { get;set;}
}
//-----------------------------------------------------------------------------//
//-----------------------------------------------------------------------------//


public class SingleOrArrayConverter<T> : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(List<T>));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JToken token = JToken.Load(reader);
        if (token.Type == JTokenType.Array)
        {
            return token.ToObject<List<T>>();
        }
        return new List<T> { token.ToObject<T>() };
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        List<T> list = (List<T>)value;
        if (list.Count == 1)
        {
            value = list[0];
        }
        serializer.Serialize(writer, value);
    }

    public override bool CanWrite
    {
        get { return true; }
    }
}

 

'C#' 카테고리의 다른 글

ADO.NET vs an ORM (Dapper & EF)  (0) 2021.10.13
C# Task WaitAll 와 WhenAll  (0) 2021.09.16
C# 정규식을 활용한 계좌번호 ,휴대폰 번호 마스킹 처리.  (0) 2021.09.08
c# Entity Framework  (0) 2021.08.29
[C#] Garbage Collection  (0) 2021.07.08

Task.WaitAll 와 Task.WhenAll 차이 점과 사례를 간략하게 정리하려고 한다.

 

일반적으로

WaitAll()   은 Task의 모든 작업이 완료 될 때까지 현재 스레드를 블럭시키고

WhenAll() 은 스레드를 블럭시키지 않고 기다리는 동작을 나타내는 Task 객체를 반환한다

 

 

또 다른 차이점으로 예외처리가 있는데

WaitAll()이 발생 AggregateException 하는 작업 중 하나를 던져 모든 던져 예외를 검사 할 수 있으며 

await await WhenAll() 풀고 AggregateException첫 번째 예외 만 반환 한다.

 

SampleCode

class SampleApp
{
    public class CustomException : Exception
    {
        public CustomException(String message) : base(message)
        { }
    }

    static void WaitAndThrow(int id, int waitInMs)
    {
        Console.WriteLine($"{DateTime.UtcNow}: Task {id} 시작");

        Thread.Sleep(waitInMs);
        throw new CustomException($"Task {id} {DateTime.UtcNow}");
    }

    static void Main(string[] args)
    {
        Task.Run(async () =>
        {
            await MethodAsync();
        }).Wait();

    }

    static async Task MethodAsync()
    {
        try
        {
            Task[] taskArray = { Task.Factory.StartNew(() => WaitAndThrow(1, 1000)),
                                 Task.Factory.StartNew(() => WaitAndThrow(2, 2000)),
                                 Task.Factory.StartNew(() => WaitAndThrow(3, 3000)) };

            // Task.WaitAll(taskArray); 또는 await Task.WhenAll(taskArray);
            
        }
        catch (AggregateException ex)
        {
            foreach (var inner in ex.InnerExceptions)
            {
                Console.WriteLine($"AggregateException {DateTime.UtcNow}: " + inner.Message);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Exception {DateTime.UtcNow}: " + ex.Message);
        }
        
        Console.ReadLine();
    }
}

output : await Task.WhenAll(taskArray)

19/11/2016 12:18:37 AM: Task 1 started
19/11/2016 12:18:37 AM: Task 3 started
19/11/2016 12:18:37 AM: Task 2 started
Exception 19/11/2016 12:18:40 AM: Task 1 19/11/2016 12:18:38 AM

 

 

Task.WaitAll 작업이 완료 될 때까지 현재 스레드를 차단.
Task.WhenAll 작업이 완료 될 때까지 기다리는 동작을 나타내는 
작업 을 반환.

 

'C#' 카테고리의 다른 글

ADO.NET vs an ORM (Dapper & EF)  (0) 2021.10.13
C# Single or array JSON converter.  (0) 2021.10.07
C# 정규식을 활용한 계좌번호 ,휴대폰 번호 마스킹 처리.  (0) 2021.09.08
c# Entity Framework  (0) 2021.08.29
[C#] Garbage Collection  (0) 2021.07.08

경우에 따라 (계좌번호,휴대폰) 부분 마스킹 처리가 필요하여 정규식 패턴 몇 가지 정리 하려고 한다. (업데이트 중..)

Regex Test https://regex101.com/

 

regex101: build, test, and debug regex

Regular expression tester with syntax highlighting, explanation, cheat sheet for PHP/PCRE, Python, GO, JavaScript, Java. Features a regex quiz & library.

regex101.com

samplecode

using System;
using System.Text.RegularExpressions;	
public class Program
{
        static void Main(string[] args)
        {
            string[] input = new[] {
            "123-00-0012-349",
            "1234-1234-1234-1234",
            "010-8888-9999"
            };
            string[] pattern = new[] {
	// 1234-1234-1234-1234 패턴
	@"((?<=\d{4}[ -]\d{4}[ -]\d{2})\d{2})|(?<=\d{4}[ -]\d{4}[ -]\d{4}[ -])\d{4}",
	// 123-00-0012-349  패턴
	@"((?<=\d{3}[ -]\d{2}[ -]\d{2})\d{2})|((?<=\d{3}[ -]\d{2}[ -]\d{4}[ -])\d{2})",
	// 010-8888-9999  패턴
	@"\d{4}(?!\d{0,4}$)"
            };
            string replacement = string.Empty;


            for (int i = 0; input.Length > i; i++)
            {
                for (int k = 0; pattern.Length > k; k++)
                {
                    if (Regex.IsMatch(input[i], pattern[k]) == true)
                    {
                        replacement = Regex.Replace(input[i], pattern[k], new MatchEvaluator(math => new string('*', math.Length)));
                        Console.WriteLine(replacement);
                        break;
                    }
                }
            }
	}
}

output

123-00-00**-**9
1234-1234-12**-****
010-****-9999

'C#' 카테고리의 다른 글

C# Single or array JSON converter.  (0) 2021.10.07
C# Task WaitAll 와 WhenAll  (0) 2021.09.16
c# Entity Framework  (0) 2021.08.29
[C#] Garbage Collection  (0) 2021.07.08
세가지 Timer 객체의 차이점 (간략메모,참조 링크 포함)  (0) 2021.05.21

- Entity Framework에서 Code-First로 Model을 생성하는 것에 대한

- Code-First Model에서 다른 Table과의 관계에 대한

 

  내용을 간단히 남기고자 한다

 

위로 개념을 올린 김에 ORM 을 보자

C#과 같은 객체 지향형 프로그래밍 언어에서 데이터베이스를 쉽게 사용하기 위한 도구라고 한 줄로 말할 수 있다

OOP개념의 객체(Object)와 관계(Relation)형 DB의 테이블을 맵핑(Mapping)해서 (native)SQL 작성을 안하고 쉽게 DB의 데이터에 Access 할 수 있는 기술이라고도 할 수 있다

여기서 나오는 3개의 단어 Object / Relation / Mapping 이 것이 ORM에서 쓰는 핵심 단어이다. ASP.NET 에서는 데이터 엑세스하는 기본 프레임워크로 Entity Framework를 쓰기 때문에 기본값 처럼 사용하는 것이다.

 

Entity Framwork 모델

크게 3가지가 있다고 한다

1. Code First

2. Model First

3. Database First   

Model First와 Database First 접근 모델은 Visual Studio 의 Visual Model Designer (EDMX) 를 통해 객체/테이블 매핑을 디자인 하는 방식으로 두 개간 차이점은 Database Frist는 기존 DB로 부터 테이블 구조를 읽어와서 디자이너를 통해 Visual Model로 구성되는 것을 말하고 Model First는 기존 DB가 없을 때 직접 Model Designer를 써서 Entity들을 추가해 가면서 모델을 구성하는 방식이다. 위 두가지는 Visual Model Designer로 디자인한 것을 edmx 파일에 저장하게 된다.

Code First 방식은 Model Designer / EDMX를 사용하지 않고 데이터 모델을 C# 클래스로 직접 코딩하는 방식으로 앞으로의 EF는 Code First 방식만을 지원한다고 한다. (http://www.csharpstudy.com/web/article/8-Entity-Framework)

 

Code First

C# 클래스로 테이블의 구조를 정의하고 클래스의 프로퍼티를 테이블 컬럼으로 맵핑한다.

Code First란 말 그대로 DB를 미리 설계하지 않고 C# 클래스로 Domain Object(Model???) 를 정의하고 프로그램 실행 시 DB가 없으면 자동으로 DB를 생성하는 방식을 취한다

-> 이 말은 코드 우선이니까 테이블 모델이 Code 기반에서 생성되고 DB에 대한 생성과 추가 수정이 Code 기반의 Class Model에서 된 다는 것을 말한다

     MS SQL의 management studio를 통해 추가/수정/설정을 하는 것이 아니라 code로 구현해서 DB 관리자가 설정하는 것 처럼 추가하고 변경 사항을

    할 수 있다는 것을 말한다

 

재밌는 부분은 Table 과 맵핑되는([Table("name")] 이런 형태의 단순한 Entity Class를 POCO(Plain Old CLR Obect) 라고 부른다고 한다. 기본적인 단순히 테이터를 저장하고 담아저 전달되는 모델을 말하는데 자바에서는 POJO라는 것이 있었다 (Plain Old Java Object) 두 개가 같은 개념으로 보인다. 제일 기본이 되는 Model 객체?? (비즈니스가 딱히 없는??) 이라고 생각하면 쉬울 것 같다

 

Code First를 활용하는 부분을 보다보면 크게 3가지가 나온다

1. DbContext

2. Fluent API

3. Data Annotation

위 내용을 깊게 들어가면 EF로 DB Table 추가하고 컬럼 변경 및 키지정 관계 지정 까지 프레임워크 사용법 까지 다루게 되어서 저런게 있다는 것만 간략히 말한다. DbContenxt는 간단히 System.Data.Entity의 클래스 이며 DB와 관련된 여러 API를 사용할 수 있다 Context란 말에서 뭔가 저장소Repository 란 단어가 생각나는데 Domain Classes 와 Database의 중간에 있는 녀석으로 생각하면 될 것 같다 간단히 그림으로

 

http://www.entityframeworktutorial.net/entityframework6/dbcontext.aspx 참고하자

 

DbContext가 DB 전체를 관리할 수 있는 API를 제공하는 느낌이라면 Fluent API는 Table에 대한 설정을 할 수있다 테이블 간의 관계나 키 설정과 관련된 매서드들을 사용할 수 있다 Data Annotation은 C#의 Attribute로 컬럼의 값에 대한 Data Validation이나 컬럼의 속성(기본키(Key, notMapped))에 대해 프로터티 바로 위에 지정할 수 있다

즉 DB 제어 / Table 제어 같은 것이지 않겠나

 

마지막으로

Code-First Model에서 다른 Table과의 관계에 대한 표현은 크게 3가지가 있다

1. 1:1 관계

2. 1:N 관계

3. N:M 관계

아마도 관계형 데이터베이스라는 개념에서 관계라는 것이 핵심일 듯 하다 이놈의 관계에서 정규화도 고려되고 인덱스 키나 Join 할 테이블도 고려된다 그냥 테이블 간의 관계라고만 생각하기보다는 Object 간의 연관된 관계로 생각해야 그 객체 안의 속성이 어떤 특성이며 이 특성은 어떤 요소들의 집합 모임인지까지 생각할 수 있을 것 같다

(수학에서 집합/확률통계/방정식(변수를 통한 식만들기) 는 체감적으로 많이 나오는 것 같다 (선형대수학이나 행렬은 아직 뭔가 더 해봐야 알 것 같다)

 

관계는 코드로 보는게 편하다

(https://www.c-sharpcorner.com/UploadFile/3d39b4/relationship-in-entity-framework-using-code-first-approach-w/)

 

일대일 관계

namespace Tester.Data  
{  
   public class User : BaseEntity  
    {  
        public string UserName { get; set; }  
        public string Email { get; set; }  
        public string Password { get; set; }  
        public UserProfile UserProfile { get; set; }  
    }  
}
namespace Tester.Data  
{  
   public class UserProfile : BaseEntity   
    {  
        public string FirstName { get; set; }  
        public string LastName { get; set; }  
        public string Address { get; set; }  
        public virtual User User { get; set; }  
    }  
}

 

두 클래스간 virtual 속성으로 각각의 class 간 연결을 만들었다 즉 연결되어 있는 다른 테이블의 속성을 알 수 있다

using System.Collections.Generic;  
  
namespace Tester.Data  
{  
  public  class Customer : BaseEntity   
    {  
      public string Name { get; set; }  
      public string Email { get; set; }  
      public virtual ICollection<Order> Orders { get; set; }  
    }
using System;  
  
namespace Tester.Data  
{  
    public class Order : BaseEntity  
    {  
        public byte Quanatity { get; set; }  
        public Decimal Price { get; set; }  
        public Int64 CustomerId { get; set; }  
        public virtual Customer Customer { get; set; }  
    }  
}

 

virtual로 선언된 객체를 보면 한쪽이 Collection이다 Code First로 생성한 Entity는 HashSet(ICollection 상속된 클래스)으로 자료형이 되어 있는데 어쨌든 .Net 적으로 얘기하면 Collection 객체를 포함하고 있다는 것이다. 그러니 단순 List도 포함된다

하나의 객체가 다수의 다른 객체를 가지고 있으니 1:N 관계가 성립되는 것이고 부모하나에서 파생된 자식들이라고도 표현이 되고 Has 관계로 연관된 Object / Entity / Table 라는 개념을 포함할 수 있다

(OOP는 진짜 용어들만 이해해도... (개념적/논리적/물리적) 이런 구분인건가? 누군가에게 설명하기도 어렵고 하고 싶지 않는 부분이다...  

어쨌든 일대다 관계는 하나의 객체가 다수의 객체를 포함한다.. 이렇게 정의할 수 있다

 

다대다 관계

using System.Collections.Generic;  
  
namespace Tester.Data  
{  
    public class Student : BaseEntity  
    {  
        public string Name { get; set; }  
        public byte Age { get; set; }  
        public bool IsCurrent { get; set; }  
        public virtual ICollection<Course> Courses { get; set; }  
    }  
}  


using System;  
using System.Collections.Generic;  
  
namespace Tester.Data  
{  
   public class Course : BaseEntity  
    {  
       public string Name { get; set; }  
       public Int64 MaximumStrength { get; set; }  
       public virtual ICollection<Student> Students { get; set; }  
    }  
}

위의 일대다를 보고 다대다를 보면 매우 간단히 답이 나온다 각각 서로에 대한 Collection 객체를 가지고 있으면 다대다 관계이다.

여기서 포인트랄 하나 잡자면 다대대 관계를 그대로 쓰지 않는다 두개 간의 관계를 연결해주는 연결테이블 or 맵핑테이블 or 조인테이블 이런 단어로 사용되는 테이블이 가운데 있다 간단히 생각하면 다대다 관계를 다이렉트로 연결하는 것이 아니라 중간에 맵핑테이블을 두어서 일대다 관계 두 개로 쪼개는 것이다. 그러면 EF의 Database First에서 EDMX 파일에서는 맵핑 Class가 생기고 그 맵핑 정보를 각각 가지고 있는다 위의 코드는 관계만 정의된 것이고 맵핑 Entity에 대한 포함 객체는 없는 것이다. 여기서 DB의 관계와 객체인 Class간에 차이가 생길 수 있는데 (회사 업무에서 발견)

간단히 생각하면 객체에 대한 역정규화 기법?? 이라고 보인다

 

맵핑 객체를 EF가 만들어 준대로 바로 쓰는 것이 아니라 Business Model로 변경 시 해당 컬럼을 프로퍼티의 일부로 포함 할 수도 있고 이미 관계테이블에 포함한 프로퍼티일 경우 중복된 프로퍼티(컬럼)을 쓰지 않아도 되는 것이다.

물론 Entity 형태의 Model을 그대로 쓰는 것이 아니라 모델이 Customizing 되기 때문에 모델이 합쳐지거나 Colection이 포함되는 관계를 맺을 때 중복된 data를 제거하거나 1:N 으로 변경된 Class 내부에 포함하거나 해서 다른 class의 속성을 가져 올 수 있는 것이다.

 

DB에서 중복을 만들거나 두 개의 Entity를 합치는 경우 보통 역정규화라고 하는 것 처럼

Class에서도 맵핑 Class를 포함 하거나 Class를 해체해서 관련 Class의 프로퍼티로 포함 시키는 부분이 모델의 역정규화 인 것 처럼 보인다.

물론 Business Model / Customizing Model에서 쓰이는 부분이다.

 

예제로 만들면 좋겠지만... 개념적인 부분에 시간이 너무 들어가서 말로 압축한다.. 길어야 하루면 될게 몇시간 초과되었다...

기본 개념은 1:N 관계 2개의 Class가 생기고 그 Mapping Entity도 Class로 생성이 되나 Business Model을 만들면서 중복을 합치거나 Class를 해체해서 관계 Class에서 자기 속성으로 바로 접근하게 만드는 건 그때그때 다르다고 생각하면 된다. 

+ Recent posts