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

r

by keisoft 2025. 5. 20.

API에서 Dapper 결과를 그대로 반환하고, 클라이언트 측에서 이를 `Dictionary<string, object>` 형태로 변환하는 방법을 설명해 드리겠습니다.

## 1. API 서버 코드

서버에서는 Dapper 결과를 JSON으로 직렬화하여 반환합니다.

```csharp
using Dapper;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.Sqlite;
using System.Collections.Generic;
using System.Threading.Tasks;

[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    private readonly IDbConnectionFactory _connectionFactory;

    public UsersController(IDbConnectionFactory connectionFactory)
    {
        _connectionFactory = connectionFactory;
    }

    [HttpGet]
    public async Task<IActionResult> GetAll()
    {
        using (var connection = _connectionFactory.CreateConnection())
        {
            await connection.OpenAsync();
            
            // Dapper 쿼리 결과를 그대로 반환
            var users = await connection.QueryAsync("SELECT * FROM Users");
            
            // ASP.NET Core가 자동으로 dynamic 객체를 JSON으로 직렬화
            return Ok(users);
        }
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> GetById(int id)
    {
        using (var connection = _connectionFactory.CreateConnection())
        {
            await connection.OpenAsync();
            
            var user = await connection.QueryFirstOrDefaultAsync(
                "SELECT * FROM Users WHERE Id = @Id",
                new { Id = id }
            );
            
            if (user == null)
                return NotFound();
                
            return Ok(user);
        }
    }
    
    // 기타 CRUD 메서드
}
```

## 2. 클라이언트 코드

클라이언트에서는 API 응답을 받아 `Dictionary<string, object>` 형태로 변환합니다.

```csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
using System.Text.Json;

public class UserApiClient
{
    private readonly HttpClient _httpClient;
    
    public UserApiClient(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }
    
    // Dictionary<string, object> 형태로 변환
    public async Task<IEnumerable<Dictionary<string, object>>> GetUsersAsDictionaryAsync()
    {
        var response = await _httpClient.GetAsync("api/users");
        response.EnsureSuccessStatusCode();
        
        var content = await response.Content.ReadAsStringAsync();
        
        // JSON 문자열을 Dictionary<string, object>의 리스트로 변환
        // System.Text.Json 사용 방식
        using var document = JsonDocument.Parse(content);
        var array = document.RootElement;
        
        List<Dictionary<string, object>> result = new List<Dictionary<string, object>>();
        
        foreach (var element in array.EnumerateArray())
        {
            var dict = new Dictionary<string, object>();
            
            foreach (var property in element.EnumerateObject())
            {
                dict[property.Name] = ExtractValue(property.Value);
            }
            
            result.Add(dict);
        }
        
        return result;
    }
    
    // JSON 값을 적절한 .NET 타입으로 변환
    private object ExtractValue(JsonElement element)
    {
        switch (element.ValueKind)
        {
            case JsonValueKind.String:
                return element.GetString();
            case JsonValueKind.Number:
                if (element.TryGetInt32(out int intValue))
                    return intValue;
                if (element.TryGetInt64(out long longValue))
                    return longValue;
                return element.GetDouble();
            case JsonValueKind.True:
                return true;
            case JsonValueKind.False:
                return false;
            case JsonValueKind.Null:
                return null;
            default:
                return element.ToString();
        }
    }
    
    // IEnumerable<dynamic>으로 변환하는 방법
    public async Task<IEnumerable<dynamic>> GetUsersAsDynamicAsync()
    {
        var dictionaries = await GetUsersAsDictionaryAsync();
        
        // Dictionary를 ExpandoObject로 변환
        return dictionaries.Select(dict => {
            dynamic expando = new System.Dynamic.ExpandoObject();
            var expandoDict = (IDictionary<string, object>)expando;
            
            foreach (var kvp in dict)
            {
                expandoDict[kvp.Key] = kvp.Value;
            }
            
            return expando;
        });
    }
    
    // LINQ 쿼리를 적용하는 예시
    public async Task<IEnumerable<Dictionary<string, object>>> GetFilteredUsersAsync(int minAge)
    {
        var users = await GetUsersAsDictionaryAsync();
        
        return users.Where(user =>
            user.ContainsKey("Age") &&
            user["Age"] is int age &&
            age >= minAge
        );
    }
}
```

## 3. 클라이언트 사용 예시 (Blazor WASM)

```csharp
@page "/users"
@using System.Collections.Generic
@inject UserApiClient UserApi

<h1>사용자 목록</h1>

@if (users == null)
{
    <p>로딩 중...</p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>ID</th>
                <th>이름</th>
                <th>이메일</th>
                <th>나이</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var user in users)
            {
                <tr>
                    <td>@user["Id"]</td>
                    <td>@user["Name"]</td>
                    <td>@user["Email"]</td>
                    <td>@user["Age"]</td>
                </tr>
            }
        </tbody>
    </table>
    
    <button class="btn btn-primary" @onclick="FilterUsers">30세 이상 필터링</button>
}

@code {
    private IEnumerable<Dictionary<string, object>> users;

    protected override async Task OnInitializedAsync()
    {
        users = await UserApi.GetUsersAsDictionaryAsync();
    }

    private async Task FilterUsers()
    {
        users = await UserApi.GetFilteredUsersAsync(30);
    }
}
```

## 4. Newtonsoft.Json을 사용하는 방법 (대안)

많은 경우 Newtonsoft.Json이 더 간편하게 사용할 수 있습니다:

```csharp
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

public async Task<IEnumerable<Dictionary<string, object>>> GetUsersAsDictionaryAsync()
{
    var response = await _httpClient.GetAsync("api/users");
    response.EnsureSuccessStatusCode();
    
    var content = await response.Content.ReadAsStringAsync();
    
    // Newtonsoft.Json을 사용한 변환
    var jArray = JArray.Parse(content);
    
    var result = new List<Dictionary<string, object>>();
    foreach (var jObject in jArray)
    {
        var dict = new Dictionary<string, object>();
        foreach (var prop in (JObject)jObject)
        {
            dict[prop.Key] = ConvertJTokenToNative(prop.Value);
        }
        result.Add(dict);
    }
    
    return result;
}

private object ConvertJTokenToNative(JToken token)
{
    switch (token.Type)
    {
        case JTokenType.Integer:
            return token.Value<long>();
        case JTokenType.Float:
            return token.Value<double>();
        case JTokenType.String:
            return token.Value<string>();
        case JTokenType.Boolean:
            return token.Value<bool>();
        case JTokenType.Date:
            return token.Value<DateTime>();
        case JTokenType.Null:
            return null;
        default:
            return token.ToString();
    }
}
```

## 5. 성능 최적화

대량의 데이터를 처리하는 경우 성능을 최적화하는 방법:

1. **페이징 구현**:

```csharp
// API 서버
[HttpGet]
public async Task<IActionResult> GetPaged(int page = 1, int pageSize = 20)
{
    using (var connection = _connectionFactory.CreateConnection())
    {
        await connection.OpenAsync();
        
        var offset = (page - 1) * pageSize;
        var users = await connection.QueryAsync(
            "SELECT * FROM Users LIMIT @PageSize OFFSET @Offset",
            new { PageSize = pageSize, Offset = offset }
        );
        
        // 전체 레코드 수를 함께 반환
        var totalCount = await connection.ExecuteScalarAsync<int>("SELECT COUNT(*) FROM Users");
        
        return Ok(new {
            Items = users,
            TotalCount = totalCount,
            Page = page,
            PageSize = pageSize,
            TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize)
        });
    }
}

// 클라이언트
public async Task<(IEnumerable<Dictionary<string, object>> Items, int TotalCount, int Page, int TotalPages)>
    GetPagedUsersAsync(int page = 1, int pageSize = 20)
{
    var response = await _httpClient.GetAsync($"api/users?page={page}&pageSize={pageSize}");
    response.EnsureSuccessStatusCode();
    
    var content = await response.Content.ReadAsStringAsync();
    using var document = JsonDocument.Parse(content);
    var root = document.RootElement;
    
    // 페이징 정보 추출
    var totalCount = root.GetProperty("totalCount").GetInt32();
    var currentPage = root.GetProperty("page").GetInt32();
    var totalPages = root.GetProperty("totalPages").GetInt32();
    
    // 아이템 배열 처리
    var items = root.GetProperty("items").EnumerateArray()
        .Select(element => {
            var dict = new Dictionary<string, object>();
            foreach (var property in element.EnumerateObject())
            {
                dict[property.Name] = ExtractValue(property.Value);
            }
            return dict;
        })
        .ToList();
    
    return (items, totalCount, currentPage, totalPages);
}
```

2. **선택적 필드 요청**:

```csharp
// API 서버
[HttpGet("fields")]
public async Task<IActionResult> GetWithFields([FromQuery] string fields)
{
    if (string.IsNullOrEmpty(fields))
    {
        fields = "*"; // 기본값은 모든 필드
    }
    
    using (var connection = _connectionFactory.CreateConnection())
    {
        await connection.OpenAsync();
        
        var users = await connection.QueryAsync($"SELECT {fields} FROM Users");
        return Ok(users);
    }
}

// 클라이언트
public async Task<IEnumerable<Dictionary<string, object>>> GetUsersWithFieldsAsync(string[] fields)
{
    var fieldsParam = string.Join(",", fields);
    var response = await _httpClient.GetAsync($"api/users/fields?fields={fieldsParam}");
    // 이하 처리 로직은 동일
}
```

이러한 방식으로 서버에서는 Dapper 결과를 그대로 반환하고, 클라이언트에서 필요에 따라 `Dictionary<string, object>` 또는 `dynamic` 형태로 변환하여 LINQ 등으로 처리할 수 있습니다.




ㅡㅡㅡㅡㅡㅡㅡㅡㅡ

C# API에서 Dapper 결과를 반환하고 클라이언트 단에서 ExpandoObject를 이용해 List<dynamic>으로 처리하는 방법을 설명해 드리겠습니다.

서버 측(API)에서는 Dapper를 사용하여 데이터를 조회하고, 클라이언트 측에서는 ExpandoObject를 활용하여 동적 데이터를 처리하는 과정을 구현해 보겠습니다.

## 서버 측 구현 (C# API)

```csharp
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Dapper;
using System.Data.SqlClient;

[ApiController]
[Route("api/[controller]")]
public class DataController : ControllerBase
{
    private readonly string _connectionString = "your_connection_string";

    [HttpGet]
    public IActionResult GetData()
    {
        using (var connection = new SqlConnection(_connectionString))
        {
            connection.Open();
            
            // Dapper로 쿼리 실행 - dynamic 타입으로 반환
            var result = connection.Query("SELECT Id, Name, Value FROM YourTable").ToList();
            
            // JSON으로 반환 (ASP.NET Core가 자동으로 직렬화)
            return Ok(result);
        }
    }
}
```

## 클라이언트 측 구현 (List<dynamic>과 ExpandoObject 활용)

```csharp
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading.Tasks;

public class Program
{
    public static async Task Main()
    {
        // HTTP 클라이언트 생성
        using var httpClient = new HttpClient();
        httpClient.BaseAddress = new Uri("https://your-api-url/");
        
        // API 호출 및 결과 받기
        var response = await httpClient.GetAsync("api/data");
        response.EnsureSuccessStatusCode();
        
        // 응답을 List<ExpandoObject>로 변환
        var jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
        var expandoList = await response.Content.ReadFromJsonAsync<List<ExpandoObject>>(jsonOptions);
        
        // List<dynamic>으로 처리
        List<dynamic> dynamicList = expandoList.Cast<dynamic>().ToList();
        
        // 동적 객체 처리 예시
        foreach (dynamic item in dynamicList)
        {
            Console.WriteLine($"Id: {item.Id}, Name: {item.Name}, Value: {item.Value}");
            
            // 동적으로 속성 추가
            ((IDictionary<string, object>)item)["NewProperty"] = "새 값";
            
            // 속성 존재 여부 확인
            var expandoDict = (IDictionary<string, object>)item;
            if (expandoDict.ContainsKey("Name"))
            {
                Console.WriteLine($"Name 속성 값: {expandoDict["Name"]}");
            }
        }
    }
}
```

## ExpandoObject 활용 방법

ExpandoObject는 System.Dynamic 네임스페이스에 있는 클래스로, 런타임에 동적으로 속성을 추가하거나 제거할 수 있습니다.

1. **속성 추가/수정**:
   ```csharp
   ((IDictionary<string, object>)dynamicObj)["PropertyName"] = value;
   ```

2. **속성 접근**:
   ```csharp
   var value = dynamicObj.PropertyName; // 동적 접근
   // 또는
   var value = ((IDictionary<string, object>)dynamicObj)["PropertyName"]; // 딕셔너리 접근
   ```

3. **속성 존재 여부 확인**:
   ```csharp
   bool exists = ((IDictionary<string, object>)dynamicObj).ContainsKey("PropertyName");
   ```

4. **속성 제거**:
   ```csharp
   ((IDictionary<string, object>)dynamicObj).Remove("PropertyName");
   ```

이러한 방식으로 서버에서 Dapper로 조회한 데이터를 클라이언트에서 동적으로 처리할 수 있습니다. 특히 스키마가 자주 변경되거나 다양한 형태의 데이터를 처리해야 할 때 유용합니다.