블로그 이미지
* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/ kaki104

카테고리

List All (574)N
Xamarin Forms (4)
Bot Framework (19)
Azure (9)
Windows 10 (47)N
WPF (3)
Facebook News & Tips (158)
Windows App(Universa.. (92)
Windows 8&8.1 (113)
Windows Phone 8 (42)
Silverlight (37)
HTML5 & MVC4 (16)
Portable Class Library (2)
Uncategorised Tips a.. (3)
Kinect for Windows (2)
ETC (12)
kaki104 Scrap (4)
App News (11)
Total531,489
Today22
Yesterday76

생각보다 시간도 오래걸렸고, 내용도 많아서, 2회에 걸쳐서 진행 하려고 한다. 일단 이번회에는 Model, ViewModel, View 구성에 관한 내용이다. 일단 완성된 화면을 보자..그래야..앞으로 갈길이 멀구나~라는 느낌이 온다.

어떻게 시작을 관리자 페이지에서 사용할 넘을 먼저 만들어서..화면 구성이 머 그냥 그렇다. 역시나 디자이너가 필요해..쿨럭

1. 클라이언트의 전체 적인 레이아웃
1-1. MEF(Manage Extensibility Framework)에 대해 먼저 알아 보고 넘어가야한다. 그동안 다른 강좌에서 MEF 사용에 대해서 이야기를 좀 했었다..음..물론 대충 어떻게 쓰는 거다 정도였지만..그래서 이번에는 좀더 자세하게 한번 다루고 넘어가려고 한다. 왜냐하면 이번 클라이언트는 MEF를 이용한 Export/Import가 다수 존재 하기 때문에 개념이 쉽게 잡히지 않을 수 있기 때문이다.

참고페이지(이 페이지는 꼭 읽어 볼 것을 권한다.)
http://www.c-sharpcorner.com/UploadFile/shivprasadk/3122/

우리가 만든 뷰, 뷰모델, 모델 등은 모두 Export 대상이 될 수 있고 그 Export한 내용들은 Composition Container라는 곳에 모여서 사용되어 지기를 기다라고 있다. 그럼 우린 그 중 필요한 내용들 만 골라서 Import라는 명령을 이용해서 건물을 만들수도, 차를 만들수도 있는 것이다. 그럼 이러한 MEF를 사용 했을때 좋은 점은 무엇인가..

1) 가장 큰 장점은 Decoupled하게 프로그램을 개발 할 수 있다.
Export 대상이 현재 프로젝트 어디 있던지, 아니면 현재 프로젝트가 아니라 웹에 이미 배포가 되어있는 것이라도..Import를 해서 끌어다가 사용 할 수 있으며, Export된 내용이 변경되더라도 Import 한 곳에는 아무런 영향을 미치지 않는다.

2) 하나의 인스턴스를 공유 한다.
하나의 모델이 A라는 aaa뷰에서 Import되고, bbb뷰모델에서 Import되고, ccc모델에서 Import되었을때 3곳에서 사용하는 A라는 Import된 모델은 동일한 내용이다.

3) 실행 중에도 내용이 변경될 수 있다.

등의 여러가지 장점들이 있다...그렇다면 이번에는 프로젝트에서 사용한 내용에 대해서 보도록 하자.


2. SL5_BOARD 프로젝의 전체 구성

2-1. 왜 이런 구성을 가지는가??
우선 BoardContext.cs는 서버와 통신을 통해서 데이터를 조회하고, 저장하는 SL5_BOARDDomainContext가 존재하며, 이 것을 통해서 가지고 온 데이터를 컨텍스트로서 로컬에서 관리를 할 수 있도록 하기위해서이다. 

모델 클래스는 BoardContext를 모두 Import를 받아서 하나의 로컬 저장소의 데이터를 모델 별로 관리를 할 수 있도록 만들어 놓은 것이고, 뷰모델은 뷰에서 사용할 ICommand와 비지니스 로직을 관리하며, 뷰는 사용자와의 인터렉션을 할 수 있도록 나누어 놓은 것이다..

최종적으로 모든 뷰는 ImportMany라는 명령을 통해서 MainPage.xaml에 Import되어 사용 되어 질 것이다.

이런 구성을 만들기 위해 나름 고민을 만이 했기 때문에..음음..아주 최고의 모델은 아니라도 마구 만들어진 모델은 아니니 나중에라도 이런 구성은 가지고 가서 사용해도 괜찮을 것이다. 참고로 이렇게 만들어 놓아야 진정한 Model-View-ViewModel이 되는 것이다..(처음부터 이렇게 만들면 개발 시간이 막코딩하는 것보다 시간이 2배 정도는 더 든다;;;..그러나 나중에 유지 보수하는 시간 까지 놓고 전체 적으로 본다면 이런 개발 패턴이 훨씬 유지보수하는데 효율적이다.)

2-2. SL5_BOARD 프로젝트에 리퍼런스 추가

Add Reference

Microsoft.Expression.Interactions 5.0.5.0
System.ComponentModel.Composition 5.0.5.0
System.ComponentModel.Composition.Initialization 5.0.5.0
System.Windows.Interactivity 5.0.5.0

3. Models

BoardContext.cs

//이 모델은 아~무것도 없다..그냥 통신하기 위한 컨텍스트만 하나 달랑있다~

using System.ComponentModel;
using System.ComponentModel.Composition;
using SL5_BOARD.Web.DomainService;

namespace SL5_BOARD.Models
{
    //Export한다~
    [Export(typeof(BoardContext))]
    public class BoardContext : INotifyPropertyChanged
    {
        SL5_BOARDDomainContext context;
        public SL5_BOARDDomainContext Context
        {
            get
            {
                return context;
            }

            private set
            {
                if (context != null)
                {
                    context = value;
                    OnPropertyChanged("Context");
                }
            }
        }

        #region PropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged(string PropertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
            }
        }
        #endregion

        public BoardContext()
        {
            context = new SL5_BOARDDomainContext();
        }
    }
}

BoardMain.cs

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.ServiceModel.DomainServices.Client;
using SL5_BOARD.Resources;
using SL5_BOARD.Web.Model;


namespace SL5_BOARD.Models
{
    //BoardMain 모델을 익스포트 시킨다.
    [Export(typeof(BoardMain))]
    public class BoardMain : INotifyPropertyChanged
    {
        //컨텍스트를 임포트 시킨다
        [Import(typeof(BoardContext))]
        public BoardContext BoardDomainService { get; set; }

        //너무 길어서 짧게 쓸려고 만들어 놓은것 - 뷰단에서 이걸 바인딩 한다.
        public EntitySet<BOARD_MAIN> CollectionData
        {
            get
            {
                return BoardDomainService.Context.BOARD_MAINs;
            }
        }

        //게시판 종류 목록 바인딩 하기 위해
        public ObservableCollection<CBoardType> BoardTypeCol { get; set; }

        //게시판 상태 바인딩용 - 이게 잘 앙대는 것 중에 하나인데;; 라디오버튼 앞으로 사용하지 말아야할듯 ㅋ
        //별것두 아닌데..바인딩으로 처리할려니 할일이 대폭 늘어난다;;
        string boardStateText;
        public string BoardStateText
        {
            get
            {
                return boardStateText;
            }
            set
            {
                if (boardStateText != value)
                {
                    boardStateText = value;

                    switch (boardStateText)
                    {
                        case "준비중":      //준비중
                            CurrentData.BOARD_STATE = "01";
                            break;
                        case "사용중":      //사용중
                            CurrentData.BOARD_STATE = "02";
                            break;
                        case "일시중지":    //일시중지
                            CurrentData.BOARD_STATE = "03";
                            break;
                        case "삭제":        //삭제
                            CurrentData.BOARD_STATE = "04";
                            break;
                    }
                    OnPropertyChanged("BoardStateText");
                }
            }
        }

        //현재 선택되어있는 보드메인
        BOARD_MAIN currentData;
        public BOARD_MAIN CurrentData
        {
            get
            {
                return currentData;
            }
            set
            {
                if (currentData != value)
                {
                    currentData = value;
                    OnPropertyChanged("CurrentData");
                }
            }
        }

        //각종 메시지 출력용
        string messageData;
        public string MessageData
        {
            get
            {
                return messageData;
            }
            private set
            {
                if (messageData != value)
                {
                    messageData = "[" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "] " + value;
                    OnPropertyChanged("MessageData");
                }
            }
        }

        //만들어 놓기는 했는데..아직 사용은 앙대고 있다. 불러온 카운트를 넣어서 사용할 예정
        int maxCount;
        public int MaxCount
        {
            get
            {
                return maxCount;
            }
            private set
            {
                if (maxCount != value)
                {
                    maxCount = value;
                    OnPropertyChanged("MaxCount");
                }
            }
        }

        #region PropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged(string PropertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
            }
        }
        #endregion

        //생성자
        public BoardMain()
        {
            //게시판 종류 설정
            BoardTypeCol = new ObservableCollection<CBoardType>
            {
                new CBoardType{BoardType = "NOR", BoardTypeComboText="일반게시판"},
                new CBoardType{BoardType = "PIC", BoardTypeComboText="사진게시판"},
                new CBoardType{BoardType = "ETC", BoardTypeComboText="기타게시판"}
            };
            this.PropertyChanged += new PropertyChangedEventHandler(BoardMain_PropertyChanged);
        }

        //자기 자신이 프로퍼티 체인지 이벤트 발생시 연속적인 다른 프로퍼티를 고치는 용도
       void BoardMain_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "CurrentData":
                    if (CurrentData != null)
                    {

                        //음..라디오 박스 때문에 신경썼는데..처음에만 대구 나중에는 잘 앙된다..원인은 불명
                        switch (CurrentData.BOARD_STATE)
                        {
                            case "01":      //준비중
                                BoardStateText = "준비중";
                                break;
                            case "02":      //사용중
                                BoardStateText = "사용중";
                                break;
                            case "03":      //일시중지
                                BoardStateText = "일시중지";
                                break;
                            case "04":      //삭제
                                BoardStateText = "삭제";
                                break;
                        }
                    }
                    else
                    {
                        BoardStateText = "";
                    }
                    break;
            }
        }

        //데이터 조회
        public void Getting()
        {
            if (BoardDomainService.Context.IsLoading == false)
            {
                BoardDomainService.Context.GetBoardMainQuery().IncludeTotalCount = true;
                BoardDomainService.Context.Load(BoardDomainService.Context.GetBoardMainQuery(), LoadBehavior.RefreshCurrent, true);
            }
            else
            {
                MessageData = "조회 중이니 잠시 후 다시 시도해 주시기 바랍니다.";
            }
        }

        //데이터 추가 - SubmitChange()전 까지는 로컬에만 존재한다.
        public void Adding(BOARD_MAIN bm)
        {
            if (bm != null && bm is BOARD_MAIN)
            {
                bm.REG_DT = DateTime.Now;
                bm.REG_IDX = 1;
                BoardDomainService.Context.BOARD_MAINs.Add(bm);
                CurrentData = bm;
            }
            else
            {
                MessageData = "데이터가 없거나 형식이 다릅니다.";
            }
        }

        //데이터 삭제 
        public void Removing(BOARD_MAIN bm)
        {
            if (bm != null && bm is BOARD_MAIN)
            {
                BoardDomainService.Context.BOARD_MAINs.Remove(bm);
            }
            else
            {
                MessageData = "데이터가 없거나 형식이 다릅니다.";
            }
        }

        //저장
        public void Saving()
        {

            //저장된 결과를 알기 위해 콜백 함수를 지정
            BoardDomainService.Context.SubmitChanges(submitCallBack, null);
        }


        //저장 콜백 함수
        private void submitCallBack(SubmitOperation so)
        {
            if (so.IsComplete == true)
            {
                MessageData = "작업을 완료 했습니다.";
            }
            else
            {
                MessageData = "작업을 실패 했습니다.";
            }
        }

        //작업 취소
        public void Canceling()
        {
            BoardDomainService.Context.RejectChanges();
        }

    }
}


4. ViewModel

BoardMainViewModel.cs

using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Windows.Input;
using Microsoft.Expression.Interactivity.Core;
using SL5_BOARD.Models;
using SL5_BOARD.Web.Model;

namespace SL5_BOARD.ViewModels
{
    //뷰모델 익스포트
    [Export(typeof(BoardMainViewModel))]
    public class BoardMainViewModel : INotifyPropertyChanged
    {
        #region PropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged(string PropertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
            }
        }
        #endregion

         //모델 인포트
        [Import(typeof(BoardMain))]
        public BoardMain BoardMainData { get; set; }

        public BoardMainViewModel()
        {
            //BoardMainData.BoardTypeCol
        }

        //조회커맨드
        ICommand selectCommand;
        public ICommand SelectCommand
        {
            get
            {
                if (selectCommand == null)
                {
                    selectCommand = new ActionCommand(() =>
                    {
                        BoardMainData.Getting();
                    });
                }
                return selectCommand;
            }
        }

        //조회 함수 - 맨처음에 조회를 하기 위해서..
        public void Select()
        {
            BoardMainData.Getting();
        }

        //추가 커맨드
        ICommand addCommand;
        public ICommand AddCommand
        {
            get
            {
                if (addCommand == null)
                {
                    addCommand = new ActionCommand(() =>
                    {
                        BOARD_MAIN bm = new BOARD_MAIN();
                        bm.BOARD_NAME = "신규 게시판의 명칭을 입력하세요";
                        bm.BOARD_DESC = "신규 게시판의 설명을 입력하세요";
                        BoardMainData.Adding(bm);
                    });
                }
                return addCommand;
            }
        }

        //삭제 커맨드
        ICommand removeCommand;
        public ICommand RemoveCommand
        {
            get
            {
                if (removeCommand == null)
                {
                    removeCommand = new ActionCommand(p =>
                    {
                        if (p != null)
                        {
                            BOARD_MAIN bm = p as BOARD_MAIN;
                            BoardMainData.Removing(bm);
                        }
                    });
                }
                return removeCommand;
            }
        }

        //저장 커맨드
        ICommand saveCommand;
        public ICommand SaveCommand
        {
            get
            {
                if (saveCommand == null)
                {
                    saveCommand = new ActionCommand(() =>
                    {
                        BoardMainData.Saving();
                    });
                }
                return saveCommand;
            }
        }

        //취소 커맨드
        ICommand cancelCommand;
        public ICommand CancelCommand
        {
            get
            {
                if (cancelCommand == null)
                {
                    cancelCommand = new ActionCommand(() =>
                    {
                        BoardMainData.Canceling();
                    });
                }
                return cancelCommand;
            }
        }
        
        //리스트 박스에서 선택항목 변경시
        ICommand selectChanged;
        public ICommand SelectChangedCommand
        {
            get
            {
                if (selectChanged == null)
                {
                    selectChanged = new ActionCommand(para => SelectChangedOperation(para));
                }
                return selectChanged;
            }
        }
        private void SelectChangedOperation(object para)
        {
            BoardMainData.CurrentData = para as BOARD_MAIN;
        }

        //라디오 버튼에서 클릭시 - 라디오 버튼;;;이렇게까지 했는대두 결국 잘 앙대구..짬뽕남;;
        ICommand changeStateCommand;
        public ICommand ChangeStateCommand
        {
            get
            {
                if (changeStateCommand == null)
                {
                    changeStateCommand = new ActionCommand(para => ChangeStateOperation(para));
                }
                return changeStateCommand;
            }
        }
        private void ChangeStateOperation(object para)
        {
            BoardMainData.BoardStateText = para as string;
        }
    }
}

5. View

BoardMainView.xaml

<UserControl
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:SL5_BOARD_ViewModels="clr-namespace:SL5_BOARD.ViewModels"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:SL5_BOARD_Converter="clr-namespace:SL5_BOARD.Converter"
    x:Name="userControl"
    x:Class="SL5_BOARD.Views.BoardMainView"
    mc:Ignorable="d"
    d:DesignHeight="500" d:DesignWidth="600">
   
 <UserControl.Resources>

   <!--바인딩 컨버터 사용 할 수 있도록... -->
  <SL5_BOARD_Converter:ValueToBoolConverter x:Key="ValueToBoolConverter"/>
  <SL5_BOARD_Converter:BoardTypeConverter x:Key="BoardTypeConverter"/>
 </UserControl.Resources>
   
    <Grid x:Name="LayoutRoot" Background="White">

     <!--조회 커맨드 실행-->
     <i:Interaction.Triggers>
      <i:EventTrigger>
       <i:InvokeCommandAction Command="{Binding SelectCommand, Mode=OneWay}"/>
      </i:EventTrigger>
     </i:Interaction.Triggers>
     <Grid.ColumnDefinitions>
      <ColumnDefinition Width="192"/>
      <ColumnDefinition/>
     </Grid.ColumnDefinitions>
     <Grid.RowDefinitions>
      <RowDefinition Height="38"/>
      <RowDefinition Height="35"/>
      <RowDefinition/>
      <RowDefinition Height="35"/>
     </Grid.RowDefinitions>
     <Grid.DataContext>

      <!--디자인 타임에서 사용하기 위해 넣어 놓은 부분-->
      <SL5_BOARD_ViewModels:BoardMainViewModel/>
     </Grid.DataContext>

      <!--사실 이걸 다 읽는다고 이쁜 디자인이 되는 것도 아니니..디자인 부분은 넘어가겠다..-->
     <TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="게시판 관리" FontSize="14.667" FontWeight="Bold" Foreground="{StaticResource ForeColor_Blues}" Margin="10,6,0,10" d:LayoutOverrides="Height"/>
     <Path Data="M0,30 L400,30" Fill="#FF5181B9" Height="3" Margin="0" Stretch="Fill" Stroke="#FF5181B9" UseLayoutRounding="False" VerticalAlignment="Bottom" StrokeLineJoin="Bevel" StrokeThickness="3" Grid.ColumnSpan="2"/>
     <Button x:Name="btnSelect" Content="조회" HorizontalAlignment="Right" Margin="0,6,8,7" Width="70" Grid.Column="1" Grid.Row="1" Height="22"/>
     <Path Data="M0,30 L400,30" Fill="{StaticResource ForeColor_Kakis}" Stretch="Fill" Stroke="{StaticResource ForeColor_Kakis}" UseLayoutRounding="False" StrokeLineJoin="Bevel" StrokeThickness="3" Grid.ColumnSpan="2" Grid.Row="1" Height="3" VerticalAlignment="Bottom" Margin="0,0,0,-1"/>
     <TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="게시판 목록" FontSize="12" FontWeight="Bold" Foreground="{StaticResource ForeColor_Kakis}" Margin="10,0,0,368" Grid.Row="2" VerticalAlignment="Bottom"/>

 

     <!--아까 모델에서 만들었던 CollectionData를 여기 ItemsSource에 바인딩 한다. 그리고, 아이템 템플릿을 이용해서 데이터를 뿌려준다.-->
     <ListBox x:Name="lbBoardMain" Margin="8,27,8,8" Grid.Row="2" BorderBrush="{x:Null}" ItemsSource="{Binding BoardMainData.CollectionData}" ItemTemplate="{StaticResource BoardMainDataTemplate}">
      <i:Interaction.Triggers>

       <!--아까 뷰모델에서 만들어 놓았던 SelectChangedCommand를 여기서 사용한다-->
       <i:EventTrigger EventName="SelectionChanged">
        <i:InvokeCommandAction Command="{Binding SelectChangedCommand, Mode=OneWay}" CommandParameter="{Binding SelectedItem, ElementName=lbBoardMain}"/>
       </i:EventTrigger>
      </i:Interaction.Triggers>
     </ListBox>

     <TextBlock TextWrapping="Wrap" Text="게시판 상세 정보" FontSize="12" FontWeight="Bold" Foreground="{StaticResource ForeColor_Kakis}" Margin="8,0,0,367" Grid.Row="2" VerticalAlignment="Bottom" HorizontalAlignment="Left" Grid.Column="1"/>

     <!--게시판 상세 정보가 들어가는 스택패널 여기서 CurrentData를 바인딩 한다.-->
     <StackPanel x:Name="spBoardMainInfo" Margin="8,27,8,8" Grid.Column="1" Grid.Row="2" DataContext="{Binding BoardMainData.CurrentData}">
      <StackPanel Orientation="Horizontal" Height="28">
       <Ellipse Fill="#FF666666" Stroke="Black" Width="5" Height="5" VerticalAlignment="Center" Margin="10,0,0,0"/>
       <TextBlock TextWrapping="Wrap" Text="명칭" FontSize="12" VerticalAlignment="Center" Width="70" Padding="5,0,0,0"/>
       <TextBox TextWrapping="Wrap" Text="{Binding BOARD_NAME, Mode=TwoWay}" Foreground="{StaticResource NormalForeColor}" BorderThickness="0,0,0,1" FontSize="12" VerticalAlignment="Center" Style="{StaticResource NormalTextBoxStyle}"/>
      </StackPanel>

      <StackPanel Orientation="Horizontal" Height="28">
       <Ellipse Fill="#FF666666" Stroke="Black" Width="5" Height="5" VerticalAlignment="Center" Margin="10,0,0,0"/>
       <TextBlock TextWrapping="Wrap" Text="종류" FontSize="12" VerticalAlignment="Center" Width="70" Padding="5,0,0,0"/>
       <ComboBox Width="120" VerticalAlignment="Center" Foreground="{StaticResource NormalForeColor}" ItemsSource="{Binding Path=DataContext.BoardMainData.BoardTypeCol, ElementName=LayoutRoot}" DisplayMemberPath="BoardTypeComboText" SelectedValuePath="BoardType" SelectedValue="{Binding BOARD_TYPE, Mode=TwoWay}"/>
      </StackPanel>

      <StackPanel Orientation="Horizontal" Height="28">
       <Ellipse Fill="#FF666666" Stroke="Black" Width="5" Height="5" VerticalAlignment="Center" Margin="10,0,0,0"/>
       <TextBlock TextWrapping="Wrap" Text="로그인확인" FontSize="12" VerticalAlignment="Center" Width="70" Padding="5,0,0,0"/>
       <CheckBox Content="{Binding MEMBER_CHECK}" VerticalAlignment="Center" Foreground="{StaticResource NormalForeColor}" IsChecked="{Binding MEMBER_CHECK, Mode=TwoWay}"/>
      </StackPanel>

      <StackPanel Orientation="Horizontal" Height="28">
       <Ellipse Fill="#FF666666" Stroke="Black" Width="5" Height="5" VerticalAlignment="Center" Margin="10,0,0,0"/>
       <TextBlock TextWrapping="Wrap" Text="설명" FontSize="12" VerticalAlignment="Center" Width="70" Padding="5,0,0,0"/>
       <TextBox TextWrapping="Wrap" Text="{Binding BOARD_DESC, Mode=TwoWay}" Foreground="{StaticResource NormalForeColor}" BorderThickness="0,0,0,1" FontSize="12" VerticalAlignment="Center" Style="{StaticResource NormalTextBoxStyle}"/>
      </StackPanel>

      <StackPanel Orientation="Horizontal" Height="28" >
       <Ellipse Fill="#FF666666" Stroke="Black" Width="5" Height="5" VerticalAlignment="Center" Margin="10,0,0,0"/>
       <TextBlock TextWrapping="Wrap" Text="상태" FontSize="12" VerticalAlignment="Center" Width="70" Padding="5,0,0,0"/>
       <RadioButton x:Name="radioButton" Content="준비중" VerticalAlignment="Bottom" Foreground="{StaticResource NormalForeColor}" Margin="0,0,0,5" GroupName="BoardState"
     IsChecked="{Binding ElementName=LayoutRoot, Path=DataContext.BoardMainData.BoardStateText, ConverterParameter=준비중, Converter={StaticResource ValueToBoolConverter}, Mode=OneWay}" Command="{Binding DataContext.ChangeStateCommand, ElementName=LayoutRoot}" CommandParameter="{Binding Content, ElementName=radioButton}"/>
       <RadioButton x:Name="radioButton1" Content="사용중" VerticalAlignment="Bottom" Foreground="{StaticResource NormalForeColor}" Margin="8,0,0,5" GroupName="BoardState"
     IsChecked="{Binding ElementName=LayoutRoot, Path=DataContext.BoardMainData.BoardStateText, ConverterParameter=사용중, Converter={StaticResource ValueToBoolConverter}, Mode=OneWay}" Command="{Binding DataContext.ChangeStateCommand, ElementName=LayoutRoot}" CommandParameter="{Binding Content, ElementName=radioButton1}"/>
       <RadioButton x:Name="radioButton2" Content="일시중지" VerticalAlignment="Bottom" Foreground="{StaticResource NormalForeColor}" Margin="8,0,0,5" GroupName="BoardState"
     IsChecked="{Binding ElementName=LayoutRoot, Path=DataContext.BoardMainData.BoardStateText, ConverterParameter=일시중지, Converter={StaticResource ValueToBoolConverter}, Mode=OneWay}" Command="{Binding DataContext.ChangeStateCommand, ElementName=LayoutRoot}" CommandParameter="{Binding Content, ElementName=radioButton2}"/>
       <RadioButton x:Name="radioButton3" Content="삭제" VerticalAlignment="Bottom" Foreground="{StaticResource NormalForeColor}" Margin="8,0,0,5" GroupName="BoardState"
     IsChecked="{Binding ElementName=LayoutRoot, Path=DataContext.BoardMainData.BoardStateText, ConverterParameter=삭제, Converter={StaticResource ValueToBoolConverter}, Mode=OneWay}" Command="{Binding DataContext.ChangeStateCommand, ElementName=LayoutRoot}" CommandParameter="{Binding Content, ElementName=radioButton3}"/>
      </StackPanel>

            <StackPanel Orientation="Horizontal" Height="28">
             <Ellipse Fill="#FF666666" Stroke="Black" Width="5" Height="5" VerticalAlignment="Center" Margin="10,0,0,0"/>
             <TextBlock TextWrapping="Wrap" Text="순서" FontSize="12" VerticalAlignment="Center" Width="70" Padding="5,0,0,0"/>
             <TextBox TextWrapping="Wrap" Text="{Binding BOARD_ORDER, Mode=TwoWay}" Foreground="{StaticResource NormalForeColor}" BorderThickness="0,0,0,1" FontSize="12" VerticalAlignment="Center" Style="{StaticResource NormalTextBoxStyle}"/>
            </StackPanel>

     </StackPanel>
     <!--게시판 상세 정보 패널 끝-->

      <!--각종 버튼들-->
    <Button x:Name="btnAdd" Content="추가" Margin="0,3,8,0" Grid.Row="2" VerticalAlignment="Top" HorizontalAlignment="Right" Width="70" Height="22">
      <i:Interaction.Triggers>
       <i:EventTrigger EventName="Click">
        <i:InvokeCommandAction Command="{Binding AddCommand, Mode=OneWay}"/>
       </i:EventTrigger>
      </i:Interaction.Triggers>
     </Button>

     <Button x:Name="btnSave" Content="저장" Margin="0,3,82,0" Grid.Row="2" VerticalAlignment="Top" Height="22" Grid.Column="1" HorizontalAlignment="Right" Width="70">
      <i:Interaction.Triggers>
       <i:EventTrigger EventName="Click">
        <i:InvokeCommandAction Command="{Binding SaveCommand, Mode=OneWay}"/>
       </i:EventTrigger>
      </i:Interaction.Triggers>
     </Button>

     <Button x:Name="btnCancel" Content="취소" Margin="0,3,8,0" Grid.Row="2" VerticalAlignment="Top" Height="22" Grid.Column="1" HorizontalAlignment="Right" Width="70">
      <i:Interaction.Triggers>
       <i:EventTrigger EventName="Click">
        <i:InvokeCommandAction Command="{Binding CancelCommand, Mode=OneWay}"/>
       </i:EventTrigger>
      </i:Interaction.Triggers>
     </Button>
     <TextBlock Margin="8,6,82,8" Grid.Row="1" TextWrapping="Wrap" Text="{Binding BoardMainData.MessageData, Mode=OneWay}" Grid.ColumnSpan="2" VerticalAlignment="Center" FontSize="12"/>

    </Grid>
</UserControl>

BoardMainView.xaml.cs

using System.ComponentModel.Composition;
using System.Windows;
using System.Windows.Controls;
using SL5_BOARD.ViewModels;

namespace SL5_BOARD.Views
{
    //아직 이넘은 익스포트 앙시켰음
    public partial class BoardMainView : UserControl
    {
        //뷰모델 임포트
        [Import(typeof(BoardMainViewModel))]
        public BoardMainViewModel BoardMainVM { get; set; }

        public BoardMainView()
        {
            InitializeComponent();

            //MEF 초기화
            CompositionInitializer.SatisfyImports(this);

            //ViewModel을 최상위 DataContext에 쑤셔넣구
            LayoutRoot.DataContext = BoardMainVM;
        }

    }
}


6. 너무 길다..작업 시간이 어제 저녁 부터 시작해서 오늘 오후에 끝났으니..음..한 7-8 시간은 작업을 한듯하니..양도 많아서..한번에 올리기가 부담스러운.. 작업을 하면서도 이거 게시판에 어떻게 올리나 한참 궁시렁 거리면서 작업을 했었는데..흐흐흐..원래는 개발하면서 주석을 잘 다는 편인데..이상하게 실버라이트 만들때는 주석 달기가 싫타;; 그래서 소스에 주석이 없어서 소스를 올려도 보기가 쉽지는 않치만..음음.. 일단 뭔가 제대로 돌아가는 듯한 모양을 갖추었으니.. 전체 소스를 한번 올리도록 하겠다.

 여기서 다루지 않았던 컨버터 부분과 게시판 종류를 콤보박스에 표시하기 위해서 약간 먼가 처리한 부분들을 추가로 보면 되고.. 만은 도움이 되었으면 한다. 그리고 디자인 해주실분~ 다시 구해본다..(구해질때까징..ㅜㅜ) 이제 좀있음 여러 화면 떠서 아이폰에서 클릭하면 옆으로 휙하고 지나가고 다시 돌아오는 애니메이션 기능 넣구 싶은뎅..이힝..알려주실분도 좋구..도와주실분도..좋쿠..좀..

Posted by MVP kaki104

일단 디자인 레이아웃은..실버라이트 코리아의 디자인을 빼꼈다는..쿨럭 ^^;;;;;디자인 해줄 분이 없는 관계로..ㅜㅜ 그냥 기본 디자인만 잡아 봤고, 이제 슬슬 본격 적으로 사용해야할 폴더들을 만들어 놓고, 리소스딕셔너리도 추가해 놓구 ㅎㅎ

1. 기본 메인 디자인

디자인 정말 거시기한.. 다시 한번 디자인 해주실 구해야 겠다는..손좀 들어보시죠??(ㅡㅡ+) 이것이 통짜 실버라이트 게시판...이 될 예정인 화면! 냐하하..음음..이 소스는..덩치가 큰 관계로 일부 파일만 첨부를 해서 올리도록 하겠다..(머 소스 업어두 아무도 신경쓰지 앙치만..ㅜㅜ 나랑 같이 만들고 있는 사람 손좀;;)

2. 중요 부분

2-1. 폴더 추가
SL5_BOARD 프로젝
-> Images, Models, Resources, ViewModels, Views

2-2. 일단 기존에 있던 내용들은 싹다 지우고 새로 그린 것이고, 이렇게 디자인을 할때 블랜드를 사용하지 앙고 디자인 하면 무지 스트레스 받으니, 미리미리 블랜드 사용법도 익혀 두는 것이 정신 건강에 도움이 된다는..

2-3. 이미지는 뷰박스로 싸메서 사용한다. 그래야 이미지 크기가 변경대구 하더라도 보기 좋다.

<Viewbox Stretch="Fill" Grid.Row="1" Margin="6,0">
 <Image x:Name="ImgLogo" Source="Images/logoPixelImage.jpg" Stretch="None"/>
</Viewbox>

참, 여기서...프로젝트에 이미지를 포함하고 불러다가 사용할 때.. 이미지 파일의 속성에 들어가서 Build Action을 꼭 Resource로 지정하고 불러다가 사용해야한다. 음음..이거 몰라서 몇 시간 허비하면 눈물난다..

2-4. 동일한 스타일이나, 컬러등은 리소스로 만들어서 사용한다. 이 리소스 만드는 작업이 블랜드를 사용하면 메뉴 하나 클릭하면 바로 만들어 지지만 VS2010에서는 일일이 수작업으로 만들어야 한다.

* 수동 작업 방법
Resources폴더에서 Add -> New Item -> Resources File 추가

BoardResourceDictionary.xaml

 <ResourceDictionary
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
 <SolidColorBrush x:Key="CountColor" Color="#FFFF1E1E"/>
 <!-- Resource dictionary entries should be defined here. -->
</ResourceDictionary>


 App.xaml

<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             x:Class="SL5_BOARD.App"
             >
    <Application.Resources>
       
     <ResourceDictionary>
      <ResourceDictionary.MergedDictionaries>
       <ResourceDictionary Source="Resources/BoardResourceDictionary.xaml"/>
      </ResourceDictionary.MergedDictionaries>
     </ResourceDictionary>
       
    </Application.Resources>
</Application>


이렇게 만들어 놓으면 어떤 화면이서든지 리소스를 xaml에 바로 적용할 수 있다.

<StackPanel Margin="4,0" Orientation="Horizontal">
 <TextBlock TextWrapping="Wrap" Text="게시물" FontSize="12" Width="40"/>
 <TextBlock TextWrapping="Wrap" Text="44" FontSize="12" FontWeight="Bold" Margin="5,0,0,0" Foreground="{StaticResource CountColor}"/>
</StackPanel>

2-5. 디자인 하면서 까다로운 작업 중 하나가.. 스크롤 관련 문제이다..이 화면도 메뉴 부분을 스크롤 시키기 위해서 약간 시간이 걸렸는데.. 머리를 열심히 굴려야 왜 저렇게 해야하는지 이해가 된다..이걸 VS2010에서만 작업해서 만들려고 하면 ...머리에서 쥐가..쿨럭..

<Border x:Name="MainMenu" BorderBrush="#FFAED8DC" BorderThickness="4" Grid.Row="1" Margin="0,8,0,0" CornerRadius="10" Padding="6">
 <ScrollViewer BorderThickness="0">
  <StackPanel Margin="0">
   <ListBox BorderThickness="0">
    <ListBoxItem Content="전체글보기" BorderThickness="0"/>
    <ListBoxItem Content="카페태그보기"/>
    <ListBoxItem Content="베스트게시물"/>
    <ListBoxItem Content="소셜앱"/>
    <ListBoxItem Content="공지/수요소식"/>
    <ListBoxItem Content="매일매일 출석~"/>
    <ListBoxItem Content="자기소개~"/>
    <ListBoxItem Content="자유로운글"/>
    <ListBoxItem Content="멋쟁이 사진첩"/>
    <ListBoxItem Content="자유사진첩"/>
    <ListBoxItem Content="세미나/행사/자료"/>
    <ListBoxItem Content="회원작품"/>
    <ListBoxItem Content="RIA 구현 사례"/>
    <ListBoxItem Content="구인/구직"/>
    <ListBoxItem Content="운영자카페"/>
    <ListBoxItem Content="운영자게시판"/>
    <ListBoxItem Content="실버라이트"/>
    <ListBoxItem Content="강좌"/>
    <ListBoxItem Content="Tips And Tricks"/>
   </ListBox>
  </StackPanel>
 </ScrollViewer>
</Border>

2-6. 메인 컨텐츠가 들어갈 부분은 ItemsControl로 작업 했다. 그곳에 다른 곳에서 만들어 놓은 UserControl을 계속 번갈아 가며 올릴 것이다. 음..이것 말고 Page를 쓸까 생각해 봤는데..Page를 사용했을 때 네비게이션 기능 말고는..별로 없는 것 같아서..이번에는 이걸로 하기로 했다.


3. 디자인이 되니 뭔가 보이는 것 같다. 빨리 진행해서 이번주 안에 1차 완성을 할 수 있었으면 한다. 그리고...디자인 해줄 분 다시 한번 구한당..ㅜㅜ

 ****첨부 파일은 SL5_BOARD 프로젝트만 포함되었다..전체 소스가 아니니 실행은 앙된다..^^;;; 전체 소스가 필요한 사람은 리플로 요청을 하면 개별 적으로 보내 주는 것으로....하지만 본인이 직접 만들어 봐야 실력이 늘어나니..열심히 화이팅~

Posted by MVP kaki104

이젠 나머지 테이블들 만들고 본격 적으로 들어가야 겠다. 다른 테이블들 만드는 것은 어렵지 앙았는데.. 한가지 문제 해결과 한가지 문제가 생겼다..


1. 나머지 테이블들
(작업 하면서 테이블 명세서를 약간씩 수정했는데..테이블 명세서만 필요하지는 않을 것 같아..첨부파일로 올리지는 앙는다)

BOARD_REPLY.cs

using System;
using System.ComponentModel.DataAnnotations;

namespace SL5_BOARD.Web.Model
{
    public class BOARD_REPLY
    {
        [Key]
        public int BOARD_REPLY_IDX { get; set; }
        public string REPLY_CONTENT { get; set; }
        public DateTime REG_DT { get; set; }
        public int REG_IDX { get; set; }
        public DateTime? UPT_DT { get; set; }
        public int? UPT_IDX { get; set; }

        public virtual BOARD_LIST BoardList { get; set; }
        public int BOARD_LIST_IDX { get; set; }
    }
}


BOARD_ATTACH.cs

using System;
using System.ComponentModel.DataAnnotations;

namespace SL5_BOARD.Web.Model
{
    public class BOARD_ATTACH
    {
        [Key]
        public int BOARD_ATTACH_IDX { get; set; }
        public string FILE_NAME { get; set; }
        public string FILE_EXT { get; set; }
        public int FILE_SIZE { get; set; }
        public DateTime REG_DT { get; set; }
        public int REG_IDX { get; set; }
        public DateTime? UPT_DT { get; set; }
        public int? UPT_IDX { get; set; }

        public virtual BOARD_LIST BoardList { get; set; }
        public int BOARD_LIST_IDX { get; set; }

        public BOARD_ATTACH()
        {
            FILE_SIZE = 0;
        }
    }
}


MST_MEMBER.cs

using System;
using System.ComponentModel.DataAnnotations;

namespace SL5_BOARD.Web.Model
{
    public class MST_MEMBER
    {
        [Key]
        public int MST_MEMBER_IDX { get; set; }
        public string EMAIL { get; set; }
        public string PASSWORD { get; set; }
        public string MEMBER_NAME { get; set; }
        public string NICK_NAME { get; set; }
        public bool EMAIL_RECEIVE { get; set; }
        public string DESCRIPTION { get; set; }
        public DateTime LAST_LOGIN_DT { get; set; }
        public string JOB { get; set; }
        public string HOBBY { get; set; }
        public string INTEREST { get; set; }
        public int MEMBER_SCORE { get; set; }
        public string MEMBER_GRADE { get; set; }
        public string ETC { get; set; }
        public int STATUS { get; set; }
        public DateTime REG_DT { get; set; }
        public int REG_IDX { get; set; }
        public DateTime? UPT_DT { get; set; }
        public int? UPT_IDX { get; set; }

        public MST_MEMBER()
        {
            EMAIL_RECEIVE = true;
            LAST_LOGIN_DT = DateTime.Parse("2000-01-01");
            MEMBER_SCORE = 0;
            STATUS = 0;
        }
    }
}


SL5_BOARD_DBCONTEXT.cs 전체 소스


using System.Data.Entity;
using System.Web;

namespace SL5_BOARD.Web.Model
{
    public class SL5_BOARD_DBCONTEXT : DbContext
    {
        public SL5_BOARD_DBCONTEXT()
            : base("SL5_BOARD")
        {
            if (HttpContext.Current == null)
            {
                //맨뒤에 새로 추가된 부분이 데이터베이스가 변경되었을 때 db를 새로 만든다..라는..머 그런 내용
                Database.SetInitializer<SL5_BOARD_DBCONTEXT>(new SL5_BOARD_DBCONTEXTInitializer());
            }
        }

        public DbSet<BOARD_MAIN> BOARD_MAINS { get; set; }
        public DbSet<BOARD_LIST> BOARD_LISTS { get; set; }
        public DbSet<BOARD_REPLY> BOARD_REPLYS { get; set; }
        public DbSet<BOARD_ATTACH> BOARD_ATTACHS { get; set; }
        public DbSet<MST_MEMBER> MST_MEMBERS { get; set; }
    }

    //내부에는 Seed라고 해서 새로운 DB가 만들어지면 내용을 넣도록 되어있는데..그런건 뺐음
    //이곳이 실행되면서 db를 새로 만들어줌..(그러나..100% 잘 동작되는 것은 아님)
    public class SL5_BOARD_DBCONTEXTInitializer : DropCreateDatabaseIfModelChanges<SL5_BOARD_DBCONTEXT>
    {
    }
}

2. 한가지 문제 해결
위에 내용이 있는데..데이터베이스가 변경되었을 때 수동으로 삭제하고 다시 만들어야 했던 부분이 해결되었다. 음..사용해 본 봐에 의하면 한 90%정도는 지워지고 다시 잘 만들어지는데 10%정도는 앙대는 경우도 있으니 그때는 삭제하고 실행하면 다시 만들어 준다..하하하..(Compact DB이니 삭제하고 만들고 삭제하고 만들고 하는 부분에 대해 이해를 해보도록 하자;;)

3. 한가지 문제 발생
각 테이블에는 REG_ID, UPT_ID라는 필드가 존재하는데..이것이..MST_MEMBER 테이블과 연결이 되어야 하는 필드들이다. 그런데 동일한 테이블들끼리 1:N 연결관계를 2개를 만들어 줘야하는데..그게 쉽지 않다...그래서, 찾아보다가.. 고수들의 도움을 받기위해 MS 포럼에 질문을 올려 놓은 상태이니 좀 기다리면..답변이 달리지 앙을까 한다..(끝까지 앙달리면 다른 방법을..쿨럭)

4. 이번은 여기서
마무리 하고 다음은 SL5_BOARD 프로젝트의 전체 적인 설계 방향을 잡아보도록 하자.

Posted by MVP kaki104

처음에 Streamlined Operation의 맛을 보았을 때 황당했었는데.. 마치 유주얼서스팩트의 마지막을 보는 듯한..느낌이였죠, 그런데, 그걸 글로 표현해서 보여줄려면 어떻게 해야 할까..고민하다가 제목의 주제로 추가 작성을 하기로 했다.


1. 서버사이드 기술과 클라이언트 사이드 기술
정리를 하고 시작해 보자.

1-1. SL5_BOARD.Web 프로젝트에서 사용되는 기술
: SQL Server Compact 4.0 + Entity Framework 4.1 (Streamlined Database)
: 서버단에서는 SL5_BOARD_DBCONTEXT를 통해 모든 데이터를 관리, 그렇기 때문에 LINQ쿼리를 통해서 마음대로 불러내고 저장하고 떡주무르듯 할 수 있음

1-2. SL5_BOARD 프로젝트에서 사용되는 기술
: WCF RIA Service Support EF4.1
: 서버에 있는 데이터를 일단은 클라이언트까지 전송하는 기능, 전송된 데이터를 관리하는 기능, 하지만, 역시 데이터가 서버에 존재 하기 때문에 클라이언트까지 가지고 오는 것이 선행되어야 함


2. 짐승같은 코딩과 인간같은 코딩

위의 둘은 요렇게 서로 연결이 되어있다..(이렇게 연결시키기 위해서 열심히 이것 저것 했으니..되어야징..) 그런데 연결을 왜 해 놓은걸까? 방금 전 소스상에서 연결해 놓은 덕을 보았나? 그렇게 할려면 뭐한다고 연결하는 것인가? 음음음...그러게..여태까지 짐승같은 코딩을 해 놓은 거내~~~~~~~~~~~~~이제 짐승코딩에서 인간코딩으로 바꿔보자..그래서 인간이 되어야 인간답게 살 수 있는 거다.


2-2. 짐승같은 코딩
BOARD_MAIN에 셀렉트 채인지 이벤트 발생시마다, 해당 데이터를 조해서 다시 뿌려주는..코딩

//그리드 셀렉션 체인지 이벤트 처리
private void bOARD_MAINDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    //컨테이너에 데이터가 존재 한다면
    if (dsContext.EntityContainer != null)
    {
        //리스트의 내용을 지워놓고
        dsContext.BOARD_LISTs.Clear();
        //선택된 BOARD_MAIN을 일단 넣궁
        BOARD_MAIN bm = e.AddedItems.Cast<BOARD_MAIN>().FirstOrDefault();

        //BOARD_MAIN에 속해있는 BOARD_LIST만 조회
        dsContext.Load(dsContext.GetBoardListByBoardMainQuery(bm.BOARD_MAIN_IDX), LoadBehavior.RefreshCurrent, true);
    }
}

2-3. 인간같은 코딩
: 딱 필요할 때만 데이터를 서버에서 불러오고, 그 외에는 자체 해결~

수정 포인트 1

MainPage.xaml.cs

private void bOARD_MAINDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{

    //선택된 BOARD_MAIN을 넣구
    BOARD_MAIN bm = e.AddedItems.Cast<BOARD_MAIN>().FirstOrDefault();

    //BOARD_MAIN에 속해있는 BOARD_LIST의 카운트가 0이거나, BOARD_MAIN이 변경되었을 경우
    if (bm.BoardLists.Count == 0 || bm.HasChanges == true)
    {

        //BOARD_MAIN_IDX가 들어있는 BOARD_LIST 조회
        dsContext.Load(dsContext.GetBoardListByBoardMainQuery(bm.BOARD_MAIN_IDX), LoadBehavior.RefreshCurrent, true);
    }
}


수정 포인트 2

MainPage.xaml

<sdk:DataGrid AutoGenerateColumns="False" Grid.Row="3"
              ItemsSource="{Binding SelectedItem.BoardLists, ElementName=bOARD_MAINDataGrid}"
              Name="bOARD_LISTDataGrid"
              RowDetailsVisibilityMode="VisibleWhenSelected">

바인딩 항목이 바뀌었다. 전에는 ItemsSource="{Binding Path=BOARD_LISTs}" 라는 항목을 바인딩 했었지만 지금은 BOARD_MAIN이 표시되는 그리드의 선택된 내용에 있는 BoardLists라는 항목을 바인딩 한다. 무슨 차이가 있을까??

지금부터 5분간 머리속으로 상상을 해보기 바란다. 그 차이를 찾는다면 왜 이게 인간같은 코딩인지 알 수 있을 것이다.


2. 열심히 로딩과 게으른 로딩(Eagerly loading and Lazy loading)
위와 같은 인간같은 코딩의 기본에는 열심히 로딩과 게으른 로딩(명시적 로딩과 비명시적 로딩 혹은 동기, 비동기 로딩)이란 베이스 기술이 필요하다.

Using DbContext in EF 4.1 Part 6: Loading Related Entities

http://blogs.msdn.com/b/adonet/archive/2011/01/31/using-dbcontext-in-ef-feature-ctp5-part-6-loading-related-entities.aspx

BOARD_MAIN은 이미 BOARD_LIST 중 어떤 넘이 자신을 찍었는지 알고 있다..다만 어떤 넘인지 표현 하려면, BOARD_LIST가 로딩이 되어야 가능하고, 로딩하는 시간과 방법에 따라 2가지로 구분을 한것이다. 좀 더 자세히 살펴 보면, BOARD_MAIN.cs 파일을 보면 아래와 같은 내용이 있었는데..

public virtual ObservableCollection<BOARD_LIST> BoardLists { get; set; }

여기서 virtual을 사용해준것이 게으른 로딩 방식을 사용하겠다는 뜻이다. 즉, 처음에 로딩할때 데이터 없이 그냥 로딩이 된다 하더라도, 나중에 데이터가 Context로 로딩이 되어 들어온다면 그 때 바인딩을 하겠다는 것이다.

3. 결과는 4회차 강좌나 5회차 강좌나 동일하다.
하지만, 그 내용적인 측면에서는 큰 의미를 가지고 있이며, 그걸 가지고 가기를 원한다. MS사의 개발 철학은 Decouple, Asynchronous, Loosely, Lazy 로 이동 중이며, Windows 8이 발표 되면서 정점을 찍을 것으로 생각된다.

Posted by MVP kaki104

이번에는 하나의 테이블을 추가해서 두개의 테이블을 서로 연결 시켜 놓고, CRUD를 하는 방법에 대해서 설명한다.

참고적으로.. 지금 개발하는 Silverlight 5, WCF RIA Service Support EF 4.1, SQL Server Compact 4.0을 이용한 예제 프로그램은 전세계에서 아마 이 프로그램이 유일할 듯하다.. 유사 프로그램도 없어서 하나 하나 찾아서 만드는게 쉽지 않다..그러나.. 끝까지 완성해서 실버라이트 공개 게시판이 자유롭게 만들어 지도록 하겠다...언넝 만들어야징;;(혼자 만들면 심심한데..ㅜㅜ 리플로 놀아주면 좋겠다는..쿨럭)

 
1. BOARD_LIST 테이블 추가

SL5_BOARD.Web -> Model Folder -> BOARD_LIST.cs 파일 추가

 using System;
using System.ComponentModel.DataAnnotations;

namespace SL5_BOARD.Web.Model
{
    public class BOARD_LIST
    {
        [Key]
        public int BOARD_LIST_IDX { get; set; }
        public string LIST_TITLE { get; set; }
        public string LIST_CONTENT { get; set; }
        public bool LIST_ATTACH_YN { get; set; }
        public int LIST_SEQ { get; set; }
        public int LIST_LEVEL { get; set; }
        public int LIST_VIEW_COUNT { get; set; }
        public string LIST_WRITER_NAME { get; set; }
        [DataType(DataType.Password)]  //패스워드를 저장할때 어떻게좀 해볼려고 써봤는데..잘 앙됨
        public string LIST_PASSWORD { get; set; }
        public string LIST_KIND { get; set; }
        public int LIST_COMPLETE_GB { get; set; }
        public bool LIST_OPEN_YN { get; set; }
        public int LIST_GOOD_COUNT { get; set; }
        public int LIST_PUBLIC_GB { get; set; }
        public DateTime REG_DT { get; set; }
        public int REG_IDX { get; set; }
        public DateTime? UPT_DT { get; set; }
        public int? UPT_IDX { get; set; }

        //여기가 중요!!
        //BOARD_MAIN 테이블과 연결 고리 음 일명 뽀링키가 되는데 형태가 오브젝트다
        //그리고 진짜 키 값은 바로 아래 필드에 존재하는데..사용 방법은 나중에

        public virtual BOARD_MAIN BoardMain { get; set; }
        public int BOARD_MAIN_IDX { get; set; }

        //생성자에서 초기값을 넣어 준다..귀찮으니 여기서 한번만 지정해 놓는다..int, bool형은 꼭 있어야 하더라는
        //이렇게 하는 것이 싫으면 위에 필드 타입에 ?를 붙여 주면 된다.

        public BOARD_LIST()
        {
            LIST_ATTACH_YN = false;
            LIST_SEQ = 0;
            LIST_LEVEL = 1;
            LIST_VIEW_COUNT = 0;
            LIST_COMPLETE_GB = 0;
            LIST_OPEN_YN = true;
            LIST_GOOD_COUNT = 0;
            LIST_PUBLIC_GB = 0;
        }
    }
}

2. BOARD_MAIN 테이블 수정

using System;
using System.Collections.ObjectModel;
using System.ComponentModel.DataAnnotations;

namespace SL5_BOARD.Web.Model
{
    public class BOARD_MAIN
    {
        [Key]
        public int BOARD_MAIN_IDX { get; set; }
        public string BOARD_NAME { get; set; }
        public string BOARD_TYPE { get; set; }
        public bool MEMBER_CHECK { get; set; }
        public string BOARD_DESC { get; set; }
        public string BOARD_STATE { get; set; }
        public DateTime REG_DT { get; set; }
        public int REG_IDX { get; set; }
        public DateTime? UPT_DT { get; set; }
        public int? UPT_IDX { get; set; }

        //BOARD_MAIN은 여러개의 BOARD_LIST를 가질 수 있다. 그래서 그런 연결관계에 대해서 메인에도 표시를
        //해주어야 한다. 역시 여기서도 옵저블컬렉션을 사용해서 세트를 만들었다.

        public virtual ObservableCollection<BOARD_LIST> BoardLists { get; set; }

        //BOARD_MAIN의 생성자
        public BOARD_MAIN()
        {
            BOARD_NAME = "신규 게시판";
            MEMBER_CHECK = false;
            //생성자에서 리스트 인스턴스 시켜준다.
            this.BoardLists = new ObservableCollection<BOARD_LIST>();
        }
    }
}

3. SL5_BOARD_DBCONTEXT 수정

using System.Data.Entity;
using System.Web;

namespace SL5_BOARD.Web.Model
{
    public class SL5_BOARD_DBCONTEXT : DbContext
    {
        public SL5_BOARD_DBCONTEXT()
            : base("SL5_BOARD")
        {
            if (HttpContext.Current == null)
            {
                Database.SetInitializer<SL5_BOARD_DBCONTEXT>(null);
            }
        }

        public DbSet<BOARD_MAIN> BOARD_MAINS { get; set; }

        //이넘 하나만 추가하면 된다.
        public DbSet<BOARD_LIST> BOARD_LISTS { get; set; }
    }
}

4. F6을 눌러서 빌드해 본다.

아마 100% 에러 날것이다.. 에..그 이유는 데이터 베이스에 내용이 변경되면 새로 만들어 주는 명령이 있는데..그 명령 EF4.1 버전에서 어떻게 사용하는지를 찾다가 포기했기 때문이다..ㅜㅜ 그래서 일단은 데이터베이스에 변경사항이 있을 경우 DB를 지워줘야 한다.(물론 수동이다..^^;;) 그래서 프로젝 끝나기 전까지는 웬만하면 데이터 마니 앙넣을려고 한다;;

SL5_BOARD.Web 프로젝트에 App_Data 폴더에 가서 SL5_BOARD.sdf 파일을 지우고 다시 F6을 눌러준다. 정상 적으로 빌드가 되었다면 다음으로 넘어간다.


5. SL5_BOARDDomainService.cs 파일 수정

#region BOARD_LIST
//기본적으로 전체 호출 하는 넘

public IQueryable<BOARD_LIST> GetBoardList()
{
    return this.DbContext.BOARD_LISTS;
}

//BOARD_MAIN_IDX 값을 받아서 조건에 만족하는 데이터만 반환, 참고로 LINQ 공부도 병행해야 한다..
public IQueryable<BOARD_LIST> GetBoardListByBoardMain(int BoardMainIDX)
{
    var query = from p in this.DbContext.BOARD_LISTS
                where p.BOARD_MAIN_IDX == BoardMainIDX
                orderby p.BOARD_LIST_IDX descending
                select p;

    return query.AsQueryable();
}

public void InsertBoardList(BOARD_LIST entity)
{
    DbEntityEntry<BOARD_LIST> entityEntry = this.DbContext.Entry(entity);
    if ((entityEntry.State != EntityState.Detached))
    {
        entityEntry.State = EntityState.Added;
    }
    else
    {
        this.DbContext.BOARD_LISTS.Add(entity);
    }
}

public void UpdateBoardList(BOARD_LIST entity)
{
    this.DbContext.BOARD_LISTS.AttachAsModified(entity, this.ChangeSet.GetOriginal(entity), this.DbContext);
}

public void DeleteBoardList(BOARD_LIST entity)
{
    DbEntityEntry<BOARD_LIST> entityEntry = this.DbContext.Entry(entity);
    if ((entityEntry.State != EntityState.Deleted))
    {
        entityEntry.State = EntityState.Deleted;
    }
    else
    {
        this.DbContext.BOARD_LISTS.Attach(entity);
        this.DbContext.BOARD_LISTS.Remove(entity);
    }
}
#endregion

수정 후 F6눌러서 컴파일을 꼭 해준다.


6. SL5_BOARD 프로젝트로 넘어간다.

MainPage.xaml의 디자인을 변경한다.



화면 구성은 대충 이정도 쯤인데.. 화면 구성만 바뀐것이 아니라 내부에 xaml단의 코딩이 변경되었다.

중요 변경사항은..riacontrol을 삭제, 그리드가 LayoutRoot의 DataContext에 있는 데이터를 바인딩 하도록 변경 되었다.

MainPage.xaml의 전체 소스다. 일단 지금은 타이틀리 커플드로 작업하고 차 후에 MVVM모델로 변경한다.

<UserControl x:Class="SL5_BOARD.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
    xmlns:my="clr-namespace:SL5_BOARD.Web.Model"
    xmlns:my1="clr-namespace:SL5_BOARD.Web.DomainService"
    d:DesignHeight="300" d:DesignWidth="400" Loaded="UserControl_Loaded">

    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="94*" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="148*" />
        </Grid.RowDefinitions>
       
        <sdk:DataGrid AutoGenerateColumns="False"
                      ItemsSource="{Binding Path=BOARD_MAINs}"
                      Name="bOARD_MAINDataGrid"
                      RowDetailsVisibilityMode="VisibleWhenSelected"
                      Grid.Row="1" SelectionChanged="bOARD_MAINDataGrid_SelectionChanged">
            <sdk:DataGrid.Columns>
                <sdk:DataGridTextColumn x:Name="bOARD_MAIN_IDXColumn" Binding="{Binding Path=BOARD_MAIN_IDX, Mode=OneWay}" Header="BOARD MAIN IDX" IsReadOnly="True" Width="SizeToHeader" />
                <sdk:DataGridTextColumn x:Name="bOARD_NAMEColumn" Binding="{Binding Path=BOARD_NAME}" Header="BOARD NAME" Width="SizeToHeader" />
                <sdk:DataGridTextColumn x:Name="bOARD_TYPEColumn" Binding="{Binding Path=BOARD_TYPE}" Header="BOARD TYPE" Width="SizeToHeader" />
                <sdk:DataGridCheckBoxColumn x:Name="mEMBER_CHECKColumn" Binding="{Binding Path=MEMBER_CHECK}" Header="MEMBER CHECK" Width="SizeToHeader" />
                <sdk:DataGridTextColumn x:Name="bOARD_DESCColumn" Binding="{Binding Path=BOARD_DESC}" Header="BOARD DESC" Width="SizeToHeader" />
                <sdk:DataGridTextColumn x:Name="bOARD_STATEColumn" Binding="{Binding Path=BOARD_STATE}" Header="BOARD STATE" Width="SizeToHeader" />
                <sdk:DataGridTemplateColumn x:Name="rEG_DTColumn" Header="REG DT" Width="SizeToHeader">
                    <sdk:DataGridTemplateColumn.CellEditingTemplate>
                        <DataTemplate>
                            <sdk:DatePicker SelectedDate="{Binding Path=REG_DT, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" />
                        </DataTemplate>
                    </sdk:DataGridTemplateColumn.CellEditingTemplate>
                    <sdk:DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Path=REG_DT, StringFormat=\{0:d\}}" />
                        </DataTemplate>
                    </sdk:DataGridTemplateColumn.CellTemplate>
                </sdk:DataGridTemplateColumn>

                <sdk:DataGridTextColumn x:Name="rEG_IDXColumn" Binding="{Binding Path=REG_IDX}" Header="REG IDX" Width="SizeToHeader" />
                <sdk:DataGridTemplateColumn x:Name="uPT_DTColumn" Header="UPT DT" Width="SizeToHeader">
                    <sdk:DataGridTemplateColumn.CellEditingTemplate>
                        <DataTemplate>
                            <sdk:DatePicker SelectedDate="{Binding Path=UPT_DT, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" />
                        </DataTemplate>
                    </sdk:DataGridTemplateColumn.CellEditingTemplate>
                    <sdk:DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Path=UPT_DT, StringFormat=\{0:d\}}" />
                        </DataTemplate>
                    </sdk:DataGridTemplateColumn.CellTemplate>
                </sdk:DataGridTemplateColumn>
                <sdk:DataGridTextColumn x:Name="uPT_IDXColumn" Binding="{Binding Path=UPT_IDX}" Header="UPT IDX" Width="SizeToHeader" />
            </sdk:DataGrid.Columns>
        </sdk:DataGrid>
        <Button Content="Insert" Height="23" HorizontalAlignment="Left" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" Margin="97,0,0,0" />
        <Button Content="Update" Height="23" HorizontalAlignment="Left" Margin="178,0,0,0" Name="button2" VerticalAlignment="Top" Width="75" Click="button1_Click"/>
        <Button Content="Delete" Height="23" HorizontalAlignment="Left" Margin="259,0,0,0" Name="button3" VerticalAlignment="Top" Width="75" Click="button1_Click"/>

        <sdk:DataGrid AutoGenerateColumns="False" Grid.Row="3"
                      ItemsSource="{Binding Path=BOARD_LISTs}"
                      Name="bOARD_LISTDataGrid"
                      RowDetailsVisibilityMode="VisibleWhenSelected">
            <sdk:DataGrid.Columns>
                <sdk:DataGridTextColumn x:Name="bOARD_LIST_IDXColumn" Binding="{Binding Path=BOARD_LIST_IDX, Mode=OneWay}" Header="BOARD LIST IDX" IsReadOnly="True" Width="SizeToHeader" />
                <sdk:DataGridTextColumn x:Name="bOARD_MAIN_IDXColumn1" Binding="{Binding Path=BOARD_MAIN_IDX}" Header="BOARD MAIN IDX" Width="SizeToHeader" />
                <sdk:DataGridCheckBoxColumn x:Name="lIST_ATTACH_YNColumn" Binding="{Binding Path=LIST_ATTACH_YN}" Header="LIST ATTACH YN" Width="SizeToHeader" />
                <sdk:DataGridTextColumn x:Name="lIST_COMPLETE_GBColumn" Binding="{Binding Path=LIST_COMPLETE_GB}" Header="LIST COMPLETE GB" Width="SizeToHeader" />
                <sdk:DataGridTextColumn x:Name="lIST_CONTENTColumn" Binding="{Binding Path=LIST_CONTENT}" Header="LIST CONTENT" Width="SizeToHeader" />
                <sdk:DataGridTextColumn x:Name="lIST_GOOD_COUNTColumn" Binding="{Binding Path=LIST_GOOD_COUNT}" Header="LIST GOOD COUNT" Width="SizeToHeader" />
                <sdk:DataGridTextColumn x:Name="lIST_KINDColumn" Binding="{Binding Path=LIST_KIND}" Header="LIST KIND" Width="SizeToHeader" />
                <sdk:DataGridTextColumn x:Name="lIST_LEVELColumn" Binding="{Binding Path=LIST_LEVEL}" Header="LIST LEVEL" Width="SizeToHeader" />
                <sdk:DataGridCheckBoxColumn x:Name="lIST_OPEN_YNColumn" Binding="{Binding Path=LIST_OPEN_YN}" Header="LIST OPEN YN" Width="SizeToHeader" />
                <sdk:DataGridTextColumn x:Name="lIST_PASSWORDColumn" Binding="{Binding Path=LIST_PASSWORD}" Header="LIST PASSWORD" Width="SizeToHeader" />
                <sdk:DataGridTextColumn x:Name="lIST_PUBLIC_GBColumn" Binding="{Binding Path=LIST_PUBLIC_GB}" Header="LIST PUBLIC GB" Width="SizeToHeader" />
                <sdk:DataGridTextColumn x:Name="lIST_SEQColumn" Binding="{Binding Path=LIST_SEQ}" Header="LIST SEQ" Width="SizeToHeader" />
                <sdk:DataGridTextColumn x:Name="lIST_TITLEColumn" Binding="{Binding Path=LIST_TITLE}" Header="LIST TITLE" Width="SizeToHeader" />
                <sdk:DataGridTextColumn x:Name="lIST_VIEW_COUNTColumn" Binding="{Binding Path=LIST_VIEW_COUNT}" Header="LIST VIEW COUNT" Width="SizeToHeader" />
                <sdk:DataGridTextColumn x:Name="lIST_WRITER_NAMEColumn" Binding="{Binding Path=LIST_WRITER_NAME}" Header="LIST WRITER NAME" Width="SizeToHeader" />
                <sdk:DataGridTemplateColumn x:Name="rEG_DTColumn1" Header="REG DT" Width="SizeToHeader">
                    <sdk:DataGridTemplateColumn.CellEditingTemplate>
                        <DataTemplate>
                            <sdk:DatePicker SelectedDate="{Binding Path=REG_DT, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" />
                        </DataTemplate>
                    </sdk:DataGridTemplateColumn.CellEditingTemplate>
                    <sdk:DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Path=REG_DT, StringFormat=\{0:d\}}" />
                        </DataTemplate>
                    </sdk:DataGridTemplateColumn.CellTemplate>
                </sdk:DataGridTemplateColumn>
                <sdk:DataGridTextColumn x:Name="rEG_IDXColumn1" Binding="{Binding Path=REG_IDX}" Header="REG IDX" Width="SizeToHeader" />
                <sdk:DataGridTemplateColumn x:Name="uPT_DTColumn1" Header="UPT DT" Width="SizeToHeader">
                    <sdk:DataGridTemplateColumn.CellEditingTemplate>
                        <DataTemplate>
                            <sdk:DatePicker SelectedDate="{Binding Path=UPT_DT, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, TargetNullValue=''}" />
                        </DataTemplate>
                    </sdk:DataGridTemplateColumn.CellEditingTemplate>
                    <sdk:DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Path=UPT_DT, StringFormat=\{0:d\}}" />
                        </DataTemplate>
                    </sdk:DataGridTemplateColumn.CellTemplate>
                </sdk:DataGridTemplateColumn>
                <sdk:DataGridTextColumn x:Name="uPT_IDXColumn1" Binding="{Binding Path=UPT_IDX}" Header="UPT IDX" Width="SizeToHeader" />
            </sdk:DataGrid.Columns>
        </sdk:DataGrid>
        <TextBlock HorizontalAlignment="Left" Name="textBlock1" Text="BOARD MAIN :" VerticalAlignment="Center" Margin="8,4,0,3" />
        <Button Content="Insert List" Height="23" HorizontalAlignment="Left" Margin="97,6,0,0" Name="button4" VerticalAlignment="Top" Width="75" Grid.Row="2" Click="button4_Click" />
        <Button Content="Update List" Height="23" HorizontalAlignment="Left" Margin="178,6,0,0" Name="button5" VerticalAlignment="Top" Width="75" Grid.Row="2"  Click="button4_Click"/>
        <Button Content="Delete List" Height="23" HorizontalAlignment="Left" Margin="259,6,0,0" Name="button6" VerticalAlignment="Top" Width="75" Grid.Row="2"  Click="button4_Click"/>
        <TextBlock HorizontalAlignment="Left" Margin="8,10,0,9" Name="textBlock2" Text="BOARD LIST :" VerticalAlignment="Center" Grid.Row="2" />
    </Grid>
</UserControl>


7. MainPage.xaml.cs 전체

using System;
using System.Linq;
using System.ServiceModel.DomainServices.Client;
using System.Windows;
using System.Windows.Controls;
using SL5_BOARD.Web.DomainService;
using SL5_BOARD.Web.Model;

namespace SL5_BOARD
{
    public partial class MainPage : UserControl
    {
        //도메인 서비스를 사용하기 위한 변수 선언
        SL5_BOARDDomainContext dsContext;
        
        public MainPage()
        {
            InitializeComponent();

            //도메인 서비스 인스턴스
            dsContext = new SL5_BOARDDomainContext();
            //LayoutRoot.DataContext에 바인딩..이렇게 하면 하위 컨트롤들의 DataContext에도 모두 함께 바인딩 됨
            LayoutRoot.DataContext = dsContext;
        }

        //BOARD_MAIN INSERT, UPDATE, DELETE
        private void button1_Click(object sender, RoutedEventArgs e)
        {
            Button btn = sender as Button;
            switch (btn.Content.ToString())
            {
                case "Insert":
                    //riacontrol을 빼고 심플하게 정리
                    BOARD_MAIN bm = new BOARD_MAIN();
                    bm.REG_DT = DateTime.Now;
                    bm.REG_IDX = -1;
                    dsContext.BOARD_MAINs.Add(bm);
                    dsContext.SubmitChanges();
                    break;
                case "Update":
                    dsContext.SubmitChanges();
                    break;
                case "Delete":
                    //그리드에 선택된 내용 확인
                    if ((bOARD_MAINDataGrid.SelectedItem as BOARD_MAIN) != null)
                    {
                        dsContext.BOARD_MAINs.Remove(bOARD_MAINDataGrid.SelectedItem as BOARD_MAIN);
                        dsContext.SubmitChanges();
                    }
                    break;
            }
        }

        //BOARD_LIST INSERT, UPDATE, DELETE
        private void button4_Click(object sender, RoutedEventArgs e)
        {
            Button btn = sender as Button;
            switch (btn.Content.ToString())
            {
                case "Insert List":
                    if ((bOARD_MAINDataGrid.SelectedItem as BOARD_MAIN) != null)
                    {
                        BOARD_LIST bl = new BOARD_LIST();
                        dsContext.BOARD_LISTs.Add(bl);
                        bl.REG_DT = DateTime.Now;
                        bl.REG_IDX = -1;

                        //선택된 BOARD_MAIN을 여기다가 쑤셔 넣은 후 저장
                        bl.BoardMain = bOARD_MAINDataGrid.SelectedItem as BOARD_MAIN;

                        dsContext.SubmitChanges();
                    }
                    break;
                case "Update List":
                    dsContext.SubmitChanges();
                    break;
                case "Delete List":
                    if (bOARD_LISTDataGrid.SelectedItem as BOARD_LIST != null)
                    {
                        dsContext.BOARD_LISTs.Remove(bOARD_LISTDataGrid.SelectedItem as BOARD_LIST);
                        dsContext.SubmitChanges();
                    }
                    break;
            }
        }

        //화면 로딩 후 BOARD_MAIN 전체 조회
        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            dsContext.Load(dsContext.GetBoardMainQuery(), LoadBehavior.RefreshCurrent, true);
        }

        //그리드 셀렉션 체인지 이벤트 처리
        private void bOARD_MAINDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            //컨테이너에 데이터가 존재 한다면
            if (dsContext.EntityContainer != null)
            {
                //리스트의 내용을 지워놓고
                dsContext.BOARD_LISTs.Clear();
                //선택된 BOARD_MAIN을 일단 넣궁
                BOARD_MAIN bm = e.AddedItems.Cast<BOARD_MAIN>().FirstOrDefault();
                //BOARD_MAIN에 속해있는 BOARD_LIST만 조회
                dsContext.Load(dsContext.GetBoardListByBoardMainQuery(bm.BOARD_MAIN_IDX), LoadBehavior.RefreshCurrent, true);
            }
        }
    }
}


8. 실행 모습



음..물론 Update, Delete 기능도 잘 동작한다.


9. 나머지 테이블도...
 후딱 만들고 본격적으로 작업에 들어가야 겠다..그런데 역시 Code First라 테이블 만들기가 매우 귀찮은..쿨럭..그냥 Model First에서 모델만들고 한번에 후루룩 만들면 더 쉬울건데..

전체 길이가 너무 길어져서..보기가 좀 불편하지만..거의 모든 소스를 올려 놓은 것이니 다시 잘 만들어 보자. 소스 요청은 리플로 이메일을 남겨주면 된다.

Posted by MVP kaki104