개발팁2016. 8. 30. 09:39

오라클의 Sequence 와 비슷한 기능으로
MS-SQL 에는 Identity 라는것이 있다.
Identity 는 오라클의 Sequence 와는 다르게, 테이블 컬럼의 속성으로 되어 있다.

 

CREATE TABLE test1
(
    seq int identity(1,1),
    val varchar(50) NULL
)

 

identity(1,1) 에서 앞에 1은 시작값, 뒤에 1은 증감값이다.
즉, 최초값을 1로 할수도 있고, 1000000 으로 할수도 있으며,
증감을 1씩 증가하게 할수도 있고, 100씩 증가하게 할수도 있으며,
반대로 -1 씩 감소하게 할수도 있다는 뜻이다.

 

insert into test1(val)
vals ( '1' );
insert into test1(val)
vals ( '2' );

 

테이블에 인서트 할때는 identity 컬럼을 지정할수 없다.
identity 컬럼을 제외한 컬럼에만 값을 지정하면, identity 컬럼은 자동으로 값이 할당된다.

 

seq val
1 1
2 2

 

DBCC CHECKIDENT 를 이용하여, 특정 테이블의 identity 값을 확인해볼 수 있다.

 

현재 identity 컬럼의 값이 얼마인지 확인하는 방법

DBCC CHECKIDENT('test1', NORESEED)

 

identity 값을 변경하는 방법

DBCC CHECKIDENT('test1', RESEED, 1000)

 

이 값은 문자열 형태로 출력이 된다.

 

 

BTW, 프로시저에서 identity 컬럼이 있는 마스터 테이블에 insert 를 한 후에,
방금 insert 하면서 생성된 identity 컬럼의 값이 필요한 경우가 있다.

 

identity 값을 실제 값으로 가져오는 방법은 3가지가 있다.

 

SELECT @@IDENTITY
SELECT IDENT_CURRENT('test1')
SELECT SCOPE_IDENTITY() 

 

1. @@IDENTITY

최종 입력된 identity 값을 가져오지만, 그 값이 a라는 테이블이든지 b라는 테이블이든지, 상관이 없이,
현재 세션내의 모든 범위에서 가져온다.

 

2. IDENT_CURRENT

지정한 테이블의 마지막 identity 값을 가져온다. 그러나, 동시에 테이블에 인서트 되는 경우에도,
다른 사람이 인서트한 마지막 값을 가져올수가 있다.

 

3. SCOPE_IDENTITY

현재 세션의 범위 내에서만 가져온다. 즉, insert 하고 나서, 생성된 identity 값만 가져온다.

 

일반적으로, @@IDENTITY 를 사용하는 경우가 있는데, 이런경우, 논리적 오류가 나올수 있다.
프로시저 안에서 insert 후에 identity 값을 가져올때는 항상 SCOPE_IDENTITY 를 사용하도록 하자.

 

추가적으로, 중간에 데이터가 삭제되면서, identity 컬럼의 값이 중간에 비어 있을때,
중간 값을 채워주고 싶다면 아래와 같이 하면 된다.

 

SET IDENTITY_INSERT test1 ON;

 

insert into test1(seq, val)
vals(9, 'inserted');

 

SET IDENTITY_INSERT test1 OFF;

 


IDENTITY_INSERT ON 으로 하면, identity 컬럼에 수동으로 값을 넣겠다는 뜻이다.

 

 

Posted by 헝개
개발팁2016. 8. 26. 10:46

 

MS-SQL 에 보면, COALESCE 라는 함수가 있다.

(근데, 이거 어떻게 발음하는건가요? 단어가 굉장히 어려워 보이는데..;;)

 

SELECT COALESCE(@a, @b, @c, 'else');

 

앞에서 순서대로 null 이 아닌 값을 찾아오는 함수다.

 

C# 에서도 비슷한 기능을 하는 연산자가 있다.

 

? 라는 연산자는 3항 연산자로 쓰이는데,

 

a == b ? 'same' : 'not same';

 

물음표를 2개 써서,   value1 ?? value2

 

앞에서 순서대로 null 이 아닌 값을 찾는다.

 

물론, 2개 이상의 값에서 찾는다면 , 순서대로 뒤에 ?? 를 나열하면 된다.

 

int? a = null;
int? b = null;
int? c = 100;

int? k = a ?? b ?? c;

int? 는 int 가 primitive type 이기 때문에 null 값이 들어갈 수 없다. int? 는 null 값을 허용한다는 뜻이다.

 

a, b, c 중에 앞에서 순서대로 null 이 아닌 값이 k 에 할당된다.

 

 

if(strV1 != null)

{

    System.out.println("the value is " + strV1);

}

else if(strV2 != null)

{

    System.out.println("the value is " + strV2);

}

else

{

    System.out.println("the value is else");

}

 

if / else 로 구성된 문장은 3항 연산자로 아래와 같이 쓸 수 있다.

 

System.out.println("the value is " + ( strV1 != null ? strV1 : strV2 != null ? strV2 : 'else' ) );

 

이는 다시 ?? 연산자로 아래와 같이 사용이 가능하다.

 

System.out.println("the value is " + ( strV1 ?? strV2 ?? 'else' ) );

 

 

Posted by 헝개
개발팁2016. 8. 25. 10:51

안드로이드 앱을 만들어보신분이라면 토스트 메세지가 어떤건지 바로 이해하실것이다.
순서대로 올라와서, 자동으로 사라지는 메세지 박스이다.

구조가 간단해서 직접 만들어보셔도 좋을것이다.
아래 링크에서 기존에 만들어진 jquery 플러그인을 다운받아보자.

 

https://github.com/Soldier-B/jquery.toast

 

 

html 파일에 css 와 js 파일을 넣고, 토스트메세지를 호출하면 된다.

 

<link rel="stylesheet" type="text/css" href="jquery.toast.min.css" />
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="jquery.toast.min.js"></script> 

 

 

$.toast('메세지', 옵션);

 

메세지는 html 코드로, 문자열에 html 태그를 포함할 수도 있다.
옵션은 json 방식으로 넣어주면된다.
json 은 javascript object notation 으로, { } 로 둘러싸인 객체 표현법이다.


 

$.toast('일반 메세지입니다.');

기본 메세지에, 기존옵션을 사용하여, 5초후에 사라진다.

 

$.toast('<h4>위험</h4> 이것은 위험 메세지입니다.', { type: 'danger', duration : 2000 } );

경고 메세지로, duration 값을 2000 으로 주었기 때문에, 2초후에 사라진다.

 

$.toast('<h4>정보</h4> 이것은 정보 메세지입니다.', { type: 'info', sticky : true } );

정보 메세지이고, sticky 에 true라고 주었기 때문에, x 버튼을 눌러 닫기 전까지는 계속 표시된다.

 

$.toast('<h4>성공</h4> 이것은 성공 메세지입니다.', { type: 'success', duration: 3000 } );

성공 메세지이고, 3초후에 자동으로 사라진다.

 

그외에도 toast 의 config 를 설정할 수 도 있다.

이 config 값은 최초에 1회만 설정하면, 이후 toast 메세지는 모두 설정값에 따라 나타난다.

 

기존으로 toast 메세지는 화면의 center 에 나오며, 메세지박스 사지는 500 이다.

 

$.toast.config.align = 'right';
$.toast.config.width = 300;

 

 

 

 

Posted by 헝개
개발팁2016. 8. 16. 10:02

숫자를 한글로 표시하는 방법이다.

은행사이트에서 이체 할때 보면, 숫자로 입력하면, 그 옆에 한글로 표시되는걸 봤을것이다.

 

123,000 => 일십이만삼천 원

 

예제는 자바스크립트 코드로 작성하였지만, 소스가 단순해서, 사용하는 언어로 포팅하여 사용하면 된다.

 

우선, 0 ~ 9 까지의 숫자를 한글로 정의해보자.

 

var arrNumberWord = new Array("","일","이","삼","사","오","육","칠","팔","구");


 

0은 블랭크로 두고, 각각의 숫자에 대해서, 배열로 정의하면, 배열인덱스로 바로 한글명으로 치환이 가능하다.

 

arrNumberWord[9] => 구

arrNumberWord[5] => 오

 

그다음, 4자리마다 끊어지는, 단위를 만들어보자.

영어에서는 3자리로 끊어지기 때문에, 콤마위치가 3자리에 와서, 한눈에 숫자를 파악하기 편한데,

우리나라는 4자리마다 끊어지는데, 콤마위치를 3자리에 뒤서, 영~ 좋지 않다.

 

var arrDigitWord = new  Array("","십","백","천");

 

1, 10, 100, 1000

1의자리는 치환이 없고, 각각, 십, 백, 천 이 된다.

7000 => 위의 arrNumberWord 배열로 칠로 바꾸고, 뒤에 000 => 길이가 3이기 때문에, 배열 인덱스 3으로 가져오면, 천으로 바뀌어, 칠천 이 된다.

 

이렇게, 각 자리수를 4로 나눈 나머지 를 처리하면된다.

 

 

마지막으로는, 4자리마다 세는 단위를 넣어보자.

만, 억, 조 가 해당된다.

10,000 => 만, 100,000,000 => 억

 

var arrManWord = new  Array("","만","억", "조");

 

이 arrManWord 는 자리수를 4로 나눈 몫으로 처리하면 된다.

10,000 의 경우 1을 제외하고, 0000 자리수가 4개이기 때문에, 4로 나누면 1이 나오고, 나머지가 0 인경우,

arrManWord[4로 나눈 정수값] = 억 이 된다.

 

 

<html>
<head>
<script>

      // 1 ~ 9 한글 표시
      var arrNumberWord = new Array("","일","이","삼","사","오","육","칠","팔","구");
      // 10, 100, 100 자리수 한글 표시
      var arrDigitWord = new  Array("","십","백","천");
      // 만단위 한글 표시
      var arrManWord = new  Array("","만","억", "조");



      // Copyright 헝그리개발자(https://bemeal2.tistory.com)
      // 소스는 자유롭게 사용가능합니다. Copyright 는 삭제하지 마세요.
      function fn_change_hangul_money(txt_id)
      {
            var num_value = txt_id.value;
            var num_length = num_value.length;



            if(isNaN(num_value) == true)
                  return;



            var han_value = "";
            var man_count = 0;      // 만단위 0이 아닌 금액 카운트.



            for(i=0; i < num_value.length; i++)
            {
                  // 1단위의 문자로 표시.. (0은 제외)
                  var strTextWord = arrNumberWord[num_value.charAt(i)];



                  // 0이 아닌경우만, 십/백/천 표시
                  if(strTextWord != "")
                  {
                        man_count++;
                        strTextWord += arrDigitWord[(num_length - (i+1)) % 4];
                  }



                  // 만단위마다 표시 (0인경우에도 만단위는 표시한다)
                  if(man_count != 0 && (num_length - (i+1)) % 4 == 0)
                  {
                        man_count = 0;
                        strTextWord = strTextWord + arrManWord[(num_length - (i+1)) / 4];
                  }



                  han_value += strTextWord;
            }

            if(num_value != 0)
                  han_value = "금 " + han_value + " 원";



            document.all.han_money.innerText = han_value;
      }


</script>
</head>

<body>
<form>
<span class="han_money" id="han_money">금액표시</span>
<br>
<input type="text" id="txt_money" maxlength="12" onkeyup="fn_change_hangul_money(this);">

</form>

</body>

</html>

 

 

간단하게 구현해본 자바스크립트 소스이다.

textbox 에 입력된 숫자를 받아서, 한글로 표시해준다.

 

 

 

Posted by 헝개
개발팁2016. 8. 10. 14:55

C# 에서 string 타입으로 숫자가 저장되어 있을때 int 타입으로 변경하는 몇가지 방법이 있다.

 

string n = "1234";

 

라고 할때,

 

 

Method.1

 

int outValue = Convert.ToInt32(n);

 

 

Method.2

 

int outValue = int.Parse(n);

 

 

Method.3

 

int outValue;

int.TryParse(n, out outValue);

 

 

그리고, 여기서 소개할 방법은, C# 의 기본 메서드를 이용하지 않고, 자체적으로 제작을 해보겠다.

 

문자로 저장된 '0' 이라는 값은 실제 메모리상에는 코드값으로 저장이 되어 있다.

우리가 ASCII 코드라고 부르는 값이다.

실제로 '0' 은 48 이라는 값으로 저장이 되며, '9' 는 57 이라는 값으로 저장이 된다.

 

하지만, '0' ~ '9' 가 어떤 값으로 저장이 되는지 알필요도 없다.

 

'0' - '0' 은 0 이 되며,

'1' - '0' = 1 이 되고,

'9' - '0' = 9 가 된다.

 

 

string n = '1234';

int outValue = 0;

 

for (int i = 0; i < n.Length; i++)
{

    outValue = outValue * 10 + (n[i] - '0');

}

 

outValue 를 확인해보면, 1234 라는 값이 들어있는것을 확인할 수 있다.

 

이를 Method.4 라고 부르고,

 

 

Method.1 ~ Method.4 까지 퍼포먼스 테스트를 해봤다.

 

1,000,000 개의 4자리 문자열로 된 배열을 만들고,

 

이를 int 형으로 변환하는 4가지 방법을, 각각 20회씩 실행해 봤다.

 

 

 

 

Convert.ToInt32
int.Parse
int.TryParse
3가지 방법 모두, 비슷한 결과가 나왔다.

4자리 문자열 100만개를 변환하는데, 0.13 ~ 0.14 초가 걸렸다.

 

반면, 직접 만들어서 int로 변환한 경우는, 0.03, 0.04 초가 걸렸다. 속도면에서, 기본 메서드 보다, 4배이상 빨랐다.

 

Posted by 헝개
개발팁2016. 8. 9. 08:29
Erwin Data Modeler R7 이후부터는 Physical Model 과 Logical Model 간에 컬럼 순서가 별도로 관리가 된다.
한군데서 컬럼순서를 바꾸면, 다른곳에서는 순서가 다르게 나오게 된다.

 

이를 자동적으로 동기화 해주는 방법은 없고, 수동으로 동기화를 해야 한다.

 

 

1. 테이블명을 더블클릭하면, Columns / Attributes 창이 뜬다.

 

 

 

Physical 을 기준으로 Logical 을 변경하는경우, Logical 에서 클릭한다.
Logical 을 기준으로 Physical 을 변경하는경우, Physical 에서 클릭한다.

 

 

2. Reset 버튼 클릭

 

 

 

 

3. Reset Order 클릭

 

 

 

4. 전체테이블 / 클릭한테이블 만 바꿀지 선택

 

 

 

Reset attribute order from column order for entity ( ) <= 1개의 테이블만 변경할때
Reset attribute order from column order for for all entities of the model <= 모든 테이블을 변경할때

 

 

 

Posted by 헝개
개발팁2016. 8. 8. 08:21

C# 의 데이터에는 Value Type 와 Reference Type 이 존재한다.

 

Value Type 은 그 자체가 값인 경우이다.
Reference Type 은 별도의 메모리 공간에 인스탄스가 생성이 되고,
메모리의 위치만을 값으로 가지는 경우다.

 

배열이나 클래스의 객체는 Reference Type 으로, 실제값은 별도로 저장이된다.

 

Class 안에 멤버가 모두 Value Type 이라면, 단순히 Clone 을 통해서, 멤버 값이 전부 새로운 값으로 복사가 된다.
그러나, 한 Class 안에 Value Type, 배열, 클래스 객체가 공존한다면,
이 객체를 Clone 을 하게 되면, Value Type 은 정상적으로 복사가 되지만, 나머지 값은 원래 객체의 주소값이 복사가 된다.

 

원래 객체 / 클론 객체가 모두 동일한 메모리 위치를 가리키기 때문에, 한쪽에서 멤버객체의 값을 변경한다면,
다른곳에서는 의도치 않게 값이 변경되는 결과를 보게 된다.

 

Deep Clone 이라는 것은 Value / Reference Type 모두 새로운 값으로 복사를 하는 것이다.

 

 

2가지 방법이 있는데, 복사하려는 객체가 Serializable 객체라면,
// 2. Serializable 객체에 대한  Deep Clone 을 이용하여,
MemoryStream 을 통해 Serialize 하여 복사가 된다.

 

그렇지 않은 Class 는,
// 1. Deep Clone 구현 을 이용하여, 복사를 하면 된다.

Class 안의 멤버는 Value Type 과 Reference Type 이 있고,
Reference Type 객체 안에는 또다시, Value Type 과 Reference Type 의 멤버가 있고,
그 안에 있는 Reference 에는 또다시 Value Type 과 Reference Type 있다.
이런식으로 계층간에 여러 단계의 객체가 존재할 수가 있다.
이를 모두 복사해야 하기 때문에, Circular 또는 Recursive 를 통해서 값을 복사를 해야 한다.

 

using System;
using System.Reflection;
using System.Collections.Generic;
using System.Runtime.Serialization.bformatters.Binary;
using System.IO;

namespace Hungry.Developer
{



    // Copyright 헝그리개발자(https://bemeal2.tistory.com)
    // 소스는 자유롭게 사용가능합니다. Copyright 는 삭제하지 마세요.

    // C# 객체를 복사해주는 클래스
    public class ObjectCopy
    {
        // 1. Deep Clone 구현
        public static T DeepClone<T>(T obj)
        {
            if (obj == null)
                throw new ArgumentNullException("Object cannot be null.");


            return (T)Process(obj, new Dictionary<object, object>() { });
        }



        private static object Process(object obj, Dictionary<object, object> circular)
        {
            if (obj == null)
                return null;



            Type type = obj.GetType();


            if (type.IsValueType || type == typeof(string))
            {
                return obj;
            }



            if (type.IsArray)
            {
                if (circular.ContainsKey(obj))
                    return circular[obj];



                string typeNoArray = type.FullName.Replace("[]", string.Empty);
                Type elementType = Type.GetType(typeNoArray + ", " + type.Assembly.FullName);
                var array = obj as Array;
                Array arrCopied = Array.CreateInstance(elementType, array.Length);

                circular[obj] = arrCopied;


                for (int i = 0; i < array.Length; i++)
                {
                    object element = array.GetValue(i);
                    object objCopy = null;



                    if (element != null && circular.ContainsKey(element))
                        objCopy = circular[element];
                    else
                        objCopy = Process(element, circular);



                    arrCopied.SetValue(objCopy, i);
                }



                return Convert.ChangeType(arrCopied, obj.GetType());
            }



            if (type.IsClass)
            {
                if (circular.ContainsKey(obj))
                    return circular[obj];



                object objValue = Activator.CreateInstance(obj.GetType());
                circular[obj] = objValue;
                FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);



                foreach (FieldInfo field in fields)
                {
                    object fieldValue = field.GetValue(obj);

                    if (fieldValue == null)
                        continue;



                    object objCopy = circular.ContainsKey(fieldValue) ? circular[fieldValue] : Process(fieldValue, circular);
                    field.SetValue(objValue, objCopy);
                }

                return objValue;
            }
            else
                throw new ArgumentException("Unknown type");
        }
 

        // 2. Serializable 객체에 대한  Deep Clone
        public static T SerializableDeepClone<T>(T obj)
        {
            using (var ms = new MemoryStream())
            {
                var bformatter = new Binarybformatter();
                bformatter.Serialize(ms, obj);
                ms.Position = 0;

                return (T) bformatter.Deserialize(ms);
            }
        }



    }

}


 

 

 

Posted by 헝개
개발팁2016. 8. 2. 07:59

OOP 디자인 패턴중에 Singleton 이라는 것이 있다.

 

일반적으로 클래스는 new 키워드를 통해서, 인스턴스를 생성하지만,
Singleton 클래스는 인스턴스가 1개만 생성이 된다.

 

생성자 즉, Constructor 를 private 으로 선언한다.
이렇게 되면, 외부에서 new 키워드로 인스턴스를 생성할 수 없다.
클래스 내부에서만 new 키워드를 사용하여, 자기 자신의 인스턴스를 생성이 가능하다.

 

아래 클래스 선언 방법대로, getInstance 를 static 으로 선언하고,
자기 자신의 인스턴스를 돌려주게 되면, 단 1개의 인스턴스만 생성하여 사용하게 된다.

 

public class MyClass
{
 private MyClass() { }

 private static MyClass instance;

 public static MyClass getInstance()
 {
  if(instance == null)
   instance = new MyClass();

  return instance;
 }

 public int Method1()
 {
  return 1;
 }
}

 

 

사용시에는 아래와 같이 사용한다.

 

MyClass myc = MyClass.getInstance();
int n = myc.Method1();

Posted by 헝개
개발팁2016. 7. 29. 10:01

IIS 웹서버로 요청되는 모든 웹페이지/웹서비스 요청에 대해, 퍼포먼스 로그를 남기는 방법이다.

 

퍼포먼스를 체크하는 방법은, 시작시간을 기록하고,

웹페이지나 특정 메서드가 실행되고 나서, 종료시간과의 차이를 계산해서 기록을 하면 된다.

 

DateTime _startTime = DateTime.Now;

 

// 웹페이지 / 메서드 실행.. 코드

 

TimeSpan span = DateTime.Now - _startTime;
string perfLog = span.Milliseconds.ToString() + " ms";


 

간단한 코드지만, 이를 매번 웹페이지마다 삽입하여 사용하는것은 어려우니,

HttpModule 을 이용하여, 자동으로 기록하는 방법이 있다.

 

IHttpModule 이라는 인터페이스를 이용하여, WebServiceAuthenticationModule 을 만들것이다.

이는 원래, 인증 체크를 위한 클래스인데, 이 안에, 퍼포먼스 로그 기능을 추가한 것이다.

 

별도의 프로젝트를 만들고, WebServiceAuthenticationModule  클래스를 만들어서,

Application_BeginRequest 와 Application_EndRequest 이벤트를 구현할 것이다.

이 프로젝트를 빌드해서, 별도의 DLL 을 만들면 된다.

 

ASP.NET 프로젝트에서 해당 DLL 을 참조 추가를 하고, Web.Config 에 아래와 같이 모듈을 추가 하면된다.

 

    <system.webServer>
      <!-- 인증체크 설정 -->
      <modules>
        <add  name="WebServiceAuthenticationModule"
              type="Hungry.Developer.HttpModule.WebServiceAuthenticationModule"/>
      </modules>
      ...

    </system.webServer>


 

이렇게 되면, 해당 ASP.NET  웹서비스의 모든 웹페이지는

항상 페이지 요청시, WebServiceAuthenticationModule 의 Application_BeginRequest 가 먼저 실행이 될것이고,

요청이 완료되면, Application_EndRequest 를 실행하게 될것이다.

 

이를 이용하여, 맨처음 보여줬던, 퍼포먼스 로그를 기록하면 된다.

 

// Copyright 헝그리개발자(https://bemeal2.tistory.com)
// 소스는 자유롭게 사용가능합니다. Copyright 는 삭제하지 마세요.
public sealed class WebServiceAuthenticationModule : IHttpModule
{
        private DateTime _startTime;    // 퍼포먼스 로깅을 위한 시작시간 저장 변수

        public void Init(HttpApplication app)
        {
            app.BeginRequest +=
                (new EventHandler(this.Application_BeginRequest));
            app.EndRequest +=
                (new EventHandler(this.Application_EndRequest));

        }
        private void Application_BeginRequest(object source, EventArgs e)
        {
             // 웹페이지 / 웹서비스 실행 시작시간 기록
            _startTime = DateTime.Now;

             // 인증체크
             ...
        }

        // 요청완료시, 퍼포먼스 로그 기록
        private void Application_EndRequest(object source, EventArgs e)
        {
            HttpApplication application = (HttpApplication)source;

            TimeSpan span = DateTime.Now - _startTime;
            string perfLog = application.Request.Url + " / " + span.Milliseconds.ToString() + " ms";
            LogWriter.PerfLog(perfLog);
        }

}

 

LogWriter 는 string 값을 파일에 추가하는 클래스다.

파일에 저장을 하던, DB에 저장을 하던, 원하는 방식으로 구현하면 된다.

 

아래는 위의 코드를 그대로 이용한, 실제 로그파일의 내용이다. 이중에 Performance 만 보면 된다.

 

2016-07-29 09:27:37.036 (Performance) From< HttpModule > :http://삐~/WebService/삐~Service.asmx / ReadBy / 100 ms
2016-07-29 09:27:37.359 (Debug) From< HttpModule > :인증체크 : 0 / cachekey(cUwze7r0rMTQCVHW9eXE/A==) / authkey(khMBxeLsajUjv7zdBcnrdG33JUQahNjqCqNzmH5G6nk=) / menuid(149) / permission(1)
2016-07-29 09:27:37.361 (Debug) From< HttpModule > :권한체크 : 0 / menuid(149) / permission(1) / requestURL(http://삐~/WebService/삐~Service.asmx) / webMethodName(ReadBy) / userIP(127.0.0.1)
2016-07-29 09:27:37.430 (Debug) From< HttpModule > :인증체크 : 0 / cachekey(cUwze7r0rMTQCVHW9eXE/A==) / authkey(khMBxeLsajUjv7zdBcnrdG33JUQahNjqCqNzmH5G6nk=) / menuid(149) / permission(1)
2016-07-29 09:27:37.433 (Debug) From< HttpModule > :권한체크 : 0 / menuid(149) / permission(1) / requestURL(http://삐~/WebService/삐~Service.asmx) / webMethodName(ReadBy) / userIP(127.0.0.1)
2016-07-29 09:27:37.470 (Performance) From< HttpModule > :http://삐~/WebService/삐~Service.asmx / ReadBy / 113 ms
2016-07-29 09:27:37.514 (Performance) From< HttpModule > :http://삐~/WebService/삐~Service.asmx / ReadBy / 87 ms
2016-07-29 09:27:46.580 (Debug) From< HttpModule > :인증체크 : 0 / cachekey(cUwze7r0rMTQCVHW9eXE/A==) / authkey(khMBxeLsajUjv7zdBcnrdG33JUQahNjqCqNzmH5G6nk=) / menuid(139) / permission(1)
2016-07-29 09:27:46.589 (Debug) From< HttpModule > :권한체크 : 0 / menuid(139) / permission(1) / requestURL(http://삐~/WebService/삐~Service.asmx) / webMethodName(삐~) / userIP(127.0.0.1)
2016-07-29 09:27:46.893 (Performance) From< HttpModule > :http://삐~/WebService/삐~Service.asmx / SponsorSearch / 315 ms 

 

Posted by 헝개
개발팁2016. 7. 26. 09:46

C# 어플리케이션, 보통 winform 형태의 어플리케이션에서
캐쉬를 만들고 처리하는 방법이다.

 

캐쉬는 데이터를 저장하고 꺼내쓰기 위해서 사용을 하는데,
데이터를 읽기 위해서 매번, 저장소에서 꺼내오는 것이 아니라,
저장소에서 읽어서 캐쉬에 저장을 해놓고, 캐쉬에서 꺼내쓴다면,
속도 향상과, 저장소에 대한 트래픽을 줄일수 있어 효율적으로 사용이 가능하다.

 

아래와 같은 방법으로 저장소에 대해서 자료를 가져오는 클래스를 정의하여 사용하면,
데이터를 가져다 쓰는 객체는 내부 구조에 상관없이 동일한 인터페이스로 사용할 수 있을것이다.


자료 클래스


- 자료읽기 요청
  1. 캐쉬에서 자료 읽기
     데이터가 없으면, 저장소에서 읽어옴
     캐쉬에 자료 저장
  2. 읽어온 자료 리턴

 

- 자료쓰기 요청
  1. 저장소에 자료 쓰기
  2. 캐쉬 삭제

 


저장소는 외부에서 변경되지 않는다는 보장이 되어야 한다.
외부에서 변경된다면, 캐쉬데이터와 저장소데이터간에 차이가 발생하게 된다.

실제로 Application Cache Object 를 아래와 같이 구현하였다.


 

    // Application 캐쉬 객체
    class AppCacheObject
    {
        private static HttpRuntime _httpRuntime;

        public static Cache Cache
        {
            get
            {
                EnsureHttpRuntime();
                return HttpRuntime.Cache;
            }
        }

        private static void EnsureHttpRuntime()
        {
            if (null == _httpRuntime)
            {
                try
                {
                    Monitor.Enter(typeof(AppCacheObject));
                    if (null == _httpRuntime)
                    {
                        _httpRuntime = new HttpRuntime();
                    }
                }
                finally
                {
                    Monitor.Exit(typeof(AppCacheObject));
                }
            }
        }

    } 

 

Cache 는 System.Web.Caching.Cache 의 객체이며, Get Property 로 사용한다.
System.Web 을 사용하기 때문에, System.Web.HttpRuntime 의 Cache 객체로 생성한다.

 

이는 Cache 객체에 대한 정의이며, 이를 이용하여,
캐쉬를 저장하는 SetCache
캐쉬를 조회하는 GetCache
캐쉬를 삭제하는 RemoveCache

 

여기서 한가지 중요한 것이, 캐쉬종류이다.
Absolute Expiration 과 Sliding Expiration 이라는것이 있다.

 

Absolute Expiration 은 지정된 만료시간이 지나면, 무조건 캐쉬가 삭제되는 것이고,
Sliding Expiration 은 동일하게 지정된 만료시간에 캐쉬가 삭제되지만, GetCache 를 할때마다 자동으로 만료시간이 연장이 된다.

 

일반적으로 로그인 처리를 하여, 로그인정보를 저장할때 Sliding Expiration Cache 를 사용하면,
지정된 시간이 지나면, 자동로그아웃이 되지만, 계속 작업을 한다면 (작업시마다 캐쉬를 사용하여 로그인정보를 확인) 로그인이 풀리지 않게 되는것이다.

 

실제로, AppCacheManager 라는 클래스로,
SetCache / GetCache / RemoveCache 와 슬라이딩 캐쉬를 구현한 것이다.
슬라딩캐쉬는 SetCache 에서만 지정해주면, GetCache / RemoveCache 는 동일하게 사용된다.

 

    // Copyright 헝그리개발자(https://bemeal2.tistory.com)
    // 소스는 자유롭게 사용가능합니다. Copyright 는 삭제하지 마세요.

    // Application 캐쉬
    public class AppCacheManager
    {
        // 인증용 캐쉬 저장 : 슬라이딩 만료 캐쉬 - minute 후에 만료되지만, GetCache 요청이 있으면, 만료시간이 연장됨
        public static bool SetSlidingCache(string name, object value, int minute)
        {
            bool bvalue = true;

            try
            {
                AppCacheObject.Cache.Add(name, value, null, System.Web.Caching.Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(minute), CacheItemPriority.Normal, null);
            }
            catch
            {
                bvalue = false;
            }

            return bvalue;
        }

        // 캐쉬 저장 : 절대 시간 만료 캐쉬 - minute 후에 만료
        public static bool SetCache(string name, object value, int minute)
        {
            bool bvalue = true;

            try
            {
                AppCacheObject.Cache.Add(name, value, null, DateTime.Now.AddMinutes(minute), System.Web.Caching.Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
            }
            catch
            {
                bvalue = false;
            }

            return bvalue;
        }

        // 캐쉬 get
        public static Object GetCache(string name)
        {
            Object ovalue = new Object();

            try
            {
                ovalue = AppCacheObject.Cache.Get(name);
            }
            catch { }

            return ovalue;
        }

        // 캐쉬 제거
        public static Object RemoveCache(string name)
        {
            Object ovalue = new Object();

            try
            {
                ovalue = AppCacheObject.Cache.Remove(name);
            }
            catch { }

            return ovalue;
        }

    } 

 

 

 

 

이를 이용하여, AppCacheExample 을 실행해보면,
로그인정보를 저장하는 Sliding Cache 와 일반 정보를 저장하는 Absolute Cache 를 사용하고 있다.
각각 캐쉬 만료시간을 1분으로 하여,
GetCache 를 했을때, 만료되는지와 자동연장되는지를 확인할 수 있다.

 

 

 

 

AppCacheExample.zip
다운로드

 

Posted by 헝개