반응형

ET Core 간단하게 알아보기

데이터 모델링은 3가지 방법으로 가능하다.

-Convention(관례)

-Data Annotation(데이터 주석)

-Fluent API(직접 정의)

1. 관례의 경우에는 Id나 [클래스]Id가 붙을 경우 자동적으로 priamry key로 인식이 된다.

2. Attribute 즉, 속성을 활용하여 가능하다.

ex)

public int Id{ {get; set;}

[MaxLength(32)]

public string UserName {get; set;}

3. API를 사용하여 직접정의한다.

protected overrid void OnModelCreationg(ModelBuilder builder)

{

builder.Entity<GameResult>().Properity(x => x.UserName).ISUnicode(false);

}

지금까지는 1번 즉 관례의 방식으로 데이터 모델링을 사용했으나

2번과 3번도 유용하게 사용되는 경우가 있기에 모든 경우에 대하여 간단하게 알아보고자 한다.

[1]Entity Class에 대한 관례

정의) EF가 참조하는 클래스를 Entity Class라고 한다.

관례)

-public

-static이면 안됨

-생성자가 없거나, 인자 없는 생성자가 있어야 함

-id혹은 [클래스]id 형태의 property를 테이블의 Primary Key로 간주한다.

Column에 대한 관례

관례)

-property의 이름이 테이블 컴럼 이릅으로 사용

-propery의 타입을 해당하는 SQL타입으로 변환

-C# 형식의 기본 nullable이 테이블 컬럼 nullable에 영향

[2]Data Annotation

Blazor의 Form 검증 때 사용한 모든 Annotation이 모두 해당한다.

ex) Required, MaxLength...

[3] FLUENT API

Convention이나 Data Annotation으로 할 수 없는 모든 경우

Fluent Api로 처리가 가능하다.

ex)

protected override void OnModelCreating(ModelBuilder builder)

{

builder.Entity<GameResult>().Property(x => x.UserName).IsUnicode(false);

//UserId를 Index로 추가하는 코드 Has라 헷갈릴수 있지만 추가하는 코드이다.

builder.Entity<GameResult>().HasIndex(x => x.UserId);

//Score가 1000을 넘는 경우에만 필터링을 처리해서 추출하는 코드

builder.Entity<GameResult>().HasQueryFilter(p => p.Score > 1000);

}

이번엔 DB에서 특정 부분만을 제거해도록 하겠다.

Data Annotation에서는 [NotMapped] 속성을 사용하면 된다.

이를 만약에 class에다가 사용할 경우 DB생성에서 제외가 된다.

그리고 Fluent Api에서 사용할 경우

builder.Entity<GameResult>().Ignore(g => g.Excluded);

를 사용하면 된다.

다음으로 DB의 칼럼에서 TYPE과 SIZE 그리고 NULL여부를 제어하고 싶은 경우를 알아보겠다.

[Data Annotation의 경우]

Not null => [Required]

Set String Size => [MaxLength(123)]

Set String VarChar => X

[Fluent Api의 경우]

Not null => .IsRequired();

Set String Size => .HasMaxLength(123)

Set String Varchar => .IsUnicode(false)

테이블의 Primary Key를 설정하는 방식을 알아보겠다.

이 경우에는 위의 3가지 방식이 모두 가능하다.

[관례의 경우]

property의 이름에 Id가 들어가 있으면 기본 키로 인식해준다.

[속성의 경우]

[Key] 속성 값을 프로퍼티위에 붙여준다.

만약 [Key]의 값이 2개 이상의 프로퍼티에 지정되어 있는 복합 키라면

복합 키로 사용되므로 [Column(Order =0)]의 속성을 붙여서

순서를 지정해준다.

[Api의 경우]

.HasKey()로 지정해준다.

다음으로 테이블의 Index를 설정하는 경우는

Fluent Api만 가능하다.

builder.Entity<GameResult>().HasIndex(x => x.UserId);

그리고 테이블의 이름을 설정하는 것은 관례,속성,Api 모든 경우가 가능하다.

[관례]

public DbSet<GameResult> GameResults { get; set: }

[속성]

[Table("GameResultTable")] 을 class위에 붙여준다.

[Api]

builder.Entity<GameResult>().ToTable("GameResultTable");

마지막으로 결론을 정리해보자면

1)기본 상태로 충분하다면 관례방식으로

2)속성으로 가능하다면, 속성방식을 무조건 사용하자!

속성은 낮우에 Blazor Form Validation등 다른 곳에서도 재사용이 가능하기 때문이다.

3)위의 2가지 경우가 안되는 정말 절망적인 상황이라면 Fluent Api를 사용하자!

ex) Index관련...

 

반응형
반응형

REST ( Representational State Trasnfer) 공식표준 스펙은 아닌데

원래 있던 HTTP 통신에서  재사용 하여 데이터 송수신 규칙을 만든것 => CRUD

 

 

CRUD 를 Blazor API 서버를 활용하여 작업 한것 

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using SharedData.Models;
using System.Collections.Generic;
using System.Linq;
using WebAPI.Data;

namespace WebAPI.Controllers
{
    //www.google.com 이건 전체 주소
    //www.google.com/api/ranking   이건 세부 라우팅 주소로 전체 주소에서 구분지어 들어갈 때 사용됨
    //그 다음 어떤걸 하는지 지정 할 수 있다 GET, POST, PUT...)
    //Create : POST 방식 api/ranking => body 에 실제 정보를 담는다 , 아이템 생성 요청

    //Read 시에는 보통 Get 을 사용한다
    //GET : api/ranking => 전체 아이템을 가져온다 , api/ranking/1 id가 1인 아이템을 가져온다
    //get /api/ranking  모든 id를 주세요
    //get /api/ranking/1   id 중에서 1전을 주세요 라는 요청 
    //get 은 body 에 정보를 넣지 않고 이 요청 하나로 처리 된다

    //update : put 으로 사용됨
    //PUT : api/ranking  (put은 보안 무제로 일반적인 웹에서 사용되지 않는다)
    //put 은 body 에 정보를 넣어서 요청을 보낸다

    //delete /api/ranking/1   이렇게 삭제 할 수 있는데 보안 문제로 웹에서 쓰지 않는다
    //id=1 번인 아이템 삭제 요청

    //api controller 는 c# 객체를 반환하는 것이 가능하다
    //null 반환하면 클라에서 204 Response (No Content) 를 받는다
    //String을 반호나하면 => text/plain 타입으로 반환한다
    //나머지는 json 형태로 반환한다

    //localhost:portnumber/api/ranking 이 요청이라는 얘기인데 RankingController 이것이 [controller] 부분에서 ranking 으로 변환됨
    [Route("api/[controller]")]
    [ApiController]
    public class RankingController : ControllerBase
    {
        ApplicationDbContext _context;
        public RankingController(ApplicationDbContext context)
        {
                _context = context;

        }

        //create, body 에 보낼때는 [frombody 를 넣어주면 된다]
        [HttpPost]
        public GameResult AddGameResult([FromBody] GameResult gameResult)
        {
            _context.GameResultList.Add(gameResult);
            _context.SaveChanges();
            return gameResult;
        }


        //update
        [HttpPut]
        public bool UpdateGameResult([FromBody] GameResult gameResult)
        {
            var findResult = _context.GameResultList.Where(item => item.Id == gameResult.Id).FirstOrDefault();
            if(findResult == null)
            {
                return false;
            }

            findResult.UserName = gameResult.UserName;
            findResult.Score = gameResult.Score;
            _context.SaveChanges();

            return true;
        }

        //read
        //ranking
        [HttpGet]
        public List<GameResult> GetGameResults()
        {
            List<GameResult> results = _context.GameResultList.OrderByDescending(item=> item.Score).ToList();
            return results;
        }

        //ranking/1
        [HttpGet("{id}")]
        public GameResult GetGameResult(int id)
        {
            GameResult result = _context.GameResultList.Where(item => item.Id == id).FirstOrDefault();
            return result;
        }


        //delete
        [HttpDelete("{id}")]
        public bool DeleteGameResult(int id)
        {
            var findResult = _context.GameResultList.Where(item => item.Id == id).FirstOrDefault();
            if (findResult == null)
            {
                return false;
            }
            _context.GameResultList.Remove(findResult);
            _context.SaveChanges();

            return true;
        }
    }
}

 

아래 예시는 모든 데이터를 json 으로 갖고 오는 예시이다

반응형
반응형

우선 테이블 명을 GameResultList 로 변경하였다

 

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Migrations;
using RankingApp.Data.Models;
using System;
using System.Collections.Generic;
using System.Text;

namespace RankingApp.Data
{
    public class ApplicationDbContext : IdentityDbContext
    {
        //DbSet 으로 추가해줘야지 GameReesult.cs 에서 GameResultList 내용을 DB에 저장할 수 있다. ORM
        //즉 서버를띄울때 자동으로 DB를 갱신하여 띄워주게 된다
        //PM> add-migration RankingService  명령어르 NeGet 콘솔에서 실행하여 DB를 갱신해준다
        //PM> update-database 명령어르 NeGet 콘솔에서 실행하여 DB를 갱신해준다
        //주의!! GameResultList 이 테이블 명과 동일해야 한다 dbo.GameResultList 에서 dbo 빼고   GameResultList 만
        public DbSet<GameResult> GameResultList { get; set; }

        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }
    }
}

 

그런다음 패키지 관리자 콘솔에서 다음 처럼 입력한다음 다음 처럼 적용한다

PM> Add-Migration GameResultList

PM> Update-Database

이 처럼 마이그레이션이 변경된다

테이블명도 자동으로 바뀌게 된다

 

서비스 쪽에 갱신과 삭제에 대한 로직을 추가 하고 테이블에도 값이 삭제/갱신이 적용 될수 있게 처리 한다

       public Task<bool> UpdateGameResult(GameResult gameResult)
        {
            var result = _context.GameResultList.Where(x => x.Id == gameResult.Id).FirstOrDefault();
            if(result==null)
            {
                return Task.FromResult(false);
            }
            result.UserName = gameResult.UserName;
            result.Score = gameResult.Score;
            _context.SaveChanges();

            return  Task.FromResult(true);
        }

        public Task<bool> DeleteGameResult(GameResult gameResult)
        {
            var result = _context.GameResultList.Where(x => x.Id == gameResult.Id).FirstOrDefault();
            if (result == null)
            {
                return Task.FromResult(false);
            }
            _context.GameResultList.Remove(gameResult);
            _context.SaveChanges();
            return Task.FromResult(true);
        }

 

 

Ranking.razor 

갱신과 삭제 버튼이 보여 내용을 수정 할수 있도록 하고

추가팝업과 갱신 팝업은 동일한 형태로 사용한다

그리고 로그인이 되어 있지 않다면 유저 정보는 보이지 않도록 Autorized, NotAutorized 처리를 해 놓았다

@page "/ranking"
@using RankingApp.Data.Models;
@using RankingApp.Data.Services;

@inject RankingService RankingService

<h3>Ranking</h3>

<AuthorizeView>
    <Authorized>
        
        @if (_gameResultList == null)
        {
            <p>Loading...</p>
        }else{
            <table class="table">
                <thead>
                    <tr>
                        <th>UserName</th>
                        <th>Score</th>
                        <th>Date</th>
                        <th></th>
                        <th></th>
                    </tr>
                </thead>
                <tbody>
                    @foreach (var gameResult in _gameResultList)
                    {
                        <tr>
                            <td>@gameResult.UserName</td>
                            <td>@gameResult.Score</td>
                            <td>@gameResult.Date</td>
                            <td>
                                <button class="btn btn-primary" @onclick="() => EditGameResult(gameResult)">
                                    Edit
                                </button>
                            </td>
                            <td>
                                <button class="btn btn-primary" @onclick="() => DeleteGameResult(gameResult)">
                                    Delete
                                </button>
                            </td>
                        </tr>
                    }
                </tbody>
            </table>
            <p>
                <!-- make button to add gameresult -->
                <button class="btn btn-primary" @onclick="AddGameResult">
                    Add
                    </button>
            </p>


            @if(_showPopup)
            {
                //dialog to show gameresult
                <div class="modal" style="display:block" role="dialog">
                    <div class="modal-dialog">
                        <div class="modal-content">
                            <div class="modal-header"></div>
                            <h3 class="modal-title">Add/Update Game Result</h3>

                            <button type="button" class="close" @onclick="ClosePopup">
                                <span aria-hidden="true">X</span>
                            </button>
                            <div class="modal-body">
                                <label for="UserName"> UserName</label>
                                <input class="form-control" type="text" placeholder="UserName" @bind-value="_gameResult.UserName" />
                                <label for="Score"> Score</label>
                                <input class="form-control" type="text" placeholder="Score" @bind-value="_gameResult.Score" />

                                <button class="btn-primary" @onclick="SaveGameResult">Save</button>
                            </div>
                        </div>
                    </div>
                </div>
            
            }
        }

    </Authorized>
    <NotAuthorized>
        인증 안됨(로그인 안됨)
    </NotAuthorized>

</AuthorizeView>

@code {
    List<GameResult> _gameResultList;

    GameResult _gameResult;

    bool _showPopup = false;

    protected override async Task OnInitializedAsync()
    {
        //db 에서 데이터 긁어와서 _gameResults 에 넣어줌
        await readGameResultList();
    }

    public async Task<List<GameResult>> readGameResultList()
    {
        //db 에서 데이터 긁어와서 _gameResultList 에 넣어줌
        _gameResultList = await RankingService.GetGameResultAsync();
        return _gameResultList;
    }


    public void AddGameResult()
    {
        _showPopup = true;
        //Add new gameresult to
        _gameResult = new GameResult()
        {
            Id = 0
        };
    }

    //ClosePopup
    void ClosePopup()
    {
        _showPopup = false;
    }


    void EditGameResult(GameResult gameResult)
    {
        _showPopup = true;
        _gameResult = gameResult;
    }

    async Task DeleteGameResult(GameResult gameResult)
    {
        var result = RankingService.DeleteGameResult(gameResult);
        await readGameResultList();
    }


    async Task SaveGameResult()
    {
        //새로 만드는 상태
        if(_gameResult.Id==0)
        {
            //save to db
            _gameResult.Date = DateTime.Now;
            var result = RankingService.AddGameResult(_gameResult);
            //close popup
            ClosePopup();
        }
        else
        {
            //수정하고 있는 상태
            var result = RankingService.UpdateGameResult(_gameResult);
        }

        await readGameResultList();
        _showPopup = false;
    }


}

 

결과는 다음과 같고

 

Edit 버튼을 눌러 값 또한 수정 반영 할 수 있다

 

 

로그인 안했을대 로그인 안된다는 문자표시

반응형
반응형

 

랭킹 데이터를 추가 하고 다시 가져오는 코드이다

 

Ranking.razor

@page "/ranking"
@using RankingApp.Data.Models;
@using RankingApp.Data.Services;

@inject RankingService RankingService

<h3>Ranking</h3>

<AuthorizeView>
    <Authorized>
        
        @if (_gameResultLIst == null)
        {
            <p>Loading...</p>
        }else{
            <table class="table">
                <thead>
                    <tr>
                        <th>UserName</th>
                        <th>Score</th>
                        <th>Date</th>
                    </tr>
                </thead>
                <tbody>
                    @foreach (var gameResult in _gameResultLIst)
                    {
                        <tr>
                            <td>@gameResult.UserName</td>
                            <td>@gameResult.Score</td>
                            <td>@gameResult.Date</td>
                        </tr>
                    }
                </tbody>
            </table>
            <p>
                <!-- make button to add gameresult -->
                <button class="btn btn-primary" @onclick="AddGameResult">
                    Add
                    </button>
            </p>


            @if(_showPopup)
            {
                //dialog to show gameresult
                <div class="modal" style="display:block" role="dialog">
                    <div class="modal-dialog">
                        <div class="modal-content">
                            <div class="modal-header"></div>
                            <h3 class="modal-title">Add/Update Game Result</h3>

                            <button type="button" class="close" @onclick="ClosePopup">
                                <span aria-hidden="true">X</span>
                            </button>
                            <div class="modal-body">
                                <label for="UserName"> UserName</label>
                                <input class="form-control" type="text" placeholder="UserName" @bind-value="_gameResult.UserName" />
                                <label for="Score"> Score</label>
                                <input class="form-control" type="text" placeholder="Score" @bind-value="_gameResult.Score" />

                                <button class="btn-primary" @onclick="SaveGameResult">Save</button>
                            </div>
                        </div>
                    </div>
                </div>
            
            }
        }

    </Authorized>
    <NotAuthorized>
        인증 안됨(로그인 안됨)
    </NotAuthorized>

</AuthorizeView>

@code {
    List<GameResult> _gameResultLIst;

    GameResult _gameResult;

    bool _showPopup = false;

    protected override async Task OnInitializedAsync()
    {
        //db 에서 데이터 긁어와서 _gameResults 에 넣어줌
        await readGameResults();
    }

    public async Task<List<GameResult>> readGameResults()
    {
        //db 에서 데이터 긁어와서 _gameResults 에 넣어줌
        _gameResultLIst = await RankingService.GetGameResultAsync();
        return _gameResultLIst;
    }

    
    public void AddGameResult()
    {
        _showPopup = true;
        //Add new gameresult to
        _gameResult = new GameResult()
        {
            Id = 0
        };
    }

    //ClosePopup
    void ClosePopup()
    {
        _showPopup = false;
    }

    async Task SaveGameResult()
    {
        //새로 만드는 상태
        if(_gameResult.Id==0)
        {
            //save to db
            _gameResult.Date = DateTime.Now;
            var result = RankingService.AddGameResult(_gameResult);
            //close popup
            ClosePopup();
        }
        else
        {
            //수정하고 있는 상태
            
        }
        
        _gameResultLIst = await readGameResults();
    }
}

 

 

RankingService.cs

using RankingApp.Data.Models;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace RankingApp.Data.Services
{
    public class RankingService
    {
        ApplicationDbContext _context;

        public RankingService(ApplicationDbContext context)
        {
            _context = context;
        }

        public Task<List<GameResult>> GetGameResultAsync()
        {
            //DB 에서 GameResult 테이블의 모든 데이터를 가져온다
            List<GameResult> results = _context.GameResult.ToList();
            return Task.FromResult(results);
        }

        public Task<GameResult> AddGameResult(GameResult gameResult)
        {
            _context.GameResult.Add(gameResult);
            _context.SaveChanges(); //db 에 실제 저장이 됨
            return Task.FromResult(gameResult);
        }

    }
}

AddGameResult() 함수가 추가 되었고  

_context.GameResult.Add(gameResult);
_context.SaveChanges(); //db 에 실제 저장이 됨

이 코드가 실제 DB 에 저장하는 부분이 된다

이후 결과는 Task 로 리턴해주고 결과를 담을때는 FromResult 로 결과를 담아줘서 리턴하면 await 으로 결과를 기다 릴 수 있다

    public async Task<List<GameResult>> readGameResults()
    {
        //db 에서 데이터 긁어와서 _gameResults 에 넣어줌
        _gameResultLIst = await RankingService.GetGameResultAsync();
        return _gameResultLIst;
    }

 

 

 

 

유저를 하나 추가하면 다음처럼 보이게 된다 (test1 유저 추가)

DB 데이터에 추가된 내용

반응형
반응형

Blazor Server 생성 단계에서 인증 유형을 개별 계정으로 선택한다

이러면 가입, 로그인 등을 할 수 있게 된다

 

RankingDB 이름으로 DB를 하나 추가 한다

그리고 appsettings.json 부분에서 다음 처럼 변경한다

{
  "ConnectionStrings": {
    //"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-RankingApp-b997cf13-bc70-49dd-9313-05e01b61b6ab;Trusted_Connection=True;MultipleActiveResultSets=true"
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=RankingDB;Trusted_Connection=True;MultipleActiveResultSets=true"

  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

DefaultConnectino 부분에서 Database=RankingDB 로 변경해준다

PM> add-migration anyName 으로 DB를 마이그레이션 할때 DefaultConnection 에 잇는 Database=RankingDB 

RankingDB 이 이름으로 DB 가 생성되게 된다

 

 

 

먼저 최종 작업한 화면은 다음과 같다

여기서 Entity = EntityFramework 는 ORM 같은 것이다

 

기본 Blazor Server 에서 Ranking 부분을 추가 한것이도 Fetch data 와는 다르게 데이터는 db 에서 읽어온다 그래서 미리 DB 에 데이터를 넣어 놓어 놓는 것으로 시작한다

 

오른쪽 상단 Register 를 눌러 가입을 하나 하면 dbo.AspNetUsers 에 추저가 하나 추가 된다

그런 다음 EmailConfirmed 를 수동으로 true 로 변경한다 (이 부분은 이메일 확인 부분이다)

유저는 기본으로 제공된느 db 를 사용한다

 

 

GameResult.cs 를 Modes 폴더를 하나 만들어추가한다(Services 로 마찬가지로 추가한다)

using System;

namespace RankingApp.Data.Models
{
    public class GameResult
    {
        public int Id { get; set; }
        public int UserID { get; set; }
        public string UserName { get; set; }
        public int Score { get; set; }
        public DateTime Date { get; set; }
    }
}

 

 

원래는 GameResult 테이블을 하나 추가해 다음 처럼 데이터를 추가해야 하지만 

이렇게 하지 않고 Entity 를 활용해 자동으로 테이블을 등록하게한다 => db에 대한 버전 관리도 되고 .cs 와 코드적으로 자동 연동 됨으로 이것이 편하다

 

ApplicationDbContext.cs 가 db 와 .cs 같에 연결해주는 핵심적인 부분이다

 

먼저 IdentityDbContext 를 상속받는다 

DbSet<GameResult> 이 부분이 DB와 코드의 연결 부분이다

IdentityDbContext : Base class for the Entity Framework database context used for identity.

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Migrations;
using RankingApp.Data.Models;
using System;
using System.Collections.Generic;
using System.Text;

namespace RankingApp.Data
{
    public class ApplicationDbContext : IdentityDbContext
    {
        //DbSet 으로 추가해줘야지 GameReesult.cs 에서 GameResult 내용을 DB에 저장할 수 있다. ORM
        //즉 서버를띄울때 자동으로 DB를 갱신하여 띄워주게 된다
        //PM> add-migration RankingService  명령어르 NeGet 콘솔에서 실행하여 DB를 갱신해준다
        //PM> update-database 명령어르 NeGet 콘솔에서 실행하여 DB를 갱신해준다
        public DbSet<GameResult> GameResult { get; set; }

        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }
    }
}

이렇게 추가해주고 

 

Startup.cs 에서 

public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("DefaultConnection")));

이 부분을 통해 DB 로 연결하게 된다 (Identity 를 사용하기 때문에 이 부분이 자동으로 등록 되어 있긴 하지만 안되어 있으면 추가해준다)

 

 

 

Nuget 패키지 관리자 > 패키지 관리자 콘솔 에서 다음 명령어들을 실행한다

 

PM> add-migration RankingService  명령어로 NeGet 콘솔에서 실행하여 DB를 마이그레이션 해준다
PM> update-database 명령어로 NeGet 콘솔에서 실행하여 DB를 업데이트해준다

 

이렇게 까지 하면 GameResult DB 가 자동으로 만들어진다

그럼 다음과 같이 Migratoinos 폴더에 코드로 옮겨진 것을 볼 수 있다

20230408233459_RankingService.cs (자동 생성된 파일)

using System;
using Microsoft.EntityFrameworkCore.Migrations;

namespace RankingApp.Data.Migrations
{
    public partial class RankingService : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.CreateTable(
                name: "GameResult",
                columns: table => new
                {
                    Id = table.Column<int>(nullable: false)
                        .Annotation("SqlServer:Identity", "1, 1"),
                    UserID = table.Column<int>(nullable: false),
                    UserName = table.Column<string>(nullable: true),
                    Score = table.Column<int>(nullable: false),
                    Date = table.Column<DateTime>(nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_GameResult", x => x.Id);
                });
        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropTable(
                name: "GameResult");
        }
    }
}

즉 GameResult 를 알아서 해석하여 테이블을 만드는 코드가 된다

Up 일때는 들어가고 Down 일때는 Drop 하게된다 (버전 관리 개념)

 

이렇게 하지 않으면 DB 에서 Field 를 변경하거나 코드에서 Field 명등 수정 사항이 안맞는 상황들이 발생 할수 있기 때문에 번거로운 작업이 계속 될 수 있는데 이것을 코드수준에서 DB로 맞춰만들어주기 때문에 작업의 효율을 높여준다

이에 더해 버전 관리 또한 가능해지게 된다

 

 

 

데이터는 우선 수동으로 다음처럼 추가한다

 

데이터를 읽어오는 코드를 먼저 작업하자

 

그러기 위해서  ApplicationDbContext.cs 에서 다음 처럼 

DbSet 을 통해서 DB 와 연결된 부분을 읽어와야 하는데 이미 위에서 다음과 같은 코드를 통해 연결을 해놨으니

public DbSet<GameResult> GameResult { get; set; }

 

RankingService.cs 서비스를 만들어 데이터를 가져올 수 있는 코드를 만들어 준면 된다

using RankingApp.Data.Models;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace RankingApp.Data.Services
{
    public class RankingService
    {
        ApplicationDbContext _context;

        public RankingService(ApplicationDbContext context)
        {
            _context = context;
        }

        public Task<List<GameResult>> GetGameResultAsync()
        {
            //DB 에서 GameResult 테이블의 모든 데이터를 가져온다
            List<GameResult> results = _context.GameResult.ToList();
            return Task.FromResult(results);
        }

    }
}

이 코드를 통해 db 에 있는 GameResult 테이블 내용을 읽어올 수 있게 된다

 

그리고 RankingService 를 사용하기 위해서 Startup.cs 에 AddScoped 로 RankigService 를 추가해준다

  public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("DefaultConnection")));
            services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
                .AddEntityFrameworkStores<ApplicationDbContext>();
            services.AddRazorPages();
            services.AddServerSideBlazor();
            services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>();
            services.AddSingleton<WeatherForecastService>();
            services.AddScoped<RankingService>();
        }

 

AddScoped() 이 내용은 (https://3dmpengines.tistory.com/search/AddScoped) 이 글을 참고

 

 

 

이제 이 내용을 Blazor 를 통해서 보여주면 된다

 

Ranking.razor

@page "/ranking"
@using RankingApp.Data.Models;
@using RankingApp.Data.Services;

@inject RankingService RankingService

<h3>Ranking</h3>

@if (_gameResults == null)
{
    <p>Loading...</p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>UserName</th>
                <th>Score</th>
                <th>Date</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var gameResult in _gameResults)
            {
                <tr>
                    <td>@gameResult.UserName</td>
                    <td>@gameResult.Score</td>
                    <td>@gameResult.Date</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    List<GameResult> _gameResults;

    protected override async Task OnInitializedAsync()
    {
        //db 에서 데이터 긁어와서 _gameResults 에 넣어줌
        _gameResults = await RankingService.GetGameResultAsync();
    }

}

이 페이지를 보여주기 위해 탭을 하나 추가한다

 

이때 상단에

@inject RankingService RankingService

이 코드를 통해 RankingService 를 사용 할수 있도록 Dependency injection 해준다

 

 

NavMenu.razor

<div class="top-row pl-4 navbar navbar-dark">
    <a class="navbar-brand" href="">RankingApp</a>
    <button class="navbar-toggler" @onclick="ToggleNavMenu">
        <span class="navbar-toggler-icon"></span>
    </button>
</div>

<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
    <ul class="nav flex-column">
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> Home
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="counter">
                <span class="oi oi-plus" aria-hidden="true"></span> Counter
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="fetchdata">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="ranking">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Ranking data
            </NavLink>
        </li>
    </ul>
</div>

@code {
    private bool collapseNavMenu = true;

    private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;

    private void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
    }
}

 

그럼 다음과 같은 결과를 볼 수 있다

 

반응형
반응형

Blazor 앱은 .NET 메서드에서 JavaScript함수를 호출하고, JavaScript함수에서 .NET메서드를 호출할 수 있다.

이것을 JavaScript interop이라고 한다. 

 

Blazor의 .NET 메서드에서 JavaScript 함수 호출

1. index.html body 태그안에 script를 추가한다. 

    <div id="app">Loading...</div>

    <div id="blazor-error-ui">
        An unhandled error has occurred.
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>
    <script src="_framework/blazor.webassembly.js"></script>
    <script>
        window.showAlert = (comment) => {
            alert(comment);
        };
    </script>

2. index.razor에서 버튼을 클릭하면 showAlert 함수를 호출하도록 한다.

  • .NET에서 JS를 호출하려면 IJSRuntime 추상화를 삽입하고 InvokeVoidAsync 함수를 호출한다.
@page "/"
@inject IJSRuntime JS

<h1>Hello, world!</h1>

Welcome to your new app.


<p>
		<button @onclick="ShowAlert">알림창 띄우기</button>
</p>

<SurveyPrompt Title="How is Blazor working for you?" />

@code {
		private async Task ShowAlert()
		{
				await JS.InvokeVoidAsync("showAlert", "Hello, world!");
		}
}

3. 알림창 띄우기 버튼을 클릭하면 alert 을 확인할 수 있다. 

showAlert 자바스크립트 함수를 index.html에서 별도의 파일로 옮겨보자. => JS isolation

1. wwwroot/js/script.js 에 아래 함수 저장

export function showAlert(comment) {
    alert(comment);
}

2. index.razor 수정

IJSInProcessObjectReference는 함수를 동기적으로 호출할 수 있는 JS 개체에 대한 참조를 나타냅니다.

@page "/"
@inject IJSRuntime JS

<h1>Hello, world!</h1>

Welcome to your new app.


<p>
		<button @onclick="ShowAlert">알림창 띄우기</button>
</p>

<SurveyPrompt Title="How is Blazor working for you?" />

@code {

		private IJSObjectReference module;

		protected override async Task OnAfterRenderAsync(bool firstRender)
		{
				if (firstRender)
				{
						module = await JS.InvokeAsync<IJSObjectReference>("import",
								"./js/scripts.js");
				}
		}

		private async Task ShowAlert()
		{
				await module.InvokeVoidAsync("showAlert", "Hello, world!");
		}
}

3. 알림창 띄우기 버튼을 클릭하면 alert 을 확인할 수 있다. 

 

 

ref : https://bigexecution.tistory.com/49

반응형
반응형

Blazor 서버 기본 구조

Dependency Injection, Router, _imports, Layout 등을 알아본다

 

 

 

Dependency Injection

이를 간단하게 알아보기 위해서 이미 존재하고있던 Data폴더에 FoodService.cs를 생성한다.

이후 이 안에 Food class를 생성해주고 프로퍼티값으로 Name, Price를 선언해준다.

그리고 cs파일을 생성하면서 생성된 FoodService class안에

public List<Food> GetFoods()
{
List<Food> foods = new List<Food>()
{
new Food() {Name = "bap", Price = 7000},
new Food() {Name = "KimBap", Price = 3000},
new Food() {Name = "ChoBap", Price = 9000}
};
return foods;
}

위와 같이 List<Food>를 반환해주는 함수를 생성해준다.

이후에 Blazor App을 생성하면서 존재하는 Index.razor파일안에 아래와 같은 코드를 추가해주면

웹 페이지에 하드코딩해서 넣어준 서비스의 목록들이 나오긴 한다.

 

@using BlazorApp.Data;

<div>
@foreach(var food in _foodService.GetFood())
{
<div>@food.Name</div>
<div>@food.Price</div>
}

</div>
@code{
FoodSerive _foodService= new FoodService();
}

다만, 위와 같이 코드를 작성하게 된다면 코드간의 의존성이 너무 심해진다는 문제가 발생한다.

따라서 Interface을 활용하여 이를 해결해주자.

다시 FoodService.cs로 돌아와서 Interface를 선언해주자 이렇게 해주면 이 FoodService를 사용하고싶은

사람들은 이 interface를 구현해야한다는 규칙이 생기게 된다.

public interface IFoodService
{
IEnumeralbe<Food> GetFoods();
}

public class FoodService : IFoodService
{
	public IEnumeralbe<Food> GetFoods()
	{
		List<Food> foods = new List<Food>()
		{
		new Food() {Name = "bap", Price = 7000},
		new Food() {Name = "KimBap", Price = 3000},
		new Food() {Name = "ChoBap", Price = 9000}
		};
		return foods;
	}
}

하지만 아직까지 razor의 컴포넌트들마다 일일이 new FoodService()를 호출해야하는 문제가 존재한다.

이를 해결하기 위해서 Dependency Injection이다.

이는 Startup.cs안에 ConfigureServices 함수안에 service.AddSingleton<IFoodService, FoodService>();로 선언하게 되면

이제 new를 통해서 선언하는 것이아닌

@inject IFoodService foodSerivce를 통해서 FoodService를 new를 통해서 생성하지 않고 사용이 가능하다.

[예시]

@using BlazorApp.Data;

@inject IFoodService foodSerivce

<div>
@foreach(var food in foodSerivce.GetFood())
{
<div>@food.Name</div>
<div>@food.Price</div>
}

</div>

하지만 이러한 방식이 전역적으로 사용되는것은 아니다.

이를 살펴보자.

public class SingletonService : IDisposable
{
public Guid ID {get; set;} //간단하게 ID를 생성하는 인터페이스이다.

public SingletonService() //cotr + tab,tab으로 해당 class의 생성자를 간단하게 생성할 수 있다.
{
ID = Guid.NewGuid();
}

public Dispose()
{
Console.WriteLine("SingletonService Disposed!");
}
}

public class TransientService : IDisposable
{

public Guid ID {get; set;} //간단하게 ID를 생성하는 인터페이스이다.

public TransientService() //cotr + tab,tab으로 해당 class의 생성자를 간단하게 생성할 수 있다.
{
ID = Guid.NewGuid();
}

public Dispose()
{
Console.WriteLine("TransientService Disposed!");
}
}

public class ScopeService : IDisposable
{
public Guid ID {get; set;} //간단하게 ID를 생성하는 인터페이스이다.

public ScopeService() //cotr + tab,tab으로 해당 class의 생성자를 간단하게 생성할 수 있다.
{
ID = Guid.NewGuid();
}

public Dispose()
{
Console.WriteLine("ScopeService Disposed!");
}

}

[생명주기]

3가지 모드를 파악해보자.

 

다음처럼 생성

service.AddSingleton<SingletonService>();

service.AddTransient<TransientService>();

service.AddScoped<ScopedService>();

 

​다음 처럼 사용 (주로 상단에 선언)

@inject SingletonService singleton;

@inject TransientService transient;

@inject ScopedService scoped;

@inject 가 있는 동일페이지에서 다음 코드가 있을때 guid를 통해  id 가 페이지개 갱신 될때마다 재생성 되는 것을 보면 생명 주기를 파악 할 수 있다

<div>

<h1>Singleton</h1>

Guid: @singleton.ID

<h1>Singleton</h1>

Guid: @transient.ID

<h1>Singleton</h1>

Guid: @scoped.ID

<div>

이렇게 하고 생명주기를 살펴보면

singleton와scoped의 id는 변화하지 않고

transient의 id는 변화한다.

하지만 새로고침을 실행하면 singleton의 id는 바뀌지 않지만 나머지 두개는 변화한다.

(Balzor Server 프로젝트의 경우에는 변화가 없지만 클라이언트(Blazor WebAssembly) 사이드같은 경우에는 singleton 방식도 guid 도 변경 된다)

즉, 변동하지 않고 모두에게 똑같이 보여야 한다면 singleton을

유저마다 갱신되어 보여야 한다면 나머지 2개를 선택하여 사용해야 하는데

Transient는 말그래도 웹페이지의 요청이 일어날때마다 바뀌고

Scoped는 웹에 접속할때 마다 변경된다.

 

 

SPA(single Page Application)

SPA는 기존에 존재하던 정적 페이지가 아닌 동적 페이지라고 할 수 있다

기존에는 웹서버에서 전체를 실시간으로 만들어 보내주는 방식이였지만

일부 변경되는 데이터에 따라(Dom) 모습이 달라지는 것을 동적페이지 방식이라 한다=> SPA

 

 

레이아웃

다음 과 같은 레이웃의 범위를 알아보면 아래 코드로 글자가 어디에 들어 가있는지 보면 된다 a, d , c

 

MainLayout.razor

@inherits LayoutComponentBase

<div class="sidebar">
    <p style="color : #be0000;">
        a
    </p>
    
    <NavMenu />
</div>

<div class="main">
    <div class="top-row px-4">
        c
        <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
    </div>

    <div class="content px-4">
        d
        @Body
    </div>
</div>

_imports.razor 알아보기

이 컴포넌트 안에 사용되는 @키워드들은 같은 폴더안에 있는 모든 컴포넌트들에게 적용되게 된다.

imports.razor 의 내용을 ABC 라는 폴더에 넣어놓고
내용을 새로만든 레이아웃 @layout MainLayout2.razor

해놓으면 해당 폴더에 있는 razor 페이지들의 기본 레이아웃은 MainLayout 이 아닌  MainLayout2 으로 변경된다

 

 

기본 레이아웃은 자체는 다음 코드를 상속받아 레이아웃 자체를 구성한다

@inherits LayoutComponentBase

 반드시 하나는 이 것을 상속받는 레잉아웃이 있어야 한다 이것이 적용된 페이지가

MainLayout.razor 이다

 

@inherits를 사용하게되면 class를 상속해서 사용할 수 있게 된다.

@layout키워드를 사용하면 레이아웃을 바꿔서 적용할 수 있다.

키워드를 사용하지 않고 모든 레이아웃을 변경하기 위해서는 App.razor에 DefaultLayout을 변경해주면 된다.

 

 

 

Router : 주소/sports/55  과 같이 주소 뒤에 어떤 페이지를 보여줄지에 대한 것

<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <LayoutView Layout="@typeof(MainLayout)">
            <p>Sorry, there's nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>

 

<Router 는 다음 처럼 되어 있다 Found 가 RenderFragment 로 내용을 보여주기 위한 형태인것을 알 수있다(템플릿)

그리고 Router는 상대적 기준으로 생각하고 작성해야 한다 즉 현재 경로 위쪽이 무엇인지 기본적으로 절대경로로 생각하면 안된다는것

 

 

 

 

 아래코드에서

<div class="top-row pl-4 navbar navbar-dark">
    <a class="navbar-brand" href="">BlazorApp</a>
    <button class="navbar-toggler" @onclick="ToggleNavMenu">
        <span class="navbar-toggler-icon"></span>
    </button>
</div>

<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
    <ul class="nav flex-column">
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> Home
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="counter">
                <span class="oi oi-plus" aria-hidden="true"></span> Counter
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="fetchdata">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="user">
                <span class="oi oi-list-rich" aria-hidden="true"></span> User
            </NavLink>
        </li>
    </ul>
</div>

@code {
    private bool collapseNavMenu = true;

    private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;

    private void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
    }
}

 

 

 이 부분은 

<div class="top-row pl-4 navbar navbar-dark">
    <a class="navbar-brand" href="">BlazorApp</a>
    <button class="navbar-toggler" @onclick="ToggleNavMenu">
        <span class="navbar-toggler-icon"></span>
    </button>
</div>

이 처럼 화면이 작아지는 모바일 환경에서 메뉴표현등으로 나타내기 위한 버튼으로 화면이 큰 윈도우 환경에선 기본적으로는 화면을 줄이지 않는 이상 안보일 수 있다

 

 

NavLink : 이동 시키는 <a href > 와 유사한데 비주얼 적으로 차이가 있는데 버튼을 누를때 스타일 적인 것만 다르다

상단에 Home, FetchData 등의 버튼을 참고하면 된다

 NavLink 는

<a href  를 써서 동일하게 구현할 수도 있고

NavigationManager 를 인젝션 하여

 이렇게 구현하여 구현 할 수도 있다

 

counter 페이지가 뜰때 다음처럼 값을 받을 수도 있다 값은 CurrentCount 로 들어가게 된다

 

 

https://localhost:44337/counter/30  30을 입력하면 CurrentCount 값이 30 이 된다

 

 

 

 

ref : https://blog.naver.com/whro152/223041082396

반응형
반응형

[색상 변경하기 예제]

 

 

Cascading Parameter 는 변수를 전달하는데 다른 페이지에

<Cascading Parameter 로 동일하게 선언되어 있는 변수가 있다면 그곳 까지 값이 전달된다

 

TableTemplate 은 C# 의 tempalte 같은 것인데 당연히 동일하지 않고 엇비슷하다


Cascading Parameter알아보기

만약 우리가 동일한 Parameter변수들을 넘겨주는 경우가 발생한다면 한번에 흘러내리는 것처럼

변수를 넘겨주게 하는 문법이다.

사용 방법은 매우 간단한데

<CascadingValue Name ="ThemeColor" Value="_selectedColor">

<ShowUser ...>

<ShowUser2 ...>

</CascadingValue>

태그를 사용하여 사용할 범위를 지정해 준다.

Name은 구분하기 위한 Key이며 Value는 범위내에 뿌릴 값이다.

이렇게 사용하게 되면 범위내에 있는 ShowUser, ShowUser2의 컴포넌트에 자동적으로 Value = "_selectedColor"

Parameter가 흘러내리듯이 정해지게 된다.

이를 ShowUser,Showuser2의 컴포넌트에서 사용하기 위해서는

[CascadingParameter(Name = "ThemeColor")]

string _color {get; set;}

위와 같이 사용하여 _color의 값에 Value="_selectedColor"가 Binding된것처럼 사용할 수 있게 된다.

[추가]

컴포넌트의 깊이에 상관없이 Parameter가 전달된다는 특징이 있다.

[Templated Component]알아보기

C#의 제너릭형식과 유사한 문법이다.

RenderFragment로 이루어진 Razor Component이다.

[넘어가는 이야기]

[c#에는 c++에 없는 partial class가 존재한다.

이는 a라는 파일에서 partial class a를 작성하고

추후에 b라는 파일에서 aprtial class a를 이어서 작성하게 되면 두 class를 하나로 합친 것처럼

작성하게 해주는 키워드이다. 현재 Blazor와는 상관없는 c#문법이므로 넘어가도 상관없다.] 딱히 이 부분은 크게 상관없는 부분이다.

 

정리하자면 TableTemplate.razor를 생성한다.

1) 코드 부분에 [Parameter] public RenderFragment Header {get; set;}를 작성한다.

2) HTML 부분에 사용하기 위한 장소에 @Header를 선언해준다.

3) 다른 razor 컴포넌트에서 <TableTemplate></TableTemplate>를 선언해준다.

4) <TableTemplate></TableTemplate>의 범위안에 <Header></Header>를 서언하여

RenderFragment Header를 사용한다는 것을 알려준다.

5) <Hedaer></Header>의 범위안에 사용하고 싶은 HTML의 태그들을 지정해준다.

6) 이렇게 사용하게 되면 TableTemplate.razor의 @Header부분에 지정한 태그들이 들어가게 된다.

 

 

TableTemplate 예시

노란색 화살표가 해당 부분이 .razor 페이지로 들어가는 부분이 된다

이때 해당 부분을 대체하는데 쓰이는 역할로 RenderFragment 가 사용된다 : 태그를 그리는 느낌

TableTemplate 을 통해 보여지는 부분의 개수를 변경하거나 스타일을 다르게 하는 등이 가능해질 수 있다

 

 

ref : https://blog.naver.com/whro152/223039845752

반응형
반응형

User.razor 전체 코드

 

컴포넌트 분리

저번 포스팅에 @code부분인 AddUser()와 KickUser()를 컴포넌트화 시키기 위해 새로운 Blazor파일을 생성해 분리하도록 해보자

ShowUser 블레이저 파일 생성

ShowUser.razor

ShowUser.razor파일을 생성해 User.razor에있던 리스트 생성과 유저 추가, 삭제하는 함수들을 옮겼다.

 

설명

User.razor

-<ShowUser></ShowUser> : 새로 생성한 ShowUser.razor를 사용하기 위한 처리

-<ShowUser User ="_users"> : ShowUser.razor의 User변수와 User.razor의 _user의 변수를 공유한다

-@ref ="_showUser" : 부모쪽에서 자식에 접근하는 방식(이 처리가 없으면 @code에 ShowUser는 null임)

CallbackTest="CallbackTestFunc" : 콜백을 사용할 때

 

ShowUser.razor

[Parameter] : 를 통해  다른 블레이저 파일에서 접근 가능하도록 명시한다.

(블레이저에서는 Action보다는 EventCallback을 추천한다. StateHasChanged()가 없어도 리프래시 처리 )

 

유저리스트에 요소 추가

유저리스트에 요소 삭제

"CallbackTest"문자가 보임 : 콜백 호출이 잘되고 있다

 

 

ref : https://funfunhanblog.tistory.com/419

반응형
반응형
@page "/bind"

<p>
    <input @bind="inputValue" />
</p>

<p>
    <input @bind="InputValue" />
</p>

<ul>
    <li><code>inputValue</code>: @inputValue</li>
    <li><code>InputValue</code>: @InputValue</li>
</ul>

@code {
    private string? inputValue;

    private string? InputValue { get; set; }
}

위 처럼 string 변수에 있는 문자 내용을 input 쪽 변수에  바인딩 시킬수도 있고

 

<button class="@inputValue"   type="button"

처럼 버튼의 스타일 또한 변경이 가능하다

<button class="@inputValue"   type="button" disabled="@(_users.Count() >= 5)"

이렇게 유저의 카운트가 0 보다 크면 버튼이 안보이게 처리 또한 가능하다

_users 이 글을 참고해보면 된다

https://3dmpengines.tistory.com/2375

 

만약 데이터가 html 즉 UI 상에 변경된 것이 보이지 않는 상황이 다음 time 관련 코드에서 적용이 안될 수 있는데

 

 

이때는 StateHasChanged(); 함수를 호출해주면 변경된 값이 HTML/UI 상에 보이게 된다

 

 

반응형
반응형

유저의 목록과 추가및 제거에 대한 변수와 함수를 바인딩하는 것을 알아본다

 

기본적으로 html 부분에 code 쪽의 변수가 바인딩 되어 있는 경우 변수가 바뀔때 자동으로 데이터가 html 상에서 갱신된다

 

[유저 목록과 추가하기]

유저의 목록 기본적으로 3개를 만들어 for 문으로 3개가 보여지도록 처리해놓는다

@page "/user"
@using BlazorApp.Data;

<h3>Online Users</h3>

<p>
    Users : <b>@_users.Count</b>
</p>
<br/>

<ul class="list-group">
    @foreach(var user in _users)
    {
        <li @key="user" class="list-group-item">
            <label>@user.Name</label>
        </li>
    }
</ul>


<br/>

<div class="contrainer">
    <div class="row">
        <div class="col-md-6">
            <input type="text" class="form-control" placeholder="Add User" @bind-value="_inputName"/>
        </div>
        <div class="col-md-6">
            <button class="btn btn-primary" type="button" @onclick="AddUser" > Add a user </button>
        </div>
    </div>

</div>


@code {
    List<UserData> _users = new List<UserData>();

    string _inputName;

    protected override void OnInitialized()
    {
        _users.Add(new UserData { Name = "John" });
        _users.Add(new UserData { Name = "Jane" });
        _users.Add(new UserData { Name = "Jack" });
    }

    void AddUser()
    {
        _users.Add(new UserData { Name = _inputName });
        _inputName = string.Empty;
    }

}

 

user 명을 추가 하면 li 목록에 추가 되는 코드이다

 

함수 AddUser 또한 @onclick= 에 연결 할 수 있다

 

 

 

 

[제거]

@page "/user"
@using BlazorApp.Data;

<h3>Online Users</h3>

<p>
    Users : <b>@_users.Count</b>
</p>
<br/>

<ul class="list-group">
    @foreach(var user in _users)
    {
        <li @key="user" class="list-group-item">
            <button type="button" class=" btn btn-link" @onclick="(()=>KickUser(user))" > X </button>
            <label>@user.Name</label>
        </li>
    }
</ul>


<br/>

<div class="contrainer">
    <div class="row">
        <div class="col-md-6">
            <input type="text" class="form-control" placeholder="Add User" @bind-value="_inputName"/>
        </div>
        <div class="col-md-6">
            <button class="btn btn-primary" type="button" @onclick="AddUser" > Add a user </button>
        </div>
    </div>

</div>


@code {
    List<UserData> _users = new List<UserData>();

    string _inputName;

    protected override void OnInitialized()
    {
        _users.Add(new UserData { Name = "John" });
        _users.Add(new UserData { Name = "Jane" });
        _users.Add(new UserData { Name = "Jack" });
    }

    void AddUser()
    {
        _users.Add(new UserData { Name = _inputName });
        _inputName = string.Empty;
    }

    void KickUser(UserData user)
    {
        _users.Remove(user);
    }

}

 

 

이름 앞에 X 표시 버튼을 추가하였고 x 를 누르면 유저목록에서 유저가 제거된다

기본 3개에서 하나를 제거한 상태이다

 

@onclick="(()=>KickUser(user))"  이 코드는 람다형태로 onclick="KickUser(user)" 의 형태로는 지원이 되지 않기 때문에 람다로 연결해주면 가능하다

 

반응형
반응형

 

 

Binding.razor 페이지를 만들고 다음 처럼 작성한다

@code 부분의 변수가 html 코드 중 @_value 로 연결 되게 처리 할 수 있고

html 에서는 @bind-value=  들로 @code 쪽 갑과 연결 되어 양방향 연결이 되게 할 수 있다

@page "/binding"

<h3>Binding</h3>

<p>Value : @_value</p>

<input type="range" step="1" @bind-value="_value" @bind-value:event="oninput" />

@code {
    int _value = 10;

}

 

 

 

메뉴 레이아웃중 하단에 binding 텝을 만들어주면

<div class="top-row pl-4 navbar navbar-dark">
    <a class="navbar-brand" href="">BlazorApp</a>
    <button class="navbar-toggler" @onclick="ToggleNavMenu">
        <span class="navbar-toggler-icon"></span>
    </button>
</div>

<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
    <ul class="nav flex-column">
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> Home
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="counter">
                <span class="oi oi-plus" aria-hidden="true"></span> Counter
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="fetchdata">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="binding">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Binding
            </NavLink>
        </li>
    </ul>
</div>

@code {
    private bool collapseNavMenu = true;

    private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;

    private void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
    }
}

 

 

환경은 Blazor Server 이다

반응형
반응형

ValuesController

using HelloEmpty.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;

namespace HelloEmpty.controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        [HttpGet]
        public List<HelloMessage> Get()
        {
            List<HelloMessage> message = new List<HelloMessage>();
            message.Add(new HelloMessage() { Message = "Hello Web API 1!" });
            message.Add(new HelloMessage() { Message = "Hello Web API 2!" });
            message.Add(new HelloMessage() { Message = "Hello Web API 3!" });
            return message;
        }
    }
}

get() 이 클라에서 서버로 api/Values 경로로 api 를 쏘면 

서버에서 보낼 내용을 message 에 담아 브라우저에 보낸다

 

 

HelloMessage

namespace HelloEmpty.Models
{
    public class HelloMessage
    {
        public string Message { get; set; }
    }
}

실제 데이터는 Models 에 들어있게 된다

 

 

program 부분인데 이 부분은 디폴트이다

using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace HelloEmpty
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}

 

 

Startup.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace HelloEmpty
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            //mvc 를 사용하겠다는 것
            //services.AddControllersWithViews();
            services.AddControllers();
        }

        // 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();
            }

            app.UseRouting();


            //홈페이지 실행할때 어떤 주소로 어떤 부분을 연동하여 실행할지에 대한 부분
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
                //endpoints.MapRazorPages();
                //endpoints.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
                /*
                endpoints.MapGet("/", async context =>
                {
                    await context.Response.WriteAsync("Hello World!");
                });
                */
            });
        }
    }
}

 

DotNet Core 3.1 버전으로 하면 StartUp.cs 가 생성된다

 

 

실행결과

 

실제 데이터가 JSON 형태로 온것을 알 수 있다

 

반응형
반응형

 

 

우리는 웹 어플리케이션을 개발할 때, 보통 java, .NET, Node.js 등의 framework 또는 language를 사용하여 server-side project를 만든다. 그리고 Angular, React, Vue.js와 같은 javascript framework를 사용하여 브라우저에 데이터를 보여준다.

 

하지만, 우리는 server-side application에서 사용한 언어를 client-side에는 사용할 수가 없다. server-side와 client-side는 기본적으로 단절되어있다. 

 

우리가 만약, server language를 client에도 재사용한다면 좋지 않겠는가? 우리는 WebAssembly를 사용하여 그렇게 할 수 있다. 

 

Blazor는 무엇이고, 어떻게 사용해야 하는가?

Blazor는 C#을 사용하여 interactive web application을 만들 수 있는 오픈소스 framework이다. 우리는 Blazor에서 server, client code에 모두 C#을 사용할 수 있다. .NET runtime은 브라우저에서 우리가 생성한 .NET DLL을 실행할 수 있다. 

 

추가적인 plugin없이 주요 브라우저에서 실행이 가능하다. .NET standard와 호환하는 library를 사용할 수 있다. (NuGet package 포함) 

 

Blazor application은 WebAssembly에서 실행되고, WebAssembly는 JavaScript sandbox에서 실행된다. 

JavaScript와의 연결이 되어 있으므로, web sockets, file API, DOM의 브라우저 기능을 사용할 수 있다. 

 

Blazor application에서 JavaScript를 호출할 수 있고, 그 반대도 가능하다. 

 

Blazor Application의 Different Models

Blazor Server

Blazor Server는 ASP.NET Core application의 server측에서만 동작하고, SignalR을 사용하여 HTML과 통신한다. blazor.server.js라는 파일을 통해 가능하다. 

server-side application은 메모리에 남아있기 때문에, 사용자당 instance가 생성된다. 

장점은, Blazor WebAssembly에 비해 다운로드 사이즈가 작다. older browser에서도 디버깅이 가능하다. 

 

단점은, 서버와 연결되어 있어야 하기 때문에 offline support는 없다. 확장성도 리스크가 될 수 있지만, 동시접속 천단위까지 제공이 가능하다. 마지막으로, serverless deployment는 불가능하며, ASP.NET Core server가 반드시 필요하다. 

 

Blazor WebAssembly

WebAssembly의 도움으로 브라우저 client에서 실행된다. application이 실행되면 browser로 모든 파일을 다운받는다. (HTML, CSS, JavaScript files, .NET Standard DLL files)

 

offline mode에서 동작한다. server가 필요하지 않다. static files로 구성되어 있기 때문에, CDN, Server, Azure Storage에 배포가 가능하다. 

 

장점은, native browser application의 속도와 비슷한 정도로 빠르다. offline mode에서 동작이 가능하다. serverless deployment가 가능하다. browser로 file만 받으면, server는 필요하지 않다. 추가 plugin 없이, modern browser에서 동작이 가능하다. 배포가 쉽다. 

 

브라우저에서만 실행되기 때문에, 브라우저 capability에 의해 성능이 제한된다. 

 

단점은 최초 로딩시간이 길다. older browser에서는 동작하지 않는다. older browser에서 동작해야 한다면 Blazor server-side application이 더 나은 선택이 되겠다. 

 

Blazor Server와 Blazor WebAssembly Application의 차이점

두 application의 차이점을 살펴보자. 

 

1. index.html, _Host.cshtml

 wwwroot 폴더를 보면 Blazor WebAssembly App은 index.html 파일이 없고, Blazor 서버 앱은 없다. 

index.html파일은 blazor.webassembly.js 파일을 참조하고, application의 모든 파일을 다운로드 한다. 

이와 비슷한 일은 Blazor 서버 앱 Page/_Host.cshtml 파일에서 발생한다. 이 파일에서 참조하는 blazor.server.js 파일은 client WebSocket connection을 담당한다. 

 

그 외 Counter, Index, FetchData 파일이 동일하게 들어가 있으며, 이것은 component files를 재사용 할 수 있다는 뜻이다. 

 

2. setting

Blazor 서버 앱은 Startup.cs, appsettings.json 파일이 있고, Blazor WebAssembly App에는 없다. 

Blazor WebAssembly App에서 서비스를 등록하기 위해서는 Program.cs 파일을 사용해야 한다. 

 

 

ref : https://bigexecution.tistory.com/65

 

반응형
반응형

SPA 중심에는 대표적인 라이브러리/프론트프레임워크 React, vue/Angular, Blazor WebAssembly가 있다.

📖 SPA (Single Page Application)

기존의 웹서비스는 클라이언트(사용자)가 요청하면 서버가 해당 웹 페이지를 HTML 코드로 계산하고 반환하며, 많은 요청이 이루어지면 계산이 더욱 오래 걸립니다.

SPA는 모든 처리가 단일 페이지에서 수행되는 방식입니다. 첫 로드시에만 웹페이지를 계산하여 HTML로 반환합니다. 그 이후에는 필요한 데이터를 HTML로 전달받지 않고 (서버에서 렌더링 X) 필요한 데이터만 서버로부터 JSON으로 전달받아 동적으로 렌더링 합니다.

장점

1. 빠른 로딩 시간 (Quick Loading Time)

  • 단일 페이지에서 웹 앱에서 페이지가 로드되면 서버는 더 이상 HTML 또는 CSS를 보내지 않기 때문입니다.
    (Google Reasearch에 따르면 페이지를 로드하는 데 200 밀리 초 이상 걸리면 비즈니스와 판매에 큰 영향을 미침)

2. 좋은 캐싱 능력 (Good Caching Abilities)

  • 로컬 데이터를 효율적으로 캐시 할 수 있습니다. SPA는 하나의 요청 만 보내고 서버의 모든 데이터를 저장, 데이터에 대한 지속적인 액세스가 있기 때문에 사용자는 오프라인으로 작업할 수도 있습니다. (방문자에게 데이터비용을 요구하지 않음)

3. 신속한 프런트 엔드 개발 (Rapid Front-End Developement)

  • 단일 페이지 애플리케이션 아키텍쳐이기 때문

4. 향상된 사용자 경험 (Improved User Experience)

  • 굳이 새 페이지가 로드 될 때까지 기다릴 필요가 없음, 콘텐츠의 동적인 로드는 사용자에게 좋은 경험을 제공

주의

초기 구동속도가 느릴 수 있음.

검색엔진 최적화(SEO)

 

왜 SPA를 사용하나요?

단일 페이지 웹 앱 (SPA)는 웹 개발을 고려할 때 이상적인 선택입니다. Saas플랫폼, SEO가 중요하지 않은 커뮤니티에 쓰입니다.

프로그램과 사용자간의 훌륭하고 풍부한 통신을 원한다면 SPA 접근 방식이 필요합니다.

 

 

 

 

기존 MVC 등은 페이지 단위를 받는 형태였다고 함..  비효율적인 구조..

 

ref : https://dhddl.tistory.com/230

반응형
반응형

셋 set (중복을 허용하지 않는 집합)

합집합, 교집합 , 차집합

 

채널을 구독할때 중복 구독되지 않도록 할때 유용

 

sadd

sinter

srem

 

추가가 되면 1 이 되고 이미 추가가 되어 있다면 0이 된다

이때 는 faker:gudok 이 되고 abc가  member 가 된다

 

 

sinter 로 faker:gudok 를 통해 읽어오면 member 인  abc 를 얻어올 수 있다

 

 

abc1 과 abc2 를 추가한뒤 sinter 로 값을 보면 정렬되어 있진 않고 중복 없는 값이 들어간걸 알 수 있다

 

 

삭제는 srem 으로 삭제 할수 있다

 

 

 

 

 

 

정렬된 Set

 

zadd, zrange 로 다음 처럼 정렬된 랭킹 정보를 구할 수도 있다

 

zadd 할때 중간에 숫자를 꼭 넣어줘야 한다, 이 숫자로 정렬된다

 

127.0.0.1:6379> zadd test:ranking 300 book
(integer) 1
127.0.0.1:6379> zadd test:ranking 200 store
(integer) 1
127.0.0.1:6379> zadd test:ranking 700 board
(integer) 1
127.0.0.1:6379> zrange test:ranking 0 10
1) "store"
2) "book"
3) "board"
127.0.0.1:6379>

 

숫자값에의해 정렬된것을 알 수 있다

 

 

리트스의 경우

lpush, rpush, lrange, lpop, rpop 등의 명령어가 있고

lrange key 0 -1 을 쓰면 전체 리스트 내용을 볼 수 있다

 

 

 

 

해시 (해시 기반)

hset :  다음 순으로 데이터를 넣게 된다

key 는 전체 카테고리 key 이고 field 와 value 가  <key, value> 가 된다

 

hget

hlen : 개수가 몇개인지

hdel : 삭제

hgetall : 모든데이터 보기

 

 

 

다음은 한번에 다른 필드로 여러 항목을 추갛 ㅏ는 방식을 보여준다

 

 

REF : 

http://redisgate.kr/redis/command/zadd.php

https://redis.io/commands/sinter/

반응형
반응형

https://github.com/tporadowski/redis/releases

 

Releases · tporadowski/redis

Native port of Redis for Windows. Redis is an in-memory database that persists on disk. The data model is key-value, but many different kind of values are supported: Strings, Lists, Sets, Sorted Se...

github.com

.zip 압축을 풀고 sever 를 실행시킨다음 cli.exe 로 클라이언트에서 테스트 해볼 수 있다

 

 

서버

 

클라 기본 상태

 

key1 으로 값 hello world를 저장

 

 

get 으로 가져올 수 있다

 

즉 이렇게 메모리상에 들고 있게 되는 것이다

 

 

서버를 끄고 get 을 요청하면 아무 일도 일어나지 않게 된다, 데이터도 역시 날라가게 된다

 

 

다시 서버를 키고 get 을 해보면 nil 이 나온다

 

set user:name "abc"

 

get user:name

"abc"

 

이렇게 나오고

append user:name " is good"

get user:name

"abc  is good"

 

이 된다

 

 

incr : 11은 문자인데 문자를 숫자로 인식하여 1을 증가한다음 12로 찍는 명령어

 

 

mset, mget  : 여러개를 설정하거나 가져올 수 있는 것

 

 

기존 키 값을 set 으로 다시 쓰게 되면 덮어써지게 된다

 

 

ttl : 유효 시간이 얼만나 남았는지 확인, -1 , 이면 무한대를 말한다

expire : 유효시간 설정

 

 

 

유효시간을 설정하고 ttl 을 호출하면 남은 시간을 볼 수 있다

 

서버에서 세션에 대한 정보를 DB 에서도 처리 할 수 있다

먼저 ID 와 pw 가 맞는지는 웹서버에서 검증한다 그리고 이 유저가 맞는지는 DB 를 통해서 인증을 거치는데

인증을 통과했다면 인증 받은 사람이라는 토큰을 클라에게 전달해준다

 

username:세션ID 를 하나의 컬럼에 설정한다음 유효시간을 별도로 설정한다

세션 id 가 존재하는지에 대해선 username 의 세션 ID를 확인하여 존재하는 유저인지 알 수 있고

관계형 DB 에선 이렇게 username 의 세션 ID를 클라에게 던져주는 형태가 된다

즉 클라가 서버에 다시 뭔가 할때 세션ID를 던진다

 

이렇게 되면 매번 서버와 DB 를 왔다갔다 하며 유효시간을 별도로 설정해줘야 한다는 것등의 별도의작업 및

불필요한 비용이 발생 할 수 있다 어차피 세션 ID가 서버에선 유효한 값 이므로
(클라에서 변조된것인지 서버에서만 판단해 주면 됨으로) 이때 redis 가 좋은대안이 될 수 있다

 

 

 

 

반응형
반응형

관계형 DB 는 B-Tree 기반으로 이뤄지고

데이터가 엄청 많아지면(1억 단위를 넘어간다면) 느려진다는 단점이 있다

MMORPG 는 대부분 1억 단위인 빅데이터 단위로 넘어가지 않는다 

그리고 한 서버군마다 동접이 5000 명에서 10000명이다

 

그런데 만약 데이터간의 관계가 떨어져서 Key-Value 로 관리 해도 무리가 없다면 NO-SQL 형태를 고려해 볼 수 있다

Hash 를 떠올리면 된다

그리고 Value 에는 문자열, 리스트, 셋, 정렬된 셋, 해시를 넣을 수 있다

 

그리고 NO-SQL 은 In memory 방식으로 전원을 끄면 데이터를 날라가는 방식이다

 

 

중요한 데이터는 관계형 DB 로 저장하고 휘발성인 랭킹 같은것들은 redis 로 사용 하는 것을 검토해 볼 수 있다

Redis 는 기본적으로 Linux 에서 돌아간다 (개발 단계에서 window 에서 돌릴 수는 있다)

 

대규모 게임이 아닌 소규모에서 redis 윈도우를 사용하거나

윈도우환경에선 redis 대용으로 나오는 memuria 를 고려해볼만 하다

 

https://www.memurai.com/

 

Redis for Windows alternative, In-Memory Datastore | Memurai

" Migrating from a legacy hardware and software system to the Microsoft stack was an interesting journey. Our in-house developers are well versed and trained on Windows, .NET, Azure and other tools available for the Microsoft ecosystem. We explored several

www.memurai.com

 

반응형
반응형

Read 에서 전반적인 흐름은 유저가 요청을 하면 서버에서 데이터 베이스에 자료를 요청하고 데이터베이스는 큰 램에서 데이터가 있다면 리턴 그렇지 않다면 하드에서 읽어 리턴하는 형식이 된다

 

노란색이 유저이고 데이터 read 요청을 한다면 노란색 박스인 램 즉 하드에서 미리 올려놓은 램에서 보고 데이터가 있다면 리턴하고 그렇지 않다면 하드에서 읽어들여온다 

만약 램이 다 차면 LRU 방식 등으로 날린다

LRU 알고리즘 : 가장 오랫동안 참조되지 않은 페이지를 교체하는 기법

 

램엔 데이터뿐만 아니라 인덱스 또한 올려 놓게되어 빠른 처리를 하는데 돕게 된다

 

 

 

 

쓸때는 쓰는 스레드를 빽단에서 돌려 별도로 쓰게 하여 하드로 접근하여 데이터를 다 쓸때까지의 대기 시간을 줄이는 방식이다

 

유저가 서버에 요청하면

SQL 파싱 처리후 DB 에 쓰기 명령 을 보낸다

데이터베이스에서 빽그라운드에서 쓰기를 처리한다

 

 

락의 종류

 

 

shared 는 read_write rock 을 말한다 : 읽을때는 동시에 동기화 부하 없이 읽다가 

쓸때는 한번에 하나만 처리 되는 방식이다, 다른 스레드는 진입 불가

 

Exclusive 는 mutex 같은 것이라 생각하면 된다

 

Update 락은 shared, exlusive 준간 정도로 처음엔 shared 로 동작하다가 상황에 따라 Exclusive 로 동작하는 락이다

 

 

row 에 가까워 질 수록 락 범위가 작아지고 윗단에서 잡을 수록 전반적으로 느려지고 자원소모가 덜해지는 경향을 보인다

ex ) row 를 몇백개 잡는 일이 발생한다면 Page 단에서 lock 을 잡는 것이 더 빠를 수 도 있다 => 하지만 어차피 데이터베이스에서 알아서 처리한다

 

 

 

트랜잭션 특징 ACID

 

1,4번이 중요함

 

원자성 : 만약 두개의 정보를 동시에 변경 해야 할때 한개만 변경되고 완료 처리가 되면 안된다는 것

 

지속성 : 물리적인 장애로 문제가 발생해도 저장해야 하는데, 만약 데이터 저장이 실패하면 롤백해야함

=> DB 에서 어떤 작업을 할때 항상 로그를 남긴다 => 이 로그를 통해 정전이 일어나서 중간 과정이 날라갔다 하더라도 로그로 기록은 남아 있기 때문에 해당 부분의 작업을 되살리거나 되돌릴 수 있다

 

 

 

 

반응형

'서버(Server) > DB' 카테고리의 다른 글

redis (2) 설치 와 기본 사용법과 컨셉  (0) 2023.04.05
NOSQL - Redis (1)  (0) 2023.04.04
[MSSQL] 예상 실행계획  (0) 2023.04.02
Clustered(바로 찾기), Non-Clustered (경유해서 찾기)  (0) 2023.03.03
DB : 복합 인덱스  (0) 2023.03.02
반응형

% 가 클 수록 시간이 더 걸린다는 것임으로 큰 쪽의 % 를 낮추는 작업을 해주면 된다

 

CTRL + M 으로 실행계획을 볼 수 있다

 

 

 

ref : https://m.blog.naver.com/seek316/222306507699

반응형
반응형

[지연시간]

유선 네트워크에서는 지연시간이 10ms, 많아야 20~30ms 인데

와이파이에서는 100ms 에서 불안정하면 200~300ms 까지도 불안정하게 발생하게 된다

보통 게임에서 30~60 프레임 기준을 많이 사용하는데

60프레임 기준에선 한프레임당 16ms 정도 의 시간이 걸린다

이 시간을 넘어가면 사람들이 인지는 하기 시작하고 

30프레임에선 한 프레임당 33 ms 인데 33ms 가 넘어가면 거의 모든 사람들이 부자연 스럽다고 인지하게 된다 



[예측과 보정]

가지고 있는 정보들을 통해서 미리 예측을 하고 정보를 받았을때 예측과 결과가 다르다면 보정을 해주는 방식 

 

ex ) 타격이 먼저 되고 때리는 모션이 나중에 나가는 경우 

논타겟의 경우 시점 분리가 된다 : 스킬 시전 시점과 스킬 피격 대상 선정 사이에 차이가 발생

만약 스킬 영역에 스킬을 시작하고 스킬이 발생 하는데 까지 시간이 걸리는데 영역안에 있던 대상이 밖으로 도망가면 피격이 되면 안된다, 이때는 피격 당하는 대상의 시점만 미뤄두고 나중에 데미지가 가해지게 하면 되지만 시점분리에 필요한 요구사항이 점점 늘어난다면?

 

ex) 스킬 대상이 넉백 되면서 데미지는 그 도중에 되면 좋겠다

: 1. 시작은 따로 2. 그다음에 넉백.  3 그다음에 데미지  이렇게 3개로 분리가 된다

 

영역은 하나인데 대상을 3번에 걸처서 타격해야 하는 상황이 발생한다

 

더 나아가 쿨 다운은 선딜 후 타격 전에 돌리고 싶다 

내가 스킬 시전하다가 넉백을 당하면 취소가 되야하고

기를 모으는 시간에 따라 효과가 다르게 하고 싶다

돌진하면서 주변에 있는 적들을 차례로 밀치고 싶다

 

등등등.. 요구 사항이 점점 더 많아짐..

 

이때 생각해 볼 수 있는거 스킬 스테이지 방식이다

 

스킬을 여러 단계로 나누어서 구성 하는 것을 말한다

 

그런데 문제는 결정된 시간이 전달이 될때 받는 쪽에서 지연이 되면 내가 원하는 타이밍에 전달되지 않게 된다 




아래처럼 동작하겠지 라고 생각하고 만들었는데



네트워크를 타는 순간 스킬 구성이 달라지게 된다 

 

이 처럼 시간 간격에 딜레이가 생기면서 타격이 늦게 일어나거나 여러 가지 시점이 흔들리는 일들이 벌어지게 된다 

 

그래서 나온게 예측 가능하게 만들자

여러 스테이지 결과를 미리 결정 및 공유하고 

이에 맞춰 각자 서버와 클라가 스킬 스테이지를 진행한다

그래서 이때 미리 계산 할 수 있는 그룹들을 미리 묶어 두어서 처리하게 된다

 

그것이 스킬 스테이지 플로우 도입

 

CapturingTarget 영역 안의 대상 선정 

이나 중요하게 반드시 시점이 나뉘어야 하는 것들만 플로우를 나누는 기준이 되고 나머지 스테이지들은 묶어서 미리 계산을 하도록 처리




이걸 



이렇게 묶음으로 써

원래 4번 전송 되던걸 두번만 전송하게 되고 묶음 안에서는 어색함 없이진행 가능

 

문제점 : 플로우가 길다보면 발생할 수 있는문제가 중간에 취소 하면 취소가 늦게 가는 경우 

피격을 하지 않았는데 피격 연출이 나온다던가 하는 문제

 

해결 방안 => 정상 타격이 완료 되면 데미지 숫자를 붉은색으로, 그렇지 않으면 파란색으로 표시하여 대미지가 안들어간것으로 보이게끔 처리

 

또는 스킬 연출과 별개의 추가 연출 구간을 만들어서 

정상타격 완료 시 : 대미지 플로터로 확정 연출

정상타격 취소로 판정시(늦게 패킷이 오면) : 대미지 플로터를 모래 연출처럼 흩어 버리기

방식으로 처리




 

논 타겟일때 움직이면 맞고 가만히 있으면 맞을때도 있고 안맞을때도 있는데

 

=> 타격자 시점에서는 상대 캐릭터가 스킬 범위에 들어갔는데 피격자에서는 안맞는 문제가 보임 즉 내 캐릭터가 공격을 하는데 상대 캐릭터가 스킬 범위에 들어왔음에도 불구하고
공격이 가해지지 않음


=> 피격자 시점에서는 타격자가 공격 당하는 상대로 스킬을 논타겟으로 썼지만 상대가 스킬 범위에 들어가 있지 않아서 공격을 받지 않은 상황으로 보이는 중

 

 

이건 플레이어 위치가 타격자와 피격자 시점에서 위치가 살짝 다를때 발생 할 수 있다

서버상에서도 피격자의 위치가 실제 달라서 발생 

타격자 시점에서 상대방 위치를 보면 조금 더 왼쪽으로 가 있는 상태














이때 이동 시스템이 어떻게 되어 있는지 알 필요가 있다

 

폴리드 타일기반으로 이동하는 시스템 

셀도 없고  방향도 특정되지 않고 자율롭게 돌아다닐 수 있는 구조

위치를 짧은 시간내에 동기화 할 수 있는 모델은 없었다는 것 MMORPG 라

대규모 전투에서 통신량이 큼으로 유저가 많을 수록 제곱에 비례해서 늘어나기 때문에..



[이동에 관한 히스토리]

  1. 클라가 서버에게 이동 할래 라는 패킷을 날리면 서버가 판단해서 이동을 시키는 구조
  2. 결국 클라이언트에서 먼저 이동하도록 변경




즉시 예상하지 못하는 상황에서 멈춤을 할때 를 생각해 보면
다른 유저, 서버에서의 해당 유저, 내클라이언트에서 다른 유저를 멈출때의 위치가 모두 달라지게 된다

즉 피어들간의 위치 정보의 오차가 커진다

 

멈추는 클라이언트에서는 빨간 위치에서 멈추라고 했지만 서버와 다른 클라에서 모두 다 조금씩 늦게 받게 되어 느려지는 현상이 발생 할 수 있다



순간적으로 변경한 명령이 다음 이동 이다 라고 하면 

이동의 시작 구간에서 보정을 진행하면 된다 이때는 이동을 하는 와중에 스킬을 잘 맞고 위치 오차도 적은 편이다

하지만 바로 멈추라고 하면 다음 이동이 없다, 즉 멈추라고 하면 다음 이동이 없어 보정할 곳이 없다 

=> 만약 원래 멈춰야 하는 곳보다 더 갔을때 캐릭터를 뒤로 당기면 이동이 어색해 지기 때문에 그 자리에 있을 수 밖에 없도록 프라시아에서는 작업을 했었고 다음 이동 명령때 다시 보정을 한다를 컨셉이였었다  => 그러다보니 생각하는 위치들이 다 달랐던 것 

 

해결  : 구간이 없어도 강제로 맞춰줘야 되는 부분이기 때문에 기본적인 해결책 부터 적용 시작

방안 :

  1. 멈추면 그 자리로 그냥 옮기자

 

핑이 안정적일때는 40ms 에서 120ms 까지는 그래도 안정적으로 추가 이동명령으로 위치 보정을 해도 어색하지 않고 추가로 멈춰야 할 위치까지 이동해서 보정이 가능하다

그런데 이미 이동을 멈춰야 하는 위치보다 더 가 있을때 이걸 당기는건 게임을 망치기 때문에 

 그 자리에 멈출 수 밖에 없다 즉 50% 확률이 보정이 되거나 안되거나 하는 상황

 

정확(무보정)의 수치를 200ms 뒤 까지에서 실행되게 해야하는 것이 목표인데

즉 대부분이 보정이 될 수 있게 처리를 해야 하는데

 

이렇게 처리 하기 위해선 내쪽에서

다른 개체의 이동 요청이 왔을때 120에 받았는데 마치 200에 받은 것 처럼 처리해 주면 된다

 

즉 다른 개체의 이동 정보는 한 80ms 늦게 실행 시킨다 

이렇게 하면 보정을 더 과하게 해야하긴 하지만 아예 보정을 할 수 없는 케이스는 거의 사라지게 된다

 

위의 문제는 이동에 관한 갑자기 멈춤에 관한 문제였음으로 상대 캐릭터 이동에 대한걸 80ms 정도 지연시켜 받아서 이동에 대한 보정 처리를 해주면 결과적으로 200ms 내에 이동 보정이 됨으로 타격은 정확한 처리가 된다 => 문제는 해결됐다
수치상 80ms 이긴 한데 상황에 따라 다를 수도 있으니 동적으로 다르게 주는 것도 생각해 볼만한 문제 



그런데 이동하는 중에는 오차가 좀 더 커진 셈이긴 하지만 캐릭터가 스킬을 안맞는 크리티컬한걸 피했다고 볼 수 있다



정리

 

  • 예측을 활용 하는 동기화 방식은 어느정도 활용 중
  • 하지만 모든 정보를 알리는 방식으로 동기화를 할 경우 과다한 네트워크 통신, 느린 반응성등의 문제가 발생 할 수 있다
  • 시간의 흐름에 따른 상태를 계산해 낼 수 있는 일부 상태만 알림을 통해 동기화 처리 한다 => 정해진 규칙에 따라 현재 상태를 도출한다
  • 네트워크를 통하면 외부 요인으로 인해 상태가 변경 되었을 때 이 정보를
    내가 늦게 알게 되는 경우가 자주 발생한다, 
  • 내가 계산해서 예측을 해서 가지고 있는 현재 상태랑 믿을 수 있는 소스 보틍은 서버와 상태가 서로 다를 경우 현재 내가 가지고 있는 상태를 실제 상태로 자연스럽게 맞추는 작업을 보정이라고 한다 

 

만약 더이상 자주 알려주기 힘들다면 고려해볼만 한 것은

이른 시점에 결정적인 상태를 만든다, 먼저 계산해서 알려준다 는것 

 

도출해낸 결과가 정확한게 가끔 많이 틀릴 수 있다면
엄청 많이 틀리는 것을 좀 완화하고 대신에 평소에는 조금 정확성이 떨어지더라도 

워스트 케이스를 없애는 게 더 좋을 수 있다

다만 정확성을 너무 포기하면 보정이 힘들어진다 

구간이 없다면 80ms 정도 늦게 받아서 처리 하는 것처럼 구간을 만들어서 구간을 활용하는 전략들을 고민하면 좋을 것 

 

예측에는 Extrapolation 외삽법이 주로 쓰이고 보간에는 interpolation 이 주로 쓰인다

 

 

 

--------------------

별도 추가 부분

--------------------

 

외삽법 : 외삽법이 뭔지 오래 되서 잘 기억이 안날 수 있음으로 복습차원에서..

검은색이 원래 데이터이고 파란색이 예측하는데이터인데 점3개에 대해서 보간을 하면 원래 검은색과 거의 유사하게 파란색이 나오는데 그 미래에 대한 4번째 점에 대해선 오차가 심해 질 수 있다는 외삽법의 단점이 있다 

고차다항식의 보간법의 위험성 : 항이 많아지고 차수가 높아 질 수록 식이 정교해 질것이라는 기대가 있지만 오히려 않좋은 결과가 나올 수도 있다 

 

점선이 정확한 값인데 점 4개를 갖고 4차 다항식을 만들 수 있고 그림을 그리면 검은 선이 나오는데 형태는 조금 유사해도  오차가 나온다 

이때 더 많은 점을 도입해서 10차 다항식으로 접합을 해보면 

runge 함수를 다항식으로 묘사를 해봤더니 양 끝단에 오차가 상당히 크게 나온걸 알 수 있다

즉 다항식 보간법을 쓸때 고치일 수록 정확하다고 볼 수 없다는 것이 Runge 라는 사람이 만든 Runge 함수이다, 오히려 낮은 차수 일때 더 유사할때가 있다는 것 

 

 

 

 

 

이글의 원문 :

 

● 발표분야: 프로그래밍
● 발표자: 넥슨코리아 정성훈 / Nexon Korea Sunghoon Jung
● 권장 대상: 프로그래머, 시스템 디자이너
● 키워드: #네트워크 #동기화 #연출 #프라시아전기

반응형
반응형

 

서버에는 탈것 에대한 상태만 있고 오브젝트는 없음

크랄에서 탈것의 비주얼을 보여줌 

 



  1. 타기요청패킷 : 일반 상태에서 서버에 부르기를 요청을 시작하면서 타기가 시작됨
    1. 이때 서버는 타기에 필요한 검사를 함
      캐릭터의 이동 방향을 보고 탈것이 캐릭터를 태우러 갈 수 있는지 확인 필요
      이때 네비게이션 메시 상에서도 유효한 경로인지 같이 검사를 한다
  2. 부르기 패킷(만날위치포함) : 준비가 끝나면 문제가 없다 판단 되면, 
    1. 모든 클라에게 탈것에 대한 만날 위치포함하여 부르기 알람 메세지를 보낸다 
      그리고 탈것이 어디로 갈지에 대한 정보도 포함한다
    2. 부르기에 필요한 시간을 기다렸다가 랑데부 알람 메세지를 클라에게 보내기 위해 서버에서 예약한다
  3. 랑데부 패킷 : (랑데부는 만남, 만나는 장소라는 뜻) 
    1. 클라에서 랑데부를 받으면 클라에서 타기지점으로 뛰기 시작한다
    2. 탈것을 스폰하고 캐릭터를 향해 달리게 한다
      캐릭터는 이 지점을 향해 달려 가는 연출을 한다
      이때 캐릭터는 탈것을 부르면서도 계속 달리는 중임으로 부르기 상태에서부터 다른 이동의 간섭을 막고 탈곳과 만나기 위한 곳으로 달려 나간다
      이때 캐릭터와 탈것은 계속 달리는 중이다 
  4. 오르기요청 : 클라가 캐릭터가 탈 준비가 완료 되면 서버에 알려주는 방식
    클라에서 서버로 알리는데 => 캐릭터와 탈것은 계속 달리는 중이기 때문에 서버와 메시지를 주고 받는 동안에도 위치가 계속 바뀌고 있는 중임으로 이때 오차가 발생 할 수 있는데 이를 줄이기 위해  
    1. 캐릭터와 탈것의 위치를 서버로 같이 전달한다
    2. 서버에서는 탈것의 위치가 탈것에 올라타는 캐릭터의 새 위치가 되기 때문에 이 둘의 범위가 약속한 범위 안쪽으로 들어오면 
      모든 클라에게 오르는중패킷을 보낸다
    3. 나 자신은 이미 이전에 오르는 연출을 시작 하기 때문에 오르기요청에 대한 응답 패킷은 무시하지만
      다른 클라는 오르는중 패킷을 받으면 오르는 연출을 시작한다
  5. 오르기 완료 패킷 : 오르는 중이 완료 되는 시간이 정해져 있고 이 시간이 완료 되면 상태와 오르기완료패킷을 클라들에게 보내어 오르기를 완료한다, 

 

위 이미지인데 타는 경로를 아래 처럼 생각 해볼 수 있다

 

캐릭터 오르는 연출

  • 랑데부 에서
    • 캐릭터와 탈것이 각각 어느 위치에 있을때 오르기가 시작되는지를 결정해야한다
      캐릭터가 오르기 시작 애니가 나가고 -> 탈것 기준에서 오르기 시작한 지점과 오르기 완료된 지점까지 완료 되면 이때 약간 더 앞으로 전진하면 원래 캐릭터가 이동하던 직직방향과 만나게 된다, 그리고 오르기 완료시까지 플레이어 입력을 일시적으로 제한한다
      일시적으로 제한되는 사이에 연출이 일어나기 때문에 이때의 시간은 기획자가 알아서 제어하도록 테이블로 빼놓음, 말이나 탈것에 따라 연출이 달라질 수 있기 때문 
      그리고 타는 동안 속력은 기존 그대로 고정한다
  • 오르기 완료 
    • 캐릭터 액터를 탈것 액터에 부착하는 방식
      • 캐릭터와 탈것의 위치와 방향은 다르지만 일단 붙여놓고 애니메이션을 재생 하면서 일정시간동안 위치와 방향의 차이가 0이 되게끔 한다
      • 이 시간은 오르기 애니메이션 시간 중에서 캐릭터가 탈것에 닿을 때까지의 시간 , 이때 위치와 방향은 형태와 상관 없이 선형적으로 처리
      • 이때 카메라는 캐릭터 위치를 따라가게 한다음 타기가 완료되면 원래 카메라를 제어하던 곳으로 반환 시켜준다
  • 모바일에서 인터넷이 일시적으로 안되는 상황을 대비해 현재 탈것 상태를 다시 알려주는 처리를 예방 , 클라는 서버에서 주는 상태대로 클라이언트 상황을 맞춰 적용해줘야함 => 현재 위치에서 다음 목표 지점이 다이렉트로 나오는 방식
  • 길이 좁거나 막혀 있다면 탈것이 올 수가 없으니 이때는 제자리에서 바로 탈것을 타도록 한다 
  • 캐릭터가담당 서버가 바뀌는 지역으로 이동하는 경우 
    • 타려는 와중에 서버 이주가 진행 된다면 서버에서 바로 오르기 완료 상태로 바꾸고 클라이언트에 알려주는 상태로 처리 => 임의의 상태로 바로 전환되게 처리 하는 것이 맞아 떨어짐
  • 클라위치와 탈것이 만나지 못하는 거리로 탈것이 지나간다면 그때는 타지 못하고 지나가고  지나간 이후에 제자리에서 바로 탄 상태가 되도록 처리




  • 내리기 처리 : 빠르게 플레이로 돌아갈 수 있는데 집중
    • 오르기완료 상태라면 내리기 처리를 시작
    • 내리는 중, 내리기 완료 패킷을 서버에서 순차적으로 보내준다
      • 내리기과정은 특별히 진행 될게 없고 서버에서 보내주는대로 처리하기면 하면 됨
      • 내릴때 현재 바라보고 있는 방향으로 달려 나가면서 내리도록 처리하고
        위치도 탈것 상태를 전달하면서 같이 모든 클라에게 전달하여 모든 클라이언트에서 같은 곳이 될 수 있도록 처리
      • 내릴때는 탈때와의 반대 과정으로 처리 : 캐릭터와 탈것은 때어내면서 캐릭터 위치는 고정하고 이쪽으로 캐릭터가 이동 되게 하면서 애니메이션 재생
      • 내리는 중 패킷을 클라가 받은 것이라면 내리기 완료 패킷이 오는것은 확정적이라서 먼저 내려서 걸어가고 조작 될 수있게 처리 하면 자연스러워짐 
        예를 들어 말에서 바로 내리는 순간 공격이 가능하다



  • 곡선이동 자동이동 길잡이
    • 곡선이동 : 
    • 캐릭터의 이동은 기본적으로 직선이동이다, 이동 회전이 있긴 한데 이동 경로가 딱딱하다 , 이동회전은 느낌이 딱딱하다
    • 그래서 먼거리를 갈때는 
      먼저 큰 길을 찾아가고 목적지에 가까운 곳까지 큰길을 통해 이동하는 길 찾기 방식을 사용, 이러면 계속해서 큰길들의 분기점을 지나게 된다

 

 

  • 이 분기점을 지날때 급격히 방향이 꺽이는 경우들이 많다
  • 탈것을 타고 빠르게 지나갈때는 문제가 더 잘보이는데 이때 곡선
    형태로 이동 할 수 있는 것을 추가, 이 지역은 내비메쉬가 깔린 지역이다

  • 이런 점들에선 곡선형태로 분이점을 지나도록 처리
  • 베지어 곡선 사용 2차
    곡선이 항상 중심을 향해 들어오고 중심에서 가도록 하기위해 A1, B1 을 잡는데 각 직선에서 ¼ 지점정도에 만든다
    중간  지점을 만들기 위해 B0 지점을 임의로 만든다
  • 이때 샘플값이 균일한 곡선이 나오진 않는데
  • 이것은 시간이 아닌 이동거리를 통해 곡선의 이동 거리를 찾으면 됨
  • 2차 베지어 곡선은 특정 구간의 거리를 계산식 하나로 구할 수 있는 특성이 있다(이것이 다른 곡선과 달리 거리에 대한 이점이 있는 곡선이라 이걸 선택함) 
  • 거리를 통해 위치를 찾을때는 정확한 지점을 찾긴 어려우니 근사 값으로 찾음(뉴턴랩스방법) : 뉴턴법은 식조작으로 풀지못하는 식의해의 근사 값을 구하는 법으로 접선으로 구한다


  • 멀리서부터 접선을 그리다 보면 빨간점의 해와 접접 가까워지면서 해를 구하는 것 
  • 뉴턴방법 = 뉴턴-랩슨 방법  이라한다
    x0 에 해당하는 fx 의 접선을 구하고 이 접선과 만나는 x 축의 점 x1 에 대해 다시 기울기를 구해 이것을 반복하는 방식 으로 근사 해를 구하는 방식중 가장 빠르다 
  • 이것을 이용해서 거리기반의 위치를 구한다
    spline component는 에르미트 곡선을 사용한다
    에르미트 곡선은 거리에 따른 위치 값을 바로 얻어 올 수 있어서 이걸 쓰면




  • 자동길잡이 : 캐릭터의 이동 경로를 미리 보여주는 기능
    • 자동이동이 시작 되는 지점에서 이동해야 하는 경로가 대부분 결정되기 때문에
      이 경로를 미리 보여주는 것 이건 나한테만 보여지는 것으로 splinecomponent 를
      그대로 이용해서 표현이 가능하다

 

 

 

이글의 원문

프라시아 전기에 멋진 탈것 만들기
멋지게 타고, 멋지게 달리고, 멋지게 내리자

● 발표분야: 프로그래밍
● 발표자: 넥슨코리아 이연석 / Nexon Korea Yeonseok Yi
● 권장 대상: 게임 프로그래머, 클라이언트 프로그래머, UE4 프로그래머

반응형
반응형

3 way handshake

TCP는 장치들 사이에 논리적인 접속을 성립시키기 위해 3 way handshake를 사용한다.

TCP 3 way handshake(이하 3way)는 TCP/IP 프로토콜을 이용해서 통신을 하는 응용프로그램이

데이터를 전송하기 전에 먼저 정확한 전송을 보장하기 위해 상대방 컴퓨터와 사전에 세션을 수립하는 과정을 의미한다.

TCP 통신은 PAR(Positive Acknowledgement with Re-transmission)을 통해 신뢰적인 통신을 제공한다.

PAR을 사용하는 기기는 ack을 받을 때까지 데이터 유닛을 재전송한다.

수신자가 데이터 유닛(세그먼트)이 손상된 것을 확인하면 해당 세그먼트를 없앤다.

그러면 sender는 positive ack이 오지 않은 데이터 유닛을 다시 보내야 한다.

작동방식

HOST P와 HOST Q가 있을 때, HOST P가 클라이언트, HOST Q가 서버 라고 가정해보자.

 
  1. 클라이언트가 서버에게 SYN 패킷을 보냄.Client > Server : TCP SYN
  2. 서버가 SYN(x)을 받고 클라이언트로 받았다는 신호 ACK(x + 1)와 SYN(y)패킷을 보냄.Server > Client : TCP SYN, ACK
  3. 클라이언트는 서버의 응답(ACK(x+1), SYN(y))을 받고, ACK(y+1)를 서버로 보냄.Client > Server : TCP ACK
  • SYN : 'Synchronize sequence numbers', 연결 요청. 세션을 설정하는데 사용되며 초기에 시퀀스 번호를 보냄.
  • ACK : 'Acknowledgement', 보낸 시퀀스 번호에 TCP 계층에서의 길이 또는 양을 더한 것과 같은 값을 ACK에 포함하여 전송.

동기화 요청에 대한 답변: Client의 Sequence Number + 1을 하여 ACK로 돌려준다.

이렇게 3번의 통신이 완료되면 연결이 성립된다.

SYN을 보내면 ACK를 받을 때 까지 재전송한다. ACK가 와야 완료된다는 뜻이다.

이러한 행위로 양쪽 모두 데이터를 전송할 준비가 되었다는 것을 보장하고,

실제로 데이터 전달이 시작하기 전에 한쪽이 다른 쪽이 준비되었다는 것을 알 수 있도록 한다.


4 way handshake

4 way handshake는 연결을 해제하는 과정이다. 여기서는 FIN 플래그를 이용한다.

  • FIN : 세션을 종료시키는데 사용되며, 더 이상 보낸 데이터가 없음을 나타낸다.

Termination의 종류

TCP는 두 가지 연결 해제 방식이 있다.

  1. Graceful connection release(정상적인 연결 해제)

정상적인 연결 해제에서는 양쪽이 커넥션을 모두 닫을 때까지 연결되어 있다.

  1. Abrupt connection release(갑작스런 연결 해제)
    1. 갑자기 한 TCP 엔티티가 연결을 강제로 닫는 경우
    2. 한 사용자가 두 데이터 전송 방향을 모두 닫는 경우

Abrupt

RST(TCP reset) 세그먼트가 전송되면 갑작스러운 연결 해제가 수행되는데, RST 세그먼트는 다음과 같은 경우에 전송된다.

  1. 존재하지 않는 TCP 연결에 대해 비 SYN 세그먼트가 수신된 경우
  2. 열린 커넥션에서 일부 TCP 구현이 잘못된 헤더가 있는 세그먼트가 수신된 경우
    • RST 세그먼트를 보내, 해당 커넥션을 닫아 공격을 방지한다.
  3. 일부 구현에서 기존 TCP 연결을 종료해야 하는 경우

Graceful

연결 종료 요청 역시, 요청을 먼저 시도한 요청자를 Client로, 요청을 받은 수신자를 Server 쪽으로 생각하면 된다.

 
  1. 클라이언트는 서버에게 연결을 종료한다는 FIN 플래그 보냄.
  2. 서버는 FIN을 받고, 확인했다는 ACK를 클라이언트에게 보낸다. 이 때, 모든 데이터를 보내기 위해 CLOSE_WAIT 상태가 된다.
  3. 데이터를 모두 보냈다면, 연결이 종료되었다는 FIN 플래그를 클라이언트에게 보낸다.
  4. 클라이언트는 FIN을 받고, 확인했다는 ACK를 서버에게 보낸다. 이 때 아직 서버로부터 받지 못한 데이터가 있을 수 있으므로 TIME_WAIT을 통해 기다린다.
    1. 서버는 ACK를 받고, 소켓을 닫는다(Closed)
    2. TIME_WAIT 시간이 끝나면 클라이언트도 닫는다 (Closed) => 의도치 않은 에러로 연결이 데드락으로 빠지는 것을 방지한다.

이렇게 4번의 통신이 완료되면 연결이 해제된다.

 

 

ref : https://mirror.xyz/0xA1d9f681B25C14C1eE7B87f1CF102E73cA3ad4d9/l3J2_t_85JfypzGWRdgX-qMYW7nob6XIwi1BkXs4Gm4

반응형
반응형

Clustered 는 Leaf Page 에 데이터가 들어가고 이전까지 보던 방식
Non-Clustered 같은 경우는 RID 를 통해 한번 경유하여 데이터를 저장하는 방식이다

Clustered 영한사전 , Non-Clustered 색인

Clustered : 실제로 논리적으로 정렬 되는 데이터 순서가 키 값 정렬 순서가 된다
Leaf Page = Data Page 끝 노드에 실제 데이터가 들어가는 형태
    데이터는 clustered index 키 순서로 정렬

 

 Non-Clustered 는 clustered index 유무에 따라서 다르게 동작한다

  1.  Non-Clustered 인데 clustered index 가 없는 경우(추가 안한 경우)
    leaf page 에는 heap table 에 대한 heap rid가 존재하여 이 rid 로 -> Heap table 에 접근 데이터 얻어온다
    >  heap rid = Heap table 에서 어느 주소에 몇번째 슬롯에 데이터가 있는지를 알 수 있는 RID
    >  데이터는 heap table 이 있고 이곳에 데이터들이 저장된다
        (즉 leaf Page 가 인덱스 페이지가 아니고 데이터 페이지가 아니라는 얘기)

  2. Non-Clustered 가 키가 있는데 이때  clustered index 를 추가 하게 되면
    heap table 이 없고 leaf page 에 데이터가 있는 상태이다 => leaf table 에 실제 데이터가 있게된다
    > heap table 이 없는 대신에 Clustered Index 의 실제 데이터 키 값을 들고 있게 된다, 이  키 값을 통해 cluster page 에서 데이터를 찾게 된다
    > Non clustered 에서 먼저 트리를 통해서 자식 중에서 cluster id에 를 찾고 이 id를 통해 cluster index 가 이루고 있는 트리 부모로 가서 여기서 다시 id 를 찾는 형태가 된다
    Non-Clustered  가 기존에 있을때 clustered index 를 추가 하면 기존에 Non-Clustered 도 영향을 주어 한단계 더 찾게 된다

 

 

기존 테이블을 지우고 테이블을 새로 하나 만든 다음
non clustered index 를 생성 하여 테스트

select *
into TestORderDetails 
from [Order Details];

select *
from TestORderDetails;


--non clustered index 를 생성
create index Index_OrderDetails
ON TestOrderDetails(orderID, ProductID);


--TestORderDetails 테이블에 있는 인덱스 정보 보기
exec sp_helpindex 'TestORderDetails';

 

TestORderDetails 테이블에 있는 인덱스 정보는 다음과 같다는걸 알 수 있다

 

 

인덱스 번호를 찾아 보려면 다음 처럼 작성하면 된다

sys.indexes 에서 오브젝트 id 를 비교하여 TestORderDetails 오브젝트 id 롸 같은 오브젝트의 index_id 들을 가져올 수 있다

select index_id, name
from sys.indexes
where object_id = object_id('TestORderDetails');

 

 

TestORderDetails 의 index_id 는 2 번이고 2 번으로 인덱스를 담고 있는 인덱스 페이지들을  볼 수 있다

--Index_OrderDetails Index_id 가 2 임으로 2를 인저로 넣어 페이지들을 보기
DBCC IND('Northwind', 'TestORderDetails', 2);

1048 이 root 고 나머지가 자식으로 들어간 걸 indexLevel 을 통해 알 수 있다

 

                              1048 

1008  1016  1017  1018  1019  1020

 

이 중에서 1008 페이지 정보를 보면 

--페이지 정보 보기
DBCC PAGE('Northwind', 1 , 1008, 3);

이 처럼 HEAP RID 가 있는 것을 알 수 있다 (index 를 non clustered index 로 생성했기 때문)

 

Heap RID = ([페이지 주소(4)[파일ID(2)[슬롯(2)] ROW)  의 바이트들로 구성 되는데이 ID 를 통해 Heap Table 테이블에서

Heap Table = [{page} {page} {page} {page}

 

몇번째 페이지 몇번 슬롯으로 되어 있는지를 Heap RID가 가리키고 있기 때문에

1008  에서 Heap RID 를 통해서 Heap Table 에 있는 데이터를 바로 검색해 찾을 수 있게 된다

 

 

DBCC  로 페이지 정보를 볼때 나오는 PageType 은 

  • 1 : Data Page
  • 2 : Index Page

를 나타낸다

 

 

 

 

현재 까지 상황은 

NonClusted Index 가 추가 된 상황이고  여기서  Clustered index 를 추가 해보면

-- clustered index 추가
create clustered index Index_OrderDetails_Clustered
ON TestOrderDetails(orderID);

이렇게 추가 된것을 볼 수 있다

 

이때 다시 2번 Non Clustered index 를 살펴보면 어떻게 달라져는지를 보면

 

cluster index 추가 하기 전
cluster index 추가 한후

 

 

페이지 번호가 바뀐 것을 알 수 있다

기존에는 

                              1048 

1008  1016  1017  1018  1019  1020

 

이런 번호였지만

1024 부터 자식이 시작 하는 걸 알 수 있는데 clustered index 를 추가 하게 되면 

 

                             1072

1024  1032  1033  1034  1035  1036

 

로 바뀌었다, 이때 1024 페이지 정보를 보면 

--페이지 정보 보기
DBCC PAGE('Northwind', 1 , 1024, 3);

원래는 Heap Rid 가 기존엔 있었지만 clustered index  를 추가하여 Heap Rid 가 없어진 것을 알 수 있다

 

그리고 페이지 id 가 전과 후가 달라진 것도 이전엔 Heap Rid로 heap table 을 찾는 형식이였지만

이젠 직접적으로 찾게 되니 PagePid 또한 달라지게 된 것

 

clustered index 를 생성할때 OrderID 에 대해 만들었는데

중복된 OrderID 를 볼 수 있는데 이것은 같은데이터에 한하여 동일한 ID 가 붙는 것이고 동일한 데이터를 구분하기 위하여

UNIQUIFIER 필드가 추가 되어 이들을 구분하게 끔 되어 있다

 

OrderID + UNIQUIFIER 조합으로 식별한다

 

 

정리하자면 Non clustered index 에서 Clustered index 를 추가 하면 기존 Heap RID 가 날라가게 된다는 것이다

 

 

 

 

위에서 본건 Non clusteered 인덱스 가 있었던건 본것이였는데

이번엔 그에 반해 위에서 새로 추가 했던 clustered 를 보면

--TestORderDetails 테이블에 있는 Index_id 보기
select index_id, name
from sys.indexes
where object_id = object_id('TestORderDetails');

 

테이블에 있는 인덱스 중에서 인덱스 1번은 검색

--Index_OrderDetails Index_id 가 2 임으로 2를 인저로 넣어 페이지들을 보기
DBCC IND('Northwind', 'TestORderDetails', 1);

 

 

                                                  9184

9144  9152  9153  9154  9155  9156  9157  9158  9159  9160

이렇게 구성 되어 있다 즉 여기서 알수 있는건 

 

DBCC  로 페이지 정보를 볼때 나오는 PageType 

  • 1 : Data Page
  • 2 : Index Page

였기 때문에 PageType 을 보면 PagePID 가 갖고 있는것이 바로 데이터 페이지가 된다는 걸 알수 있다

 

 

 

 

정리하자면

  • Clustered index 있으면 leaf  page 가 바로 데이터가 되는것이고, Heap Table 또한 없다
  • Clustered index 가 없다면 Heap RID로 Heap Table 을 검색해서 데이터를 찾는 한번 경유하는 형태가 된다는 것이다
    이때는 Heap Table 또한 생성되게 된다

 

 

반응형

'서버(Server) > DB' 카테고리의 다른 글

[DB] 쓰기와 읽기, 스래드와 캐시 & 대기와 락, 트랜잭션  (0) 2023.04.03
[MSSQL] 예상 실행계획  (0) 2023.04.02
DB : 복합 인덱스  (0) 2023.03.02
DB : INDEX  (0) 2023.03.01
DB : 윈도우함수  (0) 2023.02.28
반응형

 

use Northwind;



SELECT *
FROM [Order Details]
ORDER BY OrderID;


select *
into TestORderDetails 
from [Order Details];


select *
from TestORderDetails;


--복합 인덱스 추가
create index Index_TestOrderDetails
ON TestOrderDetails(orderID, ProductID);


--인덱스 정보 보기
exec sp_helpindex 'TestORderDetails';


--Index Seek 이건 인덱스를 활용해 빨리 찾는 것인데
--Index Scan 은 Index Full Scan 으로 전체 다 찾는 거라 느리다
--아래 처럼 and 를 쓰면서 인덱스통하여 검색을 하면 Index Seek 가 일어난 다는 걸 알 수 있다
select *
from TestORderDetails
where OrderID  = 10248 and ProductID = 11;



--index seek 으로 검색
select *
from TestORderDetails
where OrderID  = 10248;



--table scan 으로 찾음
--이유는 ON TestOrderDetails(orderID, ProductID) 이렇게 생성했을대 orderID 가 첫번째에 있었기 때문에
--orderID 로 찾기를 시작할땐 index seek 로 1차적으로 찾는데 
--그렇지 않으면 ProductID 로 찾게 되면 2차적으로 정렬하여 table scan 으로 찾게 된다
select *
from TestORderDetails
where ProductID  = 10248;


--index 정보 보기
DBCC IND('Northwind', 'TestORderDetails', 2);

DBCC PAGE('Northwind', 1 , 9160, 3);

 

 

 


복합 인덱스를 두개에대해 추가(orderID, ProductID) 했다
create index Index_TestOrderDetails
ON TestOrderDetails(orderID, ProductID);

 

이렇게 하면 데이터를 찾을때 성능에 대한 차이가 발생 하는 이유는 데이터를 정렬 하는데 있어서 차이가 있어서 그런것인데 인덱스가 두개라서 어떻게 검색에 대한 처리를 하는지 보면 

 

 

 

--index 정보 보기
DBCC IND('Northwind', 'TestORderDetails', 2);

9200이 가장 상단이고 나머지는 모두 자식 즉 Leaf 노드 인 것을 알 수 있다(IndexLevel 이 0 임으로)

 

Leaf 노드의 순서가 트리상 동일한 레벨에서 9168 -> 9169 -> 9170->  이런 순으로 나가기 때문에

9160에 대한 노드 정보를 보면

 

OrderID 와  ProductID 두개를 보면 우선 OrderID 로 정렬을 하는데 만약 같은 ID 라면 그다음 ProductID로 비교하는 걸 알 수있다 

 

OrderID 는 정렬이 되어 있는 반면 ProductID 는 첫번째 것을 기준으로 정렬 되어 있어서

바로 ProductID로 찾으려 하면 정렬이 안되어 있는것과 마찬가지 임으로

ProductID 로 검색 기준을 삼으면 Scan 이 되는 것 => 느림

 

 

인덱스(A, B) 를 글었다면 인덱스 A 에 대해 따로 인덱스를 걸어줄 필요는 없는데

B로도 검색이 필요하다면 인덱스(B) 에 대해 별도로 걸어줘야 빨라지게 된다

주의 : A,B 둘다 인덱스가 독립적으로 걸려있는게 아니다

 

 

 


페이지 분할(SPLIT) : 정리하면 인덱스를 생성하면 원소들이 페이지 단위로 관리 되는데 이 페이지 단위마다 관리 개수를 넘어가면 추가 페이지를 만들어 원소들을 관리하는 구조가 된다

 

데이터 50개 강제로 추가 해보면
인덱스 데이터 추가 /갱신/ 삭제시 그 중에서 추가 하는 경우를 보자

DECLARE @i INT = 0;
WHILE @i < 50
BEGIN
INSERT INTO TestORderDetails
VALUES (10248, 100 + @i, 10, 1, 0);
SET @i = @i +1;
END


이렇게 하면 추가 페이지가 만들어지는데 그 이유는 한 페이지에 내용이 너무 많아 지면 기존 페이지에 있던 요소 를 두개,세개 로 분할하여 담기 때문이 새로 페이지가 추가된것을 알 수 있고 이것을 페이지 Split 이라 한다

 

 

주의 : 인덱스를 Name 에 대해 걸어 놓았을때 SUBSTRING(NAME,1 ,2) 을 하게 되면 인덱스를 사용하는 최적화 사용이 불가능함으로 Index Scan 을 하게 되는 경우가 발생할 수 있다

SUBSTRING 으로 문자를 일부 가져와 어떤 문자를 찾는 것이 아닌

WHERE Name LIKE 'Ba%' ; 식으로 찾으면 Name 에 대해 최적화를 적용 받을 수 있다

 

실행 할때 Ctrl + L 로 Index Seek 인디 Index Scan 인지 확인하는 것이 좋다

 

 

 

반응형
반응형

테이블과 데이터들을 추가한다

USE Northwind;

--db 정보 보기
EXEC sp_helpdb 'Northwind';

create table Test
(
	EmployeeID INT NOT NULL,
	LastName NVARCHAR(20) NULL,
	FirstName NVARCHAR(20) NULL,
	HireDate DATETIME NULL,

);

SELECT *
FROM Test;

--Employees 데이터를 Test 에 추가한다
INSERT INTO Test
SELECT EmployeeID, LastName, FirstName, HireDate
FROM Employees;

RESULT

 

 

 

 

--index 를 걸 컬럼= LastName
--FILLFACTOR 리프 페이지 공간 1%만 사용 , 전체 사용 공간 중에 1%만 사용 하겠다는 것, 이러면 데이터가 다 안들어가기 때문에 별도 트리구조로 데이터를 저장함
--PAD_INDEX (FILFACTOR 가 중간 페이지에도 적용되게 하는 것) => 결과적으로 공간을 비효율적으로 사용하게끔 하는 것인데 목적이 테스트 하는 것임
CREATE INDEX Test_Index ON Test(LastName)
WITH (FILLFACTOR = 1, PAD_INDEX = ON)


--인덱스가 만들어진 sys.indexes 에서 index_id와 name 을 출력하는데 조건이 
--Test 에 만든 인덱스 id 와 같은것만 출력한다
SELECT index_id, name
FROM sys.indexes
WHERE object_id = object_id('Test');

 

결과 화면은 이런데 여기서 인덱스 Test_Index 를 조사해보면

 

--2번 인덱스 정보 살펴보기
DBCC IND('Northwind','Test', 2);

빨간색 박스의 IndexLevel 이

 

가장 높은 숫자가 트리의 Root 이고

2 -> 1 -> 0 

순으로 자식을 이루고 있는 인덱스 트리를 생각 하면 된다

 

2 가 가장 상단 그다음에 1에 해당 하는 것이 PagePID 보면 1008, 984 두개가 있는데

이 중에서 NextPagePID 를 1 레벨에서 다음을 말하는 것이 984 라고 나와 있는 것

즉 순서가 985 -> 1008 -> 984  로 되어 있다는 것을 알 수 있다

 

 트리 형태로 보면 가장 상단 레벨 1에 1개 2 레벨에 2개 3 레벨에 3개의 노드가 있다고 생각하면 된다

그런데 각 노드는 페이지들인데 이 정보들 또한 볼 수 있다

 

끝단 3개의 노드를 출력해보면

--1 파일번호, 968 페이지 번호, 3 출력 옵션
DBCC PAGE('Northwind',1, 968, 3);
DBCC PAGE('Northwind',1, 976, 3);
DBCC PAGE('Northwind',1, 977, 3);

 

 

위 처럼 되고 

원래 추가된 9개 와 비교해보면 3개씩 나뉘어져 들어가 있는 것을 알 수 있다

 

HEAP RID 라는 걸 볼 수 있는데

HEAP RID = 페이지주소(4바이트) , 파일 ID(2), 슬롯번호(2), 로 조합된 8바이트 ROW 식별자이고 이걸로

테이블에서 정보를 추출한다

 

TABLE [{PAGE} {PAGE} {PAGE} ....]  여기서 찾는 형태이다

 

 

RANDOM ACCESS (한건을 읽기 위해 한 페이지씩 접근 하는것 으로 트리 위에서 부터 아래로 노드를 찾아가는 과정)

BOOKMARK LOOKUP : 노드를 찾은다음 RID를 통해 행을 실제 TABLE 에서 찾는 것을 말함 

반응형
반응형

함수명을 쓰고 OVER 쓴다음 범위를 지정해주면 된다

OVER( [PARTITION] [ORDER BY] [ROWS] ) : 이걸로 서브집합을 선택해주는 것

 

ROW_NUMBER : 맨 왼쪽 인덱스 번호

RANK : 순위인데 중복되면 중복된 것 만큼 순위는 건너 띔

DENSE_RANK : 만약 1등이 두명이 나오면 그다음 3등이 2등이 되는 RANK

NTILE : 백분율로 가장 상위가 1% 로 1 로 나옴 , 가장 하위가 100

 

 

 

파티션의 경우 playerID로 Group by 와 유사하게 할수 있는데 다른 점은 Group by 는 하나로 묶어 버리지만 Partition 의 경우에는 각 행별로 모두 보이게 된다

결과

 

PlayerID 별로 랭킹이 매겨져 있는 걸 볼 수가 있다

 

 

 

LAG(바로 이전 : 나열된것의 바로 위) ,LEAD(바로 다음 : 나열된 것을 바로 아래)

 

 

 

결과

 

playerID 기준으로 salary 값 위아래를 보면 prevSalary 와 nextSalary 값이 있다는 것을 알 수 있다

 

 

FIRST_VALUE : 가장 처음 값 : 여금 여기선 큰 

LAST_VALUE : 가장 마지막 값 : 지금 여기선 작은 값

 

 

결과를 보면 WORST 가 제대로된 작은 값이 아닌데 

그 이유는 Desc 로 정렬하여 큰 값은 나오게 되는데 나오는 검색 과정이

동일 아이디의 가장 위에서 부터 하나씩 증가 시키면서 그 중에 큰걸 보게 되는 순서라

4500000 

4500000 , 2750000

4500000 , 2750000, 500000

이런순을 검색하면서 그 중 가장 큰걸 뽑아오는게 FIRST_VALUE 라서 best 는 제대로 되지만 worst 는 제대로 되지 않는다는걸 알 수 있다

 

 

 

 

 

 

그래서 LAST_VALUE 의 경우에는 제일 처음부터 증가하는 형태인 키워드 (UNBOUNDED PRECEDING) 를 넣어주고 현재 까지의 진행라인에 대한  현재 CURRENT_ROW 까지 는 BEST 라인이 범위가 되도록 ROWS 를 지정해주면 되고 

 

그다음 LAST_VALUE 에대해선 현재 라인 CURRENT_ROW 부터 동일 아이디에 대해서 마지막 라인을 얘기하는 UNBOUNDED FOLLOWING 을 넣어주면제대로된 WORST 값이 나오게 된다

 

제대로된 결과

 

 

 

오라클에서 OVER 절에 WINDOWING 절을 처음 사용할 때 ROWS와 RANGE가 어떤 차이점이 있는지 많이 헷갈릴 수 있다. 간단히 설명하면 ROWS는 각 행의 위치고, RANGE는 값의 범위라고 생각하면 된다. 아래의 예제를 여러 번 반복해서 보면 많이 어렵지 않게 이해할 수 있을 것이다.

 

아래의 키워드 의미부터 숙지를 하고 예제를 보면 더욱 이해하기 쉬울 것이다.

키워드 설명
 ROWS  물리적인 행 단위
 RANGE  논리적인 행 집합
 CURRENT ROW  현재 행
 UNBOUNDED PRECEDING  첫 번째 행
 UNBOUNDED FOLLOWING  마지막 행
 [위치] PRECEDING  [위치] 이전 행
 [위치] FOLLOWING  [위치] 다음 행

 

 

REF : https://gent.tistory.com/473

반응형

'서버(Server) > DB' 카테고리의 다른 글

DB : 복합 인덱스  (0) 2023.03.02
DB : INDEX  (0) 2023.03.01
Northwind and pubs sample databases for Microsoft SQL Server  (0) 2023.02.23
DB : 테이블을 변수에, IF 문, WHILE 문, BREAK , CONTINUE  (0) 2023.02.20
DB : BATCH : GO  (0) 2023.02.19
반응형

Northwind  Database 인데 오리지널 버전은 SQL Server 2000 으로 만들어졌었지만 

최근 MS Server 에서도 파일을 집적 복붙하여 사용가능하다

instnwnd.sql 파일

 

생성하고 나면 웹사이트 같은 느낌이라 테스트해보기에 좋다 

고개 정보, 주문, 제품, 지역, 직원 등등의 정보들이 있다

 

Northwind and pubs sample databases for Microsoft SQL Server

This folder contains scripts to create and load the Northwind (instnwnd.sql) and pubs (instpubs.sql) sample databases.

These scripts were originally created for SQL Server 2000.

 

 

Before you begin

To run this sample, you need a tool that can run Transact-SQL scripts. You can run the scripts in the following tools:

Run the scripts in SSMS

 

 

https://github.com/microsoft/sql-server-samples/tree/master/samples/databases/northwind-pubs

 

GitHub - microsoft/sql-server-samples: Azure Data SQL Samples - Official Microsoft GitHub Repository containing code samples for

Azure Data SQL Samples - Official Microsoft GitHub Repository containing code samples for SQL Server, Azure SQL, Azure Synapse, and Azure SQL Edge - GitHub - microsoft/sql-server-samples: Azure Dat...

github.com

https://raw.githubusercontent.com/microsoft/sql-server-samples/master/samples/databases/northwind-pubs/instnwnd.sql

반응형

'서버(Server) > DB' 카테고리의 다른 글

DB : INDEX  (0) 2023.03.01
DB : 윈도우함수  (0) 2023.02.28
DB : 테이블을 변수에, IF 문, WHILE 문, BREAK , CONTINUE  (0) 2023.02.20
DB : BATCH : GO  (0) 2023.02.19
DB : 변수  (0) 2023.02.18
반응형

DB 에서도 조건문 사용이 가능하다

IF ELSE 문

DECLARE @i AS INT =100;

IF @i = 100
	PRINT('100')
ELSE
	PRINT('NOT')

 

여러줄 묶을땐 BEGIN END 로 묶어야 한다

 

DECLARE @i AS INT =100;

IF @i = 100
	BEGIN
		PRINT('100')
		PRINT('101')
	END
ELSE
	PRINT('NOT')

 

 

WHILE 문 또한 사용가능하고 CONTINUE 와 BREAK 또한 있다

GO
DECLARE @i AS INT =0;
WHILE @i <= 10
BEGIN 
	SET @i = @i + 1;
	IF @i = 6 CONTINUE;
	PRINT @i;
END

 

 

 

 

원래 CREATE TABEL 명령어는 이런 형태였다

EX)

--테이블 만들기
create table accounts(
	accountId integer not null,
	accountName varchar(10) not null,
	coins integer default 0,
	createdTime DATETIME
);

 

 

그런데 테이블도 변수로 만들 수 있다
create table accounts 과 비슷하지만 다른건 tempDB 라는 곳에 임시로 데이터 베이스가 저장된다

--테이블도 변수로 만들 수 있다
--create table accounts( 과 비슷하지만 다른건 tempDB 라는 곳에 임시로 데이터 베이스가 저장된다
--

GO
DECLARE @test TABLE
(
	name VARCHAR(50) NOT NULL,
	salary	INT NOT NULL
);

INSERT INTO @test
SELECT p.nameFirst + ' ' + p.nameLast, s.salary
FROM players AS p
		INNER JOIN salaries AS s
		ON p.playerID = s.playerID;


SELECT *
FROM @test;

다음은 실행 결과다

반응형

'서버(Server) > DB' 카테고리의 다른 글

DB : 윈도우함수  (0) 2023.02.28
Northwind and pubs sample databases for Microsoft SQL Server  (0) 2023.02.23
DB : BATCH : GO  (0) 2023.02.19
DB : 변수  (0) 2023.02.18
DB : TRANSACTION  (0) 2023.02.17
반응형

변수의 유효범위 설정 키워드는 배치명령어인 GO 이다

GO 영역 전후로 별도의 영역인데

--변수 선언 , 생성함과 초기화 할수도있고 
DECLARE @i as INT = 10;

--생성만 한다음, 나중에 넣을 수도 있다
DECLARE @j as INT;
SET @j = 20;

SELECT @i,  @j;



---배치---
--이전에 썼던 변수는 없는걸로 치고 다시 변수를 선언할수 있는 명령어 go 
GO
DECLARE @i AS INT =100;

SELECT @i;

 

 

 


--배치는 하나의 묶음으로 분석 실행된다

에러가 나도 go 다음 묶음 구문은 실행된다 별도 영역이라서

 

SELECT *
FO PLAYERS;

메세지에 보면 다음 처럼 에러지만

 

 

SELECT *
FO PLAYERS;

GO

SELECT * 
FROM salaries;

이 로직의 경우 go 이후 하단은 정상적이라서 에러 메세지가 뜨긴 하지만 다음 처럼

result 탭에서는 select 구문이 나온걸 알수 있다 즉 별도로 실행된다

반응형

+ Recent posts