Asp.Net Identity 2.0 커스텀 로그인 (AspNetUsers의 PasswordHash 비교 루틴)

 

 Asp.Net MVC5에서는 Identity 클래스를 제공, 사용해서 사용자 인증을 처리합니다.

이렇게 하면 DB에 AspNetUsers 테이블에 사용자 정보가 저장되고, 회원등록 및 로긴은 Entity framework를 통해서 자동으로 처리됩니다.

이 때 OWIN이나 Aspnet Identity, EF6을 사용하지 않는 별도의 프로젝트에서 해당 테이블에 접근해 로그인만 체크해 주고 싶을 수 있습니다.


AspNet에서는 Microsoft.AspNet.Identity.PasswordHasher 클래스에서 해당 비밀번호 검증을 담당합니다.

이 부분은 https://aspnetidentity.codeplex.com/SourceControl/latest#src/Microsoft.AspNet.Identity.Core/Crypto.cs 를 참고하시면 됩니다.


간단하게 DB에 직접 붙어서 Aspnet Identity로 생성된 계정에 로그인하는 코드입니다.

핵심은 아무래도 VerifyHashedPassword 메소드가 되겠네요. SqlConn은 제가 임의로 만들어 사용하는 SqlClient 헬퍼클래스입니다.


using System;
using System.Security.Cryptography;
using System.Data;
 
namespace IdentityTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Retry:
            Console.Write(" - ID : ");
            string id = Console.ReadLine();
            Console.Write(" - PW : ");
            string pw = Console.ReadLine();
 
            bool logined = false;
 
            using (SqlConn sql = new SqlConn())
            {
                sql.ConnStr = "Server=0.0.0.0;Database=xx;User ID=xx;Password=xx;Connection Timeout=10;";
 
                DataTable result = sql.SqlSelect($"select top 1 * from AspNetUsers where UserName = '{id}'");
                if (result.Rows.Count > 0)
                {
                     logined = VerifyHashedPassword(result.Rows[0]["PasswordHash"].ToString(), pw);
                }
            }
 
            Console.WriteLine("Login 결과 : " + (logined ? "성공":"실패"));
            Console.WriteLine("------------------- Retry -------------------");
            goto Retry;
        }
 
        public static string HashPassword(string password)
        {
            byte[] salt;
            byte[] buffer2;
            if (password == null)
            {
                throw new ArgumentNullException("password");
            }
            using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(password, 0x10, 0x3e8))
            {
                salt = bytes.Salt;
                buffer2 = bytes.GetBytes(0x20);
            }
            byte[] dst = new byte[0x31];
            Buffer.BlockCopy(salt, 0, dst, 1, 0x10);
            Buffer.BlockCopy(buffer2, 0, dst, 0x11, 0x20);
            return Convert.ToBase64String(dst);
        }
 
        public static bool VerifyHashedPassword(string hashedPassword, string password)
        {
            byte[] buffer4;
            if (hashedPassword == null)
            {
                return false;
            }
            if (password == null)
            {
                throw new ArgumentNullException("password");
            }
            byte[] src = Convert.FromBase64String(hashedPassword);
            if ((src.Length != 0x31) || (src[0] != 0))
            {
                return false;
            }
            byte[] dst = new byte[0x10];
            Buffer.BlockCopy(src, 1, dst, 0, 0x10);
            byte[] buffer3 = new byte[0x20];
            Buffer.BlockCopy(src, 0x11, buffer3, 0, 0x20);
            using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(password, dst, 0x3e8))
            {
                buffer4 = bytes.GetBytes(0x20);
            }
            return ByteArraysEqual(buffer3, buffer4);
        }
 
        private static bool ByteArraysEqual(byte[] a, byte[] b)
        {
            if (== null && b == null)
            {
                return true;
            }
            if (== null || b == null || a.Length != b.Length)
            {
                return false;
            }
            var areSame = true;
            for (var i = 0 ; i < a.Length ; i++)
            {
                areSame &= (a[i] == b[i]);
            }
            return areSame;
        }
 
 
    }
}


실행 결과는 아래와 같습니다.



스플래시 화면은 응용 프로그램이 초기화되는 동안 응용 프로그램을 처음로드 할 때 표시되는 이미지 또는 페이지입니다. 
스플래시 화면은 앱의 첫 페이지가 표시 될 때까지 계속 표시됩니다. 
Xamarin Forms에는 스플래시 화면을 추가 할 수있는 기능이 없으므로 플랫폼별로 이 작업을 수행해야합니다.

UWP

UWP는 이미지를 사용하여 스플래시 화면을 표시합니다. 다음 이미지로 이동하여 이 이미지를 배치 할 수 있습니다.

Project > package.appxmanifest > Visual Assets> Splashscreen

WinRT 응용 프로그램과 UWP 응용 프로그램 사이에는 스플래시 화면과 큰 차이가 있습니다. WinRT는 전체 세로 이미지를 스플래시 화면으로 배치했으나 UWP는 가로 이미지를 허용하지만 전체 화면을 차지하지 않습니다. 대부분의 앱이 세로형으로 시작하기 때문에 이미지를 전체 화면으로 가져올 수 없습니다.

UWP는 일반 배경색을 깔고 그 위 화면 중앙에 로고를 표시합니다. 이 작업을 수행하려면 가운데에 로고가 지정된 크기로 이미지를 만들고 선택한 배경색을 단색으로 만듭니다. 그런 다음 화면의 배경색 옵션을 아래 예와 동일한 단색 배경색으로 설정하십시오.

UWP 시작 화면 설정

그림과 같이 스플래시 화면이 나타납니다.

tesla_splashscreen

UWP에서 이미지로 전체 화면을 채우는 완전히 사용자 정의 가능한 스플래시 화면을 만들려면 확장 스플래시 화면 만들기 글을 참고하세요.



Android

Android의 시작 화면은 새로운 액티비티를 만들어야 하므로 더 복잡합니다. Xamarin.Android를 처음 사용하는 경우 Activity는 사용자가 정상적으로 상호 작용할 수있는 단일 UI입니다. Xamarin Forms는 MainActivity를 사용하여 애플리케이션을 로드하고, 다른 Activity를 생성합니다. 이 예제는 여러분이 AppCompat 를 사용하고 있다고 가정합니다. 이것은 새로운 모든 안드로이드 어플리케이션에 권장됩니다.

  1. 적절한 풀다운 폴더에 전체 화면 이미지 (예 : splashscreen.png)를 배치하십시오. Drawable 폴더는 다음 크기와 연결됩니다.

    • MDPI는 320 x 480 dp = 320x480px (기본값 x1)입니다.
    • LDPI는 0.75 x MDPI = 240x360 픽셀입니다.
    • HDPI는 1.5 x MDPI = 480x720px입니다.
    • XHDPI는 2 x MDPI = 640x960 픽셀입니다.
    • XXHDPI는 3 x MDPI = 960x1440 픽셀입니다.
    • XXXHDPI는 4 x MDPI = 1280x1920 픽셀입니다.
  2. 이 새 이미지를 참조하는 새 스타일을 만듭니다. 이것을 Resources> values> styles.xml (Build Action : Android Resource)에 위치 시키 십시오.
    <?xml version="1.0" encoding="utf-8" ?>
    <resources>
      <style name="splashscreen" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="android:windowBackground">@drawable/splashscreen</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowIsTranslucent">false</item>
        <item name="android:windowIsFloating">false</item>
        <item name="android:backgroundDimEnabled">true</item>
      </style>
  3. 새 클래스 만들기 SplashActivity.cs

    Public class SplashActivity : AppCompatActivity { protected override void OnResume () { ActivLayout = true, NoHistory = true) public class SplashActivity : AppCompatActivity { { base.OnResume (); StartActivity (typeof (MainActivity)); } }

  4. MainActivity.cs 를 열고 MainLauncher = true 를 MainLauncher = false 로 변경하십시오.

더 나은 접근법

다른 방법을 통해보다 빠른 경험과로드 시간을 제공 할 수 있습니다.

  1. Resources> Values> styles.xml에서 스플래시 스크린 테마를 만듭니다.
    <?xml version="1.0" encoding="utf-8" ?>
    <resources>
      <style name="splashscreen" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="android:windowBackground">@drawable/splashscreen</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowIsTranslucent">false</item>
        <item name="android:windowIsFloating">false</item>
        <item name="android:backgroundDimEnabled">true</item>
      </style>
  2. MainActivity.cs로 가서 MainLauncher = true 인지 확인 하십시오 . MainLauncher = true 인 다른 Activity가 없는지 확인하십시오.
  3. MainActivity.cs에서 기본 테마를 스플래시 화면으로 변경하십시오.
    [Activity(Label = "Mobile App", Theme = "@style/splashscreen", Icon = "@drawable/icon", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation, LaunchMode = LaunchMode.SingleTop)]
     public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity
  4. OnCreate에서 아래 코드로 테마를 전환하십시오.
    protected override void OnCreate(Bundle bundle)
    {
        base.Window.RequestFeature(WindowFeatures.ActionBar);
        // Name of the MainActivity theme you had there before.
        // Or you can use global::Android.Resource.Style.ThemeHoloLight
        base.SetTheme(Resource.Style.MainTheme);
    
        base.OnCreate(bundle);
    
        ...

9- 패치 이미지

밀도와 해상도를 변경하면 Android 용 스플래시 화면 이미지에 문제가 발생합니다. Xamarin Android 9-Patch Image Splashscreen 을 보시고 Android Splash 스크린 용 9 패치 이미지 를 만드십시오.

iOS

iOS는 응용 프로그램이 실행되고있는 화면 크기에 따라 이미지를 사용하여 스플래시 화면을 표시합니다.

  1. 다음과 같은 크기의 스플래시 화면 이미지를 만듭니다.
    1. 320 × 480
    2. 640 × 960
    3. 640 × 1136
  2. Properties> iOS Application> iPhone Launch Images로 이동하여 이미지를 가져 오십시오. iOS 프로젝트  Resources 폴더에 자동으로 추가됩니다 .
    ios_splashscreen_settings
  3. 속성 페이지를 아래로 이동하여 iPad 앱용 이미지를 추가하십시오.
  4. 이미지가 계속 표시되는 데 문제가있는 경우 가능한 원인이 몇 가지 있습니다. LaunchScreen.storyboard가 문제 일 수 있습니다. 열어서 수정하거나 심지어 삭제할 수도 있습니다. 이 작업을 완료하면 애플리케이션을 재구성하고 iOS 시뮬레이터를 재설정하거나 iOS가이 영역에 광범위하게 캐시되는 경향이 있으므로 iPhone에서 삭제하십시오.


원문 : https://xamarinhelp.com/creating-splash-screen-xamarin-forms/

 

 

 

[.NET] 파일 이름 변경, 복사할 때 동일한 파일이 있으면 자동으로 넘버링 해 주기.

 

 

윈도우 탐색기에서 파일 복사하기. 많이 쓰이는 기능입니다.

윈도우 탐색기 파일 복사의 특징 중 하나는 동일한 파일을 동일한 위치에 복사&붙여넣기를 계속 시도하면

 

"파일명 - 복사본"

"파일명 - 복사본 - 복사본"

"파일명 - 복사본 - 복사본 - 복사본"

 

이와 같이 자동으로 " - 복사본" 이라는 명칭을 덧붙여 새로운 사본을 생성시킨다는 것을 알고 계시죠.

 

폴더의 경우, '새 폴더' - '새 폴더(1)' - '새 폴더(2)' - '새 폴더(3)'  과 같이 순차적으로 번호매김을 해 줍니다.

 

이것과 유사한 기능을 하는 함수입니다.

닷넷에서 임의로 디스크의 파일을 복사하거나 이동하거나 이름바꾸기를 할 때 해당 파일의 존재 여부를 확인하고 자동으로 적절한 넘버링을 붙여주는 함수입니다.

 

 

VB.NET 코드 :

 

    Public Shared Sub RenameFileExt(srcFileNM As String, ByRef tgtFileNM As String)
        Dim count As Integer = 1
        Dim FileNameOnly As String = System.IO.Path.GetFileNameWithoutExtension(tgtFileNM)
        Dim Extension As String = System.IO.Path.GetExtension(tgtFileNM)
        Dim path As String = System.IO.Path.GetDirectoryName(tgtFileNM)
        Dim newFullPath As String = tgtFileNM

        While File.Exists(newFullPath)
            count += 1
            Dim tmpFileNM As String = String.Format("{0} ({1})", FileNameOnly, count)
            newFullPath = System.IO.Path.Combine(path, tmpFileNM + Extension)
        End While

        Try
            File.Move(srcFileNM, newFullPath)
            tgtFileNM = newFullPath
        Catch ex As Exception
        End Try

    End Sub

 

 

C#.NET 코드 :

 

public static void RenameFileExt(string srcFileNM, ref string tgtFileNM)
{
	int count = 1;
	string FileNameOnly = System.IO.Path.GetFileNameWithoutExtension(tgtFileNM);
	string Extension = System.IO.Path.GetExtension(tgtFileNM);
	string path = System.IO.Path.GetDirectoryName(tgtFileNM);
	string newFullPath = tgtFileNM;

	while (File.Exists(newFullPath)) {
		string tmpFileNM = string.Format("{0} ({1})", FileNameOnly, count++);
		newFullPath = System.IO.Path.Combine(path, tmpFileNM + Extension);
	}

	try {
		File.Move(srcFileNM, newFullPath);
		tgtFileNM = newFullPath;
	} catch (Exception ex) {
	}

}

 

 

인수로 주어지는 타겟파일명을 ByRef로 한 이유는, 함수 내부에서 타겟파일명이 변경될 수 있기 때문입니다.

RenameFileExt("C:\111.txt", "C:\111.txt") 라고 넣는다면, 소스와 타겟이 동일하므로

C:\111 (1).txt 라는 파일로 타겟파일명을 바꿔주게 되겠죠. 이 최종 파일명 정보를 리턴하는 것입니다.

 

File.Copy 메서드로 바꿔서 사용하면  윈도우 탐색기의 복사 기능과 비슷한 로직으로 사본을 생성하게 됩니다.

저는 Rename을 위해 File.Move메서드를 사용했습니다.

 

많이 활용하시길...

 

 

 

TAB to XLSX.exe

 

 

[vb.NET] Tab 파일을 XLSX로 변환

 

 

TAB 형식의 파일을 Excel 2007 / 2010 / 2013용 XLSX 파일로 변환해 주는 간단한 유틸입니다.

필요에 의해서 만들어 보았습니다.

웬지 있을것 같아서 10여분 서치해 봤는데 딱 맘에들게 기능하는게 안찾아져서..그냥 후딱 만들었습니다.

파일들 또는 tab 파일이 들어있는 폴더채로 드래그하여  폼에 드롭하면 tab파일 옆에  xlsx로 일괄 변환이 됩니다..

아래 보시다시피 별다른 옵션이나 기능은 없습니다^^;

 

 

 

닷넷프레임워크2.0과  엑셀2007 이상이 설치되어 있으면 됩니다..

조금 변형하면 csv, txt 파일등도 같은방식으로 변환할 수 있을 것 같습니다.

 

 

 

 

Imports System.IO
Imports XL = Microsoft.Office.Interop.Excel

Public Class frmMain
    Private xApp As XL.Application
    Private xWB As XL.Workbook
    Private xWS As XL.Worksheet

    '******************************************************************************************************************************************************
    ' 파일드롭 처리
    '******************************************************************************************************************************************************
    Private Sub frmMain_DragDrop(sender As Object, e As System.Windows.Forms.DragEventArgs) Handles Me.DragDrop
        Dim FileNames As String() = e.Data.GetData(DataFormats.FileDrop, False)

        For Each fNames As String In FileNames

            Dim fInfo As DirectoryInfo = New DirectoryInfo(fNames)
            If fInfo.Exists Then
                Dim fList As FileInfo() = fInfo.GetFiles()

                For Each fItem As FileInfo In fList
                    If fItem.Extension = ".tab" Then
                        Try
                            tabtoxlsx(fItem.FullName)
                        Catch ex As Exception
                        End Try
                    End If
                Next
            Else

                Dim fin As FileInfo = New FileInfo(fNames)
                If fin.Extension = ".tab" Then
                    Try
                        tabtoxlsx(fNames)
                    Catch ex As Exception
                    End Try
                End If

            End If

        Next

        MessageBox.Show("파일 변환이 완료되었습니다.")
    End Sub

    Private Sub frmMain_DragOver(sender As Object, e As System.Windows.Forms.DragEventArgs) Handles Me.DragOver
        e.Effect = DragDropEffects.All
    End Sub

    Private Sub frmMain_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
        xApp = New XL.Application
        'xApp.Visible = True
        xApp.IgnoreRemoteRequests = True
        xApp.ScreenUpdating = False
        xApp.DisplayAlerts = False
    End Sub

    Private Sub frmMain_Disposed(sender As Object, e As System.EventArgs) Handles Me.Disposed
        Try : xWB.Close(False) : Catch ex As Exception : End Try
        Try
            xApp.ScreenUpdating = True
            xApp.DisplayAlerts = True
        Catch ex As Exception : End Try
        Try : xApp.IgnoreRemoteRequests = False : Catch ex As Exception : End Try
        Try : xApp.Quit() : Catch ex As Exception : End Try
    End Sub


    '******************************************************************************************************************************************************
    ' 탭파일을 열어 엑셀파일로 저장. 형식은 모두 텍스트형식으로 읽는다.
    '******************************************************************************************************************************************************
    Private Sub tabtoxlsx(tabFileNM As String)
        Dim ColArray(0 To 1000, 0 To 1)
        For x = 0 To 1000
            ColArray(x, 0) = x + 1
            ColArray(x, 1) = 2
        Next

        xApp.Workbooks.OpenText(tabFileNM, Origin:=949, StartRow:=1, DataType:=XL.XlTextParsingType.xlDelimited, Tab:=True, FieldInfo:=ColArray)
        xWB = xApp.Workbooks(1)
        xWS = xWB.Sheets(1)

        xWS.Cells.Font.Size = 10
        With xWS.Rows(2)
            '.Interior.Pattern = XL.Constants.xlSolid
            .Cells.Interior.Color = 14211289
            .Font.Bold = True
        End With
        xWS.Rows(1).Delete(XL.XlDirection.xlUp)

        xApp.ActiveWindow.SplitRow = 1
        xApp.ActiveWindow.FreezePanes = True

        xWB.SaveAs(tabFileNM.Replace(".tab", ".xlsx"), XL.XlFileFormat.xlWorkbookDefault)
        xWB.Close(False)

    End Sub

End Class

 

 

[.Net] Datatable을 Access에 Insert하는 방법. (Bulk Insert)

 

 

.NET과 Access를 가지고 놀던 중 대량의 엑셀 데이터를 엑세스DB로 업로드 하는 괜찮은 방법을 찾아서 혹시 필요하신 분이 계실까 하여 포스팅합니다.

 

 

vb.net 기준이고 엑셀 문서를 datatable로 한방에 읽어오는 방법은,,

닷넷에서 기본 지원하는 Oledb를 이용하면 간단하므로 생략하겠습니다.

DataAdapter를 만들어 Fill을 해도 되고

ExcuteReader로 읽어와도 되고..  여튼 엑셀을 datatable로 만드는것은 간단합니다.

 

여기서 From Source를 Datatable로 한 것은, 비단 엑셀 뿐 아니라 xml이나 txt, 기타 sql서버 등 여러 가지 다른 소스를 대상으로 하여도 동일한 사용성을 갖게 하기 위해서 입니다.

엑셀파일만 대상으로 엑세스로 덤프하는것은 http://cafe.naver.com/xlsvba/1101  이 방법이 더 간단합니다.

 

 

여튼, .NET의 알짜배기인 Dataset, DataTable을  이용합니다.

 

 

처음에는 그냥 단순하게 아래의 방법으로 인서트를 했습니다.

 

'***** 데이터를 업로드한다. 
For i = 0 To OrgDataTable.Rows.Count - 1 
    Application.DoEvents() 

    qq.Clear() 
    qq.AppendLine(" INSERT INTO [Data]") 
    qq.AppendLine(" ([이름], [주민등록번호])") 
    qq.AppendLine(" VALUES") 
    qq.AppendLine(" (") 
    qq.AppendLine("     '" + OrgDataTable.Rows(i).Item(0).ToString.Trim + "'") 
    qq.AppendLine("     ,'" + OrgDataTable.Rows(i).Item(1).ToString.Trim + "'") 
    qq.AppendLine(" )") 

    Try 
        dbCon.aceRS.Close() 
    Catch ex As Exception 
    End Try 
    dbCon.aceRS.Open(qq.ToString, dbCon.aceDB, 1) 
    'System.Threading.Thread.Sleep(20) 

    loFunctions.UpdateProgress(ProgressBar1, lbl_Progress, (i + 1), (i + 1).ToString + " / " + OrgDataTable.Rows.Count.ToString) 
Next

 

 

지극히 평범한 방식이죠. 오리지널을 순서대로 돌면서 한줄한줄 인서트 하는 방법.

 

10만여개의 이름,주민등록번호 데이터를 넣는데도 10분이 훨씬 넘는 시간이 걸리더군요.

 

 

그래서 다른 방법을 구글링해서 아래 소스를 발견하고 적용해 보았습니다. DataAdapter를 사용하는 방법이죠.

http://www.codeproject.com/Articles/17028/Bulk-Record-Insert-for-Access

벌크 인서트에 관해서는 대부분 저 링크가 걸려있더군요.

많은 샘플소스들이 이렇게 DataAdapter를 활용해서 인텔리전스한 Update메서드를 사용하는걸 추천했습니다.

그런데 저대로는 잘 안되었습니다. 왜인가 이유를 살펴보니, DataAdapter의 Update메서드는

지정된 DataTable을 훌륭하게 동기화 해주기는 하지만 그 이전에 DataTable의 각각의 Rows객체의 RowState를 보고

Insert인지, Update인지, Delete인지를 판단해서 해당 커맨드를 실행하니까요.

지금 하려는 것은 원본데이터를 몽땅 Insert하는 것이므로 DataTable의 모든 Rows의 rowstate는 RowAdded 값을 가져야만 합니다.

 

그래서 아래와 같이 해봤습니다.

 

Dim oConn As OleDbConnection = New OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + AppDomain.CurrentDomain.BaseDirectory + My.Settings.DBFileNM + ";") 
Dim SQL As String = "SELECT * FROM Data WHERE 0=1" 
Dim insSQL As String = "INSERT INTO Data ([이름], [주민등록번호]) VALUES (@이름, @주민등록번호)" 
Dim oAdpt As OleDbDataAdapter = New OleDbDataAdapter(SQL, oConn) 

For Each dRow As DataRow In OrgDataTable.Rows 
    dRow.AcceptChanges() 
    dRow.SetAdded() 
Next 

oAdpt.InsertCommand = New OleDbCommand(insSQL, oConn) 
oAdpt.InsertCommand.Parameters.Add("@이름", OleDbType.Char, 255, "이름") 
oAdpt.InsertCommand.Parameters.Add("@주민등록번호", OleDbType.Char, 255, "주민등록번호") 
oAdpt.Update(OrgDataTable) 


 

 

 

DataTable의 모든 Rows를 순회하면서 임의로 RowState값을 RowAdded로 설정했습니다. 그래야 몽땅 Insert 적용되지요.

그리고 실행해보니 업로드는 잘 됩니다.

그러나 웬걸,, 시간이 아까보다 더 많이 걸리네요.

하도 오래 걸리기에 어느부분에서 시간이 걸리나 봤더니 oAdp.Update(OrgDataTable) 에서  하염없이.. 못넘어갑니다.

Progress로 진행률도 알아낼 수가 없어서 몇분 기다리다 답답해서 강종 해버리고 엑세스파일을 열어보니 고작 3만개 들어갔네요.

이딴 성능을 가지고 뭘 벌크로 인서트 하라고 --;;;;

 

 

방법이 이것말곤 없나 찾다가 드디어 궁극의 비기를 발견합니다.

 

http://stackoverflow.com/questions/7070011/writing-large-number-of-records-bulk-insert-to-access-in-net-c

 

정말 현답자이십니다. 6가지 사례를 들어  밀리세컨까지  시뮬레이션 결과를 정리해 주셨더군요.

메모리에 DAO Recordset객체를 만들어내는 방법이 가장 좋다합니다.

같은 일을 하는데 그 방법을 어떻게 하느냐에 따라 2.8초 vs 86초? 

 

어쨋든 해 보았습니다. 맨 아래에 친절하게도 클래스급으로 만들어진 소스가 있어서 그대로 따다가..

vb.net 코드로 컨버팅 하고.. 참조에 Microsoft Office Data Access Object 14.0 걸어주고.. (Dao3.6은 mdb까지밖에 안되므로)

 

Class uploadAccess 
    Public Sub BulkExportToAccess(dtOutData As DataTable, DBPath As [String], TableNm As [String]) 
        Dim dbEngine As New DAO.DBEngine() 
        Dim CheckFl As [Boolean] = False 

        Try 
            Dim db As DAO.Database = dbEngine.OpenDatabase(DBPath) 
            Dim AccesssRecordset As DAO.Recordset = db.OpenRecordset(TableNm) 
            Dim AccesssFields As DAO.Field() = New DAO.Field(dtOutData.Columns.Count - 1) {} 

            'Loop on each row of dtOutData 
            For rowCounter As Int32 = 0 To dtOutData.Rows.Count - 1 
                AccesssRecordset.AddNew() 
                'Loop on column 
                For colCounter As Int32 = 0 To dtOutData.Columns.Count - 1 
                    ' for the first time... setup the field name. 
                    If Not CheckFl Then 
                        AccesssFields(colCounter) = AccesssRecordset.Fields(dtOutData.Columns(colCounter).ColumnName) 
                    End If 
                    AccesssFields(colCounter).Value = dtOutData.Rows(rowCounter)(colCounter) 
                Next 

                AccesssRecordset.Update() 
                CheckFl = True 
            Next 

            AccesssRecordset.Close() 
            db.Close() 
        Finally 
            System.Runtime.InteropServices.Marshal.ReleaseComObject(dbEngine) 
            dbEngine = Nothing 
        End Try 
    End Sub 

End Class 

 

 

그리고 실행코드..

빨리빨리..빨리 해서 결과를 보고싶엉ㅇㅇ +_+ 대충 빨리..

 

Dim Acce As uploadAccess = New uploadAccess 
Acce.BulkExportToAccess(OrgDataTable, AppDomain.CurrentDomain.BaseDirectory + My.Settings.DBFileNM, "Data") 

 

 

매우매우 잘 됩니다. 10만5천건의 데이터가 눈깜짝할새에 쏵~ 들어가네요.

12만row 몇컬럼인진 모르겠으나 4초걸렸다고 했는데  제가 테스트한 10만5천row 2컬럼짜리는 0.5초만에 들어가네요.

 

전 개인적으로 어떤 DataTable을 다룰때 행,열 For문 돌려서 뭘 하는게 굉장히 맘에 안들었습니다.

SQL에서 레코드셋으로 받아온 결과를  엑셀에 뿌릴때에도..  컬럼, 로우 중첩for문 돌면서 처리하는거 절대 안썼고

쿼리문을 변경해서라도  copyfromrecordset 을 써서  한방에 뿌리는걸 선호했죠.

수십수백만개가 될지도 모르는걸  For문으로 처리해 버릇하면  후에 감당이 안될것 같아서요.

그래서 닷넷으로 와서도 copyfromrecordset이 지원되지 않는 닷넷의 ado 기본공급자들 안쓰고

따로 ado 6.0을 참조걸어서 쓸 정도였죠.

 

 

그런데 지금의 결과를 보니 for도 잘 쓰면 베스트가 될 수도 있구나 하는걸 깨달았습니다.

 

아마 이 방법은 db서버에 있는 데이터를 엑셀로 내려받기 해 줄 때에도 유용하게 쓸수 있을것 같네요.

어떤 데이터 원본이던지 DataTable로 만들어낼 수 있고,  DAO로 컨트롤할 수 있는 대상이면 적용 될 테니까요..

 

두서없이 제가 쓰던 코드를 고대로 갖다넣어버림으로써  샘플소스로 쓰기에는 부적절한 (lofunctions등 개별적으로 쓰는 클래스) 코드가 된 점 양해 부탁드립니다. 링크 원본을 가시면  다 있습니다^^;;;

 

 

 

 

[.net] ADO (6.0) + ODBC드라이버를 이용한 DB Connection (MS-SQL, Excel, Access)

 

 

Microsoft ActiveX Data Objects 6.0 Library (adodb)와 ODBC 를 이용해서 각 DB에 Connection하는 클래스

 

 

Public Class DBConn
    Public myDB As New ADODB.Connection
    Public myRS As New ADODB.Recordset
    Public qq As StringBuilder

    Public Function DBOpen() As Boolean
        Dim Opened As Boolean
        Try
            myDB.Open("Provider=SQLOLEDB.1;Persist Security Info=False;Data Source=111.111.111.111,1111;Initial Catalog=DBNAME;User ID=DBNAME;Password=DBPASS;")

            If myDB.State Then
                Opened = True
            Else
                Opened = False
            End If
        Catch ex As Exception
            Opened = False
        End Try
        myRS.CursorType = ADODB.CursorTypeEnum.adOpenStatic
        myRS.CursorLocation = ADODB.CursorLocationEnum.adUseClient
        myRS.LockType = ADODB.LockTypeEnum.adLockOptimistic

        DBOpen = Opened
    End Function


    Public Sub DBClose()
        Try
            myDB.Close()
            loFunctions.releaseObject(myDB)
        Catch ex As Exception
        End Try
    End Sub

End Class




Public Class xlDBConn
    Public xlDB As New ADODB.Connection
    Public xlRS As New ADODB.Recordset
    Public qq As StringBuilder

    Public Function xlDBOpen(xlFileNM As String) As Boolean
        Dim Opened As Boolean

        Try
            With xlDB
                .ConnectionString = "Dsn=Excel Files;dbq=" + xlFileNM + ";driverid=1046;fil=excel 12.0;maxbuffersize=2048;pagetimeout=5"
                .Open()
            End With

            If xlDB.State Then
                Opened = True
            Else
                Opened = False
            End If
        Catch ex As Exception
            Opened = False
        End Try

        xlDBOpen = Opened
    End Function


    Public Sub xlDBClose()
        Try
            xlDB.Close()
            loFunctions.releaseObject(xlDB)
        Catch ex As Exception
        End Try
    End Sub

End Class



Public Class aceDBConn
    Public aceDB As New ADODB.Connection
    Public aceRS As New ADODB.Recordset
    Public qq As StringBuilder

    Public Function aceDBOpen(aceFileNM As String) As Boolean
        Dim Opened As Boolean

        Try
            With aceDB
                .ConnectionString = "Dsn=MS Access Database;dbq=" + aceFileNM.Replace("\\", "\") + ";driverid=25;fil=MS Access;maxbuffersize=2048;pagetimeout=5;uid=admin"
                .Open()
            End With

            If aceDB.State Then
                Opened = True
            Else
                Opened = False
            End If
        Catch ex As Exception
            Opened = False
        End Try

        aceDBOpen = Opened
    End Function


    Public Sub aceDBClose()
        Try
            aceDB.Close()
            loFunctions.releaseObject(aceDB)
        Catch ex As Exception
        End Try
    End Sub


End Class

 

[.NET] 디렉토리의 파일 목록 조사 + 필터링. Linq와 람다식을 이용한 Directory.GetFiles 확장자 지정하기.

 

 

사용자에게 폴더를 선택하게끔 하고, 해당 폴더 안에 들어있는 파일을 리스트업 해줘야 할 때가 있다.

.NET에서는  System.IO.Directory.GetFiles 를 이용해서 디렉토리 내 파일 목록을 얻어올 수 있다.

사용방법은 잘들 아시겠지만 다음과 같다.

 

Dim DataFileNM As String() = Directory.GetFiles(TargetFolderNM, "*.*", SearchOption.TopDirectoryOnly)

 

여기서 보듯이 GetFiles메서드에는 패턴검색 옵션을 지정할 수가 있게 되어있는데(searchpattern)

이 패턴 옵션이 뭔가 어정쩡하다.

 

예를들어, 폴더 내의 이미지파일을 모두 보여주고 싶을 때에는 파일확장명에 *.gif, *.tif, *.jpg, *.png, *.bmp 가 지정되어야 할 것이다.

하지만 익숙한 확장자 나열 기법을 적용해서

Directory.GetFiles(TargetFolderNM, "*.gif|*.jpg|*.png", SearchOption.TopDirectoryOnly) 처럼 사용하면 에러가 발생한다.

searchpattern에는 와일드카드를 사용할 수 있지만, 조건식을 나열할 수는 없는 반쪽짜리인 것이다..-_-

꼭 단일 확장자로만 지정을 해줘야 한단다.

 

그래서 그럼 이렇게 해보았다. Concat메서드로  리턴된 배열을 합쳐버리는 방법이다.

 

DataFileNM = Directory.GetFiles(TargetFolderNM, "*.jpg", SearchOption.TopDirectoryOnly)
DataFileNM = DataFileNM.Concat(Directory.GetFiles(TargetFolderNM, "*.tif", SearchOption.TopDirectoryOnly).ToArray)

 

하지만 이 방법 역시 경우에 따라서 오류가 발생한다.

Concat 메서드는 연결할 첫 번째 시퀀스가 nothing일 경우 ArgumentNullException 에러를 발생시킨다.

즉, 첫번째줄에서 *.jpg파일이 하나도 발견되지 않았다면  *.tif 에서 반환된 파일목록 배열은 붙을곳을 찾지 못해 오류가 나는 것이다..

모든 폴더에 jpg파일이 최소한 한개씩은 있어준다는 보장이 없으니 이 역시 사용불가이다. 물론 이것도 조건분기를 처리해서 DataFileNM이 Nothing일 경우엔 그냥 할당하고  아닐경우에만 Concat을 하는 식으로 코딩할 수는 있겠으나 바람직한 코딩이 아니다.

 

 

사설이 길었는데, 이러한 연유로 아래의 방식을 찾았다.  별도의 함수를 만들지 않아도 되고 코드 길이도 짧다.

실로 베스트한 방식이 아닐수 없다. 후후후 -_ㅡv

여기엔 Linq와 람다식이 사용되었다.

 

[VB.net]

'***** 지정된 폴더에서 특정 이미지 파일만 찾아온다.
Dim DataFileNM As String()
Dim exts As String() = {".tif", ".jpg", ".gif"}

 

DataFileNM = Directory.GetFiles(TargetFolderNM, "*.*", SearchOption.TopDirectoryOnly).Where(Function(s) exts.Contains(Path.GetExtension(s), StringComparer.OrdinalIgnoreCase)).ToArray 

 

[C#.net]

string[] exts = new [] {".tif", ".jpg", ".gif"};

string[] DataFileNM =  Directory.GetFiles(TargetFolderNM, "*.*", SearchOption.TopDirectoryOnly).Where(s=>exts.Contains(Path.GetExtension(s), StringComparer.OrdinalIgnoreCase)).ToArray;

 

 

일단  exts에 필터링 할 확장자를 배열로 지정해 놓고

Directory.GetFiles(TargetFolderNM, "*.*", SearchOption.TopDirectoryOnly) 로 폴더내 모든 파일을 가져온다(String배열)

여기서 .Where(Function(s) exts.Contains(Path.GetExtension(s), StringComparer.OrdinalIgnoreCase)) 으로  확장자를 필터링해서 .ToArray로 다시 변환해서 리턴하는 것이다.

 

그러면 DataFileNM 스트링배열 개체에  해당 파일명이 아름답게 담겨지게 된다!

 

 

.NET Framework 4.0 을 기반으로 VSTO 애플리케이션을 작성하는데,

참조로 다른 DLL을 가져다 쓰고 빌드를 하려니 이런 오류가 발생한다.

"혼합 모드 어셈블리는 런타임의 버전 'v2.0.50727'에 대해 빌드되며 추가 구성 정보 없이 '4.0' 런타임에 로드할 수 없습니다."

 

그럼 하위버전의 DLL을 같이 쓸수 없다는말인가?!  하위호환성을 중요시하는 MS의 닷넷인데?!

찾아보니 간단한 해결방법이 있었다.

App.config 파일 (응용프로그램 설정을 사용하게 되면 자동으로 생김) 에 몇줄 추가해 주는 방법이 그것이다.

일단 App.config파일이 없다면  [프로젝트]-[추가]-[구성 요소] 에서 "응용 프로그램 구성 파일"을 추가해주면 된다.

여기에 아래의 코드를 적어넣고 저장후 다시 빌드를 하면 오류가 발생하지 않는단다.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup useLegacyV2RuntimeActivationPolicy="true">
    <supportedRuntime version="v4.0"/>
  </startup>
</configuration>

 

그런데,,,,,,,,,,

app.config를 만들어 놓고  아무리 빌드를 다시 해보아도 동일한 에러가 발생한다. 해결이 안된다. OTL

이유인즉슨, 일반 윈폼프로그램이 아닌 VSTO( Visual Studio Tools for Office ) 애플리케이션이기 때문이다.

이 app.config는 실행파일을 기준으로 동작을 하는 모양이다.

VSTO 로 개발된 프로그램은 (나의 경우는 Excel 문서수준 사용자지정 애플리케이션) 당연히 OFFICE를 기반으로 실행된다.

그래서 또 열심히 구글링을 한 결과 Office의 실제 실행파일 옆에 app.config를 만들어야 한다는 사실을 알게 되었다.

오피스2010버전 엑셀의 경우

C:\Program Files (x86)\Microsoft Office\Office14\EXCEL.EXE 

이게 실행파일이다.

그래서 동일한 폴더에 동일한 파일명으로 .config를 생성했다.

C:\Program Files (x86)\Microsoft Office\Office14\EXCEL.EXE.config

물론 파일의 내용은 위의 코드와 동일하게 해서..

 

이제 빌드를 해보니 혼합 어셈블리 빌드가 성공적으로 잘 된다.

 

 

 

 

[.NET] 시스템 환경변수 Path에 특정 경로 추가하기.

 

프로그램을 만들어 배포를 하게되면 경우에 따라 System 환경 변수의 Path를 건드려 주어야 할 때가 있다.

 

 

헌데 없어보이게  사용자에게 직접 환경변수를 추가하라고 할 수는 없는 노릇이고

제일 무난하고 편한 Install Factory로 해도, Install Shield 로 해도 방법이 없는것은 아니지만 해본결과 별루이다.

레지스트리를 직접 변경해주는 방식으로 하게 되는데, 이때 기존의 Path내용을 유지하면서 새로운것만 추가하기가 간단치 않다.

 

일단 '사용자 환경변수' 레지스트리의 위치는

 HKEY_CURRENT_USER\Environment\  이다.

사용자변수는 간단하다.

문제는 시스템 환경변수이다. 시스템환경변수는 최소 두군데 이상 동시에 존재한다.

일단 기본위치는

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment

이곳이지만, 사용자계정에따라 ControlSet001, ControlSet002 ... 등등으로 나뉘어진다.

HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Session Manager\Environment

어드메 있는것을 바꿔야 하는지까지는 잘 모르겠다. 이미 이렇게 널부러져 있는것을 확인한순간 다른방법을 고민했으니..

 

 

.NET에는 System.Environment 라는 훌륭한 클래스가 기본제공되질 않는가!

그래서 방법은 프로그램 실행시 시스템 환경변수값을 불러다가  내가 추가하고자 하는 경로가 이미 존재하면 제껴버리고,

없으면  맨앞에 경로를 하나 추가해서 다시 셋팅해주는 것이다.

그게 ControlSet001이 수정되야 하는지 002가 수정되야 하는지, 아니면 CurrentControlSet 에서 수정되야 하는지 알필요 따윈 없다.

어차피 이 프로그램이 System.Environment 가  찾아내준 위치의 시스템환경변수값만 조작해주면 DLL을 불러들이는데는 지장이 없으니까.

 

그래서 작성한 코드.

 

    Public Shared Sub SystemPathControl(AppPath As String)

        Dim _sysPath As String = System.Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine)
        Dim oldPath() As String = _sysPath.Split(";")
        Dim newPath As New StringBuilder

        If Not oldPath.Contains(AppPath) Then
            newPath.Append(AppPath + ";")
            For Each ePath As String In oldPath
                newPath.Append(ePath + ";")
            Next
            System.Environment.SetEnvironmentVariable("Path", newPath.ToString, EnvironmentVariableTarget.Machine)
        End If

    End Sub

 

 

사용된 클래스는 보시다시피..

System.Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine)

System.Environment.SetEnvironmentVariable("Path", newPath.ToString, EnvironmentVariableTarget.Machine)

이 되겠다.

Get으로 Machine(시스템) 환경변수 중 Path를 가져와서 매개변수로 들어온 AppPath값이 있는지 찾아보구

없으면 젤앞에 AppPath를 붙이고 나머지껄 줄줄이 이어붙여서 newPath를 만들어준 다음

Set으로 다시 Machine의 Path값을 바꿔주는 초간단 함수가 되겠다.

Get/Set EnvironmentVariable 메서드의 마지막 매개변수는 사용자/시스템 변수값에 대한 상수이다.

시스템변수는 EnvironmentVariableTarget.Machine

사용자변수는 EnvironmentVariableTarget.User

 

 

이제 이것을 프로그램 시작할때 도는 프로시져 한귀퉁이에서 호출해주면 되시겠다.

사실 이 행위를 플그램 실행때마다 매번 하는게 솔직히 비효율적일 수가 있다.

어디서 지나가다 주워본것 같은데, 닷넷은 배포후 최초 실행되었는지를 리턴해주는 함수가 있다고 봤다(다시 찾아보려니 찾을수가 없지만 ㅜㅜ)

이걸 조합한다면  프로그램 인스톨 후 최초 실행시에만 한번 돌려주도록 만들 수도 있을것이다.

 

최초실행여부를 확인해 주는 함수를 아시는분은 댓글로 제보좀 부탁드립니다 ㅋㅋ 

+ Recent posts