본문 바로가기
카테고리 없음

Asp.net core API - sql server 연동해서 Swagger로 내보내기

by 잘먹는 개발자 에단 2024. 7. 31.

먼저 제가 짠거라서 좀 로직이 이상하다 싶거나, 필요없는 동작이 있다면 댓글로 남겨주시면 감사하겠습니다 ( 꾸벅 )

 

먼저 Sql Server랑 연결하려면 연결 문자열을 설정해줘야 합니다.

필요한게 몇가지가 있는데 다음과 같아요.

 

서버

사용할 데이터베이스

유저명

비밀번호

 

그래서 다음을 가지고 연결 문자열을 만들어줍니다..!

$"Server={_SERVER_NAME};Initial Catalog={_DATABASE_NAME};User ID={_USER_NAME};Password={_PW};";

여기서 Initial Catalog는 처음에 지정할 데이터베이스입니다. 만약에 지정 안해주시면 사전에 정해진 default Database로 연결되니까 조심하세요.

 

 

우리 최소한 몇번은 사용할 거니까 데이터베이스 관련한 로직은 클래스로 만들어주는게 좋습니다.

 

만들기 전에 몇가지 생각해보겠습니당

 

일단 아까 말한 그 클래스 이름은 UseDataBase로 하겠습니다.

 

클래스 프로퍼티로 다음의 것들을 가지고 있겠습니다!

 

 private SqlConnection _connection = null!;
 private string _CONN;
 private string _DATABASE_NAME { get; set; }
 private string _SERVER_NAME { get; set; }
 private string _USER_NAME { get; set; }
 private string _PW { get; set; }

 

1. SqlConnection 

    ㄴ 인스턴스마다 Connection을 가지고 있기 때문에 데이터베이스 별로 인스턴스를 따로 쓰면 되니, 메인 로직이 담긴 코드가 조금이라도 보기 편해질 수 있습니다.

 

2. 연결 문자열, 데이터베이스 이름, 서버, 유저명, 비밀번호

    ㄴ 연결 문자열을 제외하고는 사실 없어도 되긴합니다. 클래스 생성자에서 그냥 연결 문자열 만들어주고, 이후에는 필요가 없어요. 하지만 private로 선언했기 때문에 그냥 냅두겠습니다..! 

 

 

 

메서드는 3가지를 만들었어요.

1. 쿼리를 날려서 결과를 DataTable로 반환하는 메서드

 public DataTable? ExecuteQuery(SqlCommand cmd, string action)
 {
     if (_connection is null)
     {
         throw new Exception("_connection is null");
     }
     else
     {
         switch (action)
         {
             case "SELECT":
                 if (_connection.State == ConnectionState.Closed)
                 {
                     _connection.Open();
                 }

                 using (SqlDataAdapter adapter = new SqlDataAdapter())
                 {
                     adapter.SelectCommand = cmd;

                     using (DataTable table = new DataTable())
                     {
                         adapter.Fill(table);
                         CloseConnection();
                         return table;
                     }
                 }

             case "INSERT":
             case "UPDATE":
             case "DELETE":
             default:
                 return null;
         }
     }
 }

 

2. SqlCommand를 만들어서 반환하는 메서드

public SqlCommand MakeCommand(string queryString)
{
    if (_connection is null)
    {
        throw new Exception("_connection is null");
    }
    else
    {
        var cmd = new SqlCommand(queryString, _connection);
        return cmd;
    }
}

 

3. SqlConnection을 닫는 메서드

 public Boolean CloseConnection()
 {
     _connection.Close();

     return _connection.State == ConnectionState.Closed;
 }

 

 

* 2,3 번은 편의상 있는 메서드입니다.

- 꼭 필요없을 수도 있어요. 하지만 저게 없으면 메인 로직에서 일일히 다 만들어줘야 해서 편의상 만들었습니다.

 

 

*** 그래서 정리하면 

클래스 프로퍼티로 SqlConnection을 보유한다.

초기화 시에 SqlConnection을 만들어서 넣어주고, 이후에 쿼리를 날릴 때마다, 

연결이 열려있으면 바로 쿼리 날려주고 연결 닫아주고 결과(DataTable) 반환한다.

연결이 닫혀있으면 열어주고 결과 반환한다.

* 닫을 거면 왜 여는 것이냐

ㄴ SqlDataAdapter는 쓸때마다 닫아줘야 한다. 연결이 유지가 되지 않는다.

ㄴ 연결을 유지하고 싶다면 SqlDataReader를 쓰라. 

 

그래서 정리해보면 이런 코드가 나옵니다...!

 

 internal class UseDatabase
 {
     private SqlConnection _connection = null!;
     private string _CONN;
     private string _DATABASE_NAME { get; set; }
     private string _SERVER_NAME { get; set; }
     private string _USER_NAME { get; set; }
     private string _PW { get; set; }

     public UseDatabase(string DATABASE_NAME, string SERVER_NAME, string USER_NAME, string PW)
     {
         _DATABASE_NAME = DATABASE_NAME;
         _SERVER_NAME = SERVER_NAME;
         _USER_NAME = USER_NAME;
         _PW = PW;

         _CONN = $"Server={_SERVER_NAME};Initial Catalog={_DATABASE_NAME};User ID={_USER_NAME};Password={_PW};";

         _connection = new SqlConnection(_CONN);
     }

     public DataTable? ExecuteQuery(SqlCommand cmd, string action)
     {
         if (_connection is null)
         {
             throw new Exception("_connection is null");
         }
         else
         {
             switch (action)
             {
                 case "SELECT":
                     if (_connection.State == ConnectionState.Closed)
                     {
                         _connection.Open();
                     }

                     using (SqlDataAdapter adapter = new SqlDataAdapter())
                     {
                         adapter.SelectCommand = cmd;

                         using (DataTable table = new DataTable())
                         {
                             adapter.Fill(table);
                             CloseConnection();
                             return table;
                         }
                     }

                 case "INSERT":
                 case "UPDATE":
                 case "DELETE":
                 default:
                     return null;
             }
         }
     }

     public SqlCommand MakeCommand(string queryString)
     {
         if (_connection is null)
         {
             throw new Exception("_connection is null");
         }
         else
         {
             var cmd = new SqlCommand(queryString, _connection);
             return cmd;
         }
     }

     public Boolean CloseConnection()
     {
         _connection.Close();

         return _connection.State == ConnectionState.Closed;
     }
 }

 

 

 

 

 

그럼 일단 데이터베이스 가져다 쓰는 클래스는 다 만들었는데요. 

 

이제 DataTable로 반환한 그걸 갖다가 rest api로 내보내야하는데 

 

내보내는 과정까지를 봐야겠죠?

 

 

일단 데이터를 다룰 때 주로 사용하는게 DataTable과 DataSet, 그리고 DataRow가 있어요.

 

DataRow가 여러개 모여서 만들어진게 DataTable이고,

DataTable이 여러개 모여서 만들어진게 DataSet이에요. ( 1,2,3차원 배열을 떠올리면 이해가 편합니다 )

 

 app.MapGet("/", () =>
 {
     var database = new UseDatabase(SERVER_NAME: server_name, DATABASE_NAME: database_name, USER_NAME: user_name, PW: pw);

     string queryString = @"SELECT 컬럼1, 컬럼2 FROM dbo.테이블이름 WHERE 날짜 = '2018-02-06'";

     var command = database.MakeCommand(queryString);

     var result = database.ExecuteQuery(cmd: command, action: "SELECT");

     if (result is not null && result.Rows.Count > 0)
     {
         var dataList = methods.ConvertDataTableToList(result);
         return Results.Ok(dataList); // 객체 리스트를 반환
     }
     else
     {
         return Results.NotFound(); // 404 반환
     }
 })
 .WithName("엔드포인트이름") 
 .WithTags("태그");

 

볼까요. 아까 만든 그 클래스를 쓰고 있습니다.

 

초기화 시에 인자를 전달할 때 인자의 순서를 기억해서 순서대로 넣는 것보다 저렇게 넣는게 조금 더 마음이 편합니다.

 

 

이제 볼거는 저기 methods.ConvertDataTableToList 와 모델 클래스입니다..!

 

using System.Data;

namespace Api.Models
{
    public class ItemModel
    {
        public string Title { get; set; }
        public string Body { get; set; }
    }

    public class ModelMethods
    {
        public List<ItemModel> ConvertDataTableToList(DataTable dataTable)
        {
            var dataList = new List<ItemModel>();

            foreach (DataRow row in dataTable.Rows)
            {
                var item = new ItemModel
                {
                    Title = row["컬럼1"].ToString(),
                    Body = row["컬럼2"].ToString()
                };

                dataList.Add(item);
            }

            return dataList;
        }
    }
}

 

일단 모델을 만들어주는데 저 모델은 우리가 이제 밖에 내보낼 때, 필요한 내용만 받아서 그걸 갖다가 리스트나 객체로 내보내겠죠?! ( 아마 리스트일 겁니당 ) 

 

근데 컬럼이 예를 들어서 20개 있다고 쳐보았을 때 그거 20개 아마 다 쓰지 않을거에요. 

 

아마 그 중에서 2개 정도 쓴다고 쳐봅시다.

 

그럼 그 2개에 대한 틀을 만들어주는거에요. 

public class ItemModel
    {
        public string Title { get; set; }
        public string Body { get; set; }
    }

2개를 가져다 쓸거고, 이름은 Title이랑 Body로 지었죠. 

그럼 저희 api를 가져다 쓰는 사람에게는 Title이랑 Body로 보일 겁니다. 

 

 public class ModelMethods
    {
        public List<ItemModel> ConvertDataTableToList(DataTable dataTable)
        {
            var dataList = new List<ItemModel>();

            foreach (DataRow row in dataTable.Rows)
            {
                var item = new ItemModel
                {
                    Title = row["컬럼1"].ToString(),
                    Body = row["컬럼2"].ToString()
                };

                dataList.Add(item);
            }

            return dataList;
        }
    }

 

그러면 

 

이제 다음과 같이 사용할 수 있습니다!

 

 app.MapGet("/", () =>
 {
     var database = new UseDatabase(SERVER_NAME: server_name, DATABASE_NAME: database_name, USER_NAME: user_name, PW: pw);

     string queryString = @"SELECT 컬럼1, 컬럼2 FROM dbo.테이블이름 WHERE 날짜 = '2018-02-06'";

     var command = database.MakeCommand(queryString);

     var result = database.ExecuteQuery(cmd: command, action: "SELECT");

     if (result is not null && result.Rows.Count > 0)
     {
         var dataList = methods.ConvertDataTableToList(result);
         return Results.Ok(dataList); // 객체 리스트를 반환
     }
     else
     {
         return Results.NotFound(); // 404 반환
     }
 })
 .WithName("엔드포인트이름") 
 .WithTags("태그");

 

 

 

 

아 맞다. 

asp.net core 최소 api에서는 기본적으로 Swagger를 쓸 수 없어요. 

 

먼저, 패키지를 설치해줍니다.

 

Swashbuckle.AshNetCore

 

그리고 나서 


            var builder = WebApplication.CreateBuilder(args);

            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen(options =>
            {
                options.SwaggerDoc("v1", new OpenApiInfo
                {
                    Version = "v1",
                    Title = "TEST API",
                    Description = "An API to get TESTdata"
                });
            });

            var app = builder.Build();

            if (app.Environment.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI();
            }

 

 

실행하면 해당  http://localhost:포트번호 로 연결이 될텐데 뒤에다가

/swagger를 붙여줍니다.

 

그러면 자동으로 만들어진 Swagger api 테스트 문서를 볼 수 있어요..!