관리 메뉴

웹개발자의 기지개

[ASP.NET Core MVC] 두개의 Select문 만들기 (시도, 구군 주소 선택하기) - 비동기식(Async), Entity Framework 이용 본문

ASP.NET/ASP.NET Core

[ASP.NET Core MVC] 두개의 Select문 만들기 (시도, 구군 주소 선택하기) - 비동기식(Async), Entity Framework 이용

http://portfolio.wonpaper.net 2022. 12. 14. 00:17

[ASP.NET Core MVC] 두개의 Select문 만들기 (시도, 구군 주소 선택하기) - Dapper 이용

 

[ASP.NET Core MVC] 두개의 Select문 만들기 (시도, 구군 주소 선택하기) - Dapper 이용

실무에서 자주 접하는 시도 , 구군 선택하는 입력폼이다. 우선 주소관련 DB 이다. 필자의 깃허브에 올려놓았다. (시도, 구군, 구군동) https://github.com/wonpaper/sido_gugun_dong GitHub - wonpaper/sido_gugun_dong:

wonpaper.tistory.com

 

Dapper 를 이용한 방식은 앞전 포스팅에서 올려놓았는데, 

이번에는 EF 를 이용해서 동일한 방식으로 만들어 보았다.

 

 

[ appsettings.json ] - DB 연결자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
  "Logging": {
    "LogLevel": {
      "Default""Information",
      "Microsoft""Warning",
      "Microsoft.Hosting.Lifetime""Information"
    }
  },
  "AllowedHosts""*",
  "ConnectionString": {
    "AppDb""Server=DESKTOP-ASIDMUS;Database=MyDB2;Trusted_Connection=True;MultipleActiveResultSets=true"
  },
  "ConnectionStrings""Server=DESKTOP-ASIDMUS;Database=IdeaApp;Trusted_Connection=True;MultipleActiveResultSets=true"
}
 
cs

10라인과 11라인의 MyDB2 디비를 연결하여 작업한다.

 

[  Startup.cs ]   - 닷넷3.1 버전으로 예제가 진행함으로 이 파일에서 환경설정한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using MyMVCTest1.Models;
using MyMVCTest1.Models.AddressPkg;
using MyMVCTest1.Models.Buyers;
using MyMVCTest1.Models.Context;
using MyMVCTest1.Models.Product;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
 
namespace MyMVCTest1
{
    public class Startup
    {
        private IWebHostEnvironment environment;
 
        //private IConfiguration config = null;
        public Startup(IWebHostEnvironment environment, IConfiguration configuration)
        {
            this.environment = environment;
            Configuration = configuration;
            //this.config = configuration;
        }
 
        public IConfiguration Configuration { get; set; }
 
        public void ConfigureServices(IServiceCollection services)
        {
            // 세션추가
            services.AddSession(options => { 
                // 세션 유지 시간
                options.IdleTimeout = TimeSpan.FromSeconds(30);
            });
 
            services.AddControllersWithViews();
 
            services.AddSingleton<IConfiguration>(Configuration);
 
            services.AddDbContext<AppDbContext>(options => options.UseSqlServer(Configuration.GetSection("ConnectionString").GetSection("AppDb").Value));
 
 
            // AddressSido 종속성 주입
            services.AddTransient<IAddressSido, AddressSidoRepository>();
 
            // AddressSido 종속성 주입 (EF) - 비동기방식
            services.AddTransient<IAddressSidoEF, AddressSidoRepositoryEF>();
        }
 
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }
            app.UseHttpsRedirection();
            app.UseStaticFiles();
 
            app.UseRouting();
 
            app.UseAuthorization();
 
            // 세션사용
            app.UseSession();
 
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}
 
cs

46라인에서 DbContext 를 서비스에 추가한다.

services.AddDbContext<AppDbContext>(options => options.UseSqlServer(Configuration.GetSection("ConnectionString").GetSection("AppDb").Value)); 

 

52,53라인에서 이번에는 EF 형태로 서비스를 등록하여준다. (종속성주입)

 

// AddressSido 종속성 주입 (EF) - 비동기방식
services.AddTransient<IAddressSidoEF, AddressSidoRepositoryEF>();

 

 

EF를 위해 실제 DbContext 를 만든다.

 

필자는 별도의 새프로젝트로 MyMVCTest1.Models 클래스 라이브러리를 만들고, 

이안에 폴더별로 구분하여 모아놓았다.

 

 

[ MyMVCTest1.Models.Context / AppDbContext.cs ]  

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using Microsoft.EntityFrameworkCore;
using MyMVCTest1.Models.AddressPkg;
using MyMVCTest1.Models.EF;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text;
 
namespace MyMVCTest1.Models.Context
{
    public class AppDbContext : DbContext
    {
        public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
        {
 
        }
 
        public DbSet<AddressSido> AddressSidos { get; set; }
 
    }
}
cs

 

 

[ MyMVCTest1.Models.AddressPkg / AddressSido.cs ] - 기본 테이블 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text;
 
namespace MyMVCTest1.Models.AddressPkg
{
    [Table("zipcode1")]
    public class AddressSido
    {
        [Key]
        [Column("no")]
        public int Id { get; set; }
        public string Si { get; set; }
        public string Gu { get; set; }
    }
}
 
cs

9 라인처럼 테이블명을 zipcode1 로 데코레이션해 놓았다.

그리고, 13 라인처럼 클래스에서는 public int Id 이지만 실제 테이블에는 'no' 칼럼명이라 이를 데코레이션 처리해 놓았다.

 

 

[  MyMVCTest1.Models.AddressPkg / IAddressSidoEF.cs ] - 인터페이스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
 
namespace MyMVCTest1.Models.AddressPkg
{
    public interface IAddressSidoEF
    {
        Task<List<string>> GetAllAsync();
        Task<List<AddressSido>> GetAllAsync(string si);
        Task<AddressSido> GetAsync(string si, string gu);
    }
}
 
cs

 

[  MyMVCTest1.Models.AddressPkg / AddressSidoRepositoryEF.cs ]  - 리포지터리 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using Microsoft.EntityFrameworkCore;
using MyMVCTest1.Models.Context;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
 
namespace MyMVCTest1.Models.AddressPkg
{
    public class AddressSidoRepositoryEF : IAddressSidoEF
    {
        private readonly AppDbContext db = null;
        public AddressSidoRepositoryEF(AppDbContext db)
        {
            this.db = db;
        }
 
        public async Task<List<string>> GetAllAsync()
        {
 
            // select distinct(si) from zipcode1
            return await db.AddressSidos.Select(s => s.Si).Distinct().ToListAsync();              // return Task<List<string>>
 
        }
 
        public async Task<List<AddressSido>> GetAllAsync(string si)
        {
            // select * from zipcode1 where si='서울' --> 강동구, 서초구 ... (gu)
            return await (from num in db.AddressSidos.Where(s => s.Si == si) select num).ToListAsync();
        }
 
        public async Task<AddressSido> GetAsync(string si, string gu)
        {
            return await db.AddressSidos.SingleOrDefaultAsync(s => s.Si == si && s.Gu == gu);
        }
    }
}
 
cs

15라인 처럼 생성자 종속성 주입을 AppDbContext 를 넣어 db 형태로 어디서든지 이용하면 된다.

본 예제는 async 형태로 비동기 기법으로 코딩해 보았다.

그래서, 관련 메소드 마다. async Task<> 와  await 단어들이 보인다. 

 

그릭고, 20라인의 async Task<List<string>> GetAllAsync()  처럼 List<string> 형태로  string 형임을 주의하자.

zipcode 에서 distinct 하여 시도 부분을 묶어버리면 string List 형태가 되도록했다.

 

이를 24라인처럼

return await db.AddressSidos.Select(s => s.Si).Distinct().ToListAsync(); 

LINQ 기법으로 코딩해 보았다.

 

 

 

 

이번에는 실제 Controller 와 View 파일들을 만들어보자.

 

[ Controllers / AddressPkgEFController.cs ] - Controller

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
using Microsoft.AspNetCore.Mvc;
using MyMVCTest1.Models.AddressPkg;
using System.Threading.Tasks;
 
namespace MyMVCTest1.Controllers
{
    public class AddressPkgEFController : Controller
    {
        private IAddressSidoEF addressSidoRepositoryEF;
        public AddressPkgEFController(IAddressSidoEF addressSidoRepositoryEF)
        {
            this.addressSidoRepositoryEF = addressSidoRepositoryEF;
        }
 
        [HttpGet]
        public async Task<IActionResult> ListType1(string sido)
        {
            if (sido == null) {
                var sidoList = await addressSidoRepositoryEF.GetAllAsync();
                ViewBag.SidoList = sidoList;
            } else {
                var sidoList = await addressSidoRepositoryEF.GetAllAsync();
                var gugunList = await addressSidoRepositoryEF.GetAllAsync(sido);
                ViewBag.SidoList = sidoList;
                ViewBag.GugunList = gugunList;
            }
            return View();
        }
 
    }
}
 
cs

 

16 라인처럼 HttpGet 방식으로 ListType1 확장메소드에서 실제 View 파일에서 sido(시도값) 이 있을때 없을때로 

구분하여 마술상자인(?)  ViewBag  에 각각 생성하여 넣어 주고 있다.

 

ViewBag 은 실제 ListType1.cshtml 페이지에서 시도와 구군 Select 박스에서 Foreach 형태로 뿌려준다.

 

 

[  Views / AddressPkgEF / ListType1.html ] - Vivew 페이지

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@using MyMVCTest1.Models.AddressPkg
@using Microsoft.AspNetCore.Http
 
<form name="f" method="post">
 
    <h2>주소 (시도 / 구군)</h2>
    <br />
 
    <label for="sido">시도</label>
    <select id="sido" name="sido" class="form-control" onchange="sidoChg()">
        <option value="">= 시도 =</option>
        @foreach (var sido in ViewBag.SidoList)
        {
            @if (Context.Request.Query["sido"].ToString() == sido)
            {
                <option value="@sido" selected>@sido</option>
            }
            else
            {
                <option value="@sido">@sido</option>
            }
        }
    </select>
 
    <br /><br />
 
    <label for="gugun">구군</label>
    <select id="gugun" name="gugun" class="form-control">
        @if (ViewBag.GugunList == null)
        {
            <option value="">= 구군 =</option>
        }
        else
        {
            @foreach (var gugun in ViewBag.GugunList)
            {
                <option value="@gugun.Gu">@gugun.Gu</option>
            }
        }
    </select>
 
</form>
 
<script>
    function sidoChg() {
        var form = document.f;
        var sido = form.sido.value;
        location.href = "?sido=" + sido;
    }
</script>
cs

 

 

조은 참고 사이트1 : https://www.learnentityframeworkcore.com/dbset/querying-data

조은 참고 사이트2 : https://www.simplilearn.com/tutorials/asp-dot-net-tutorial/c-hash-linq-distinct

조은 참고 사이트3 : 

https://www.codeproject.com/Articles/535374/DistinctBy-in-Linq-Find-Distinct-object-by-Propert

Comments