티스토리 뷰

반응형

MVC4에서 WebAPI를 이용해서 CRUD가 가능하다는 부분은 이전 포스트에서 다루었다. 이번에는 WebAPI를 OData Endpoint로 변경해서 WCF DataService로 사용하는 방법에 대해서 살펴 보려고 한다.

 

1. 기본 참고 포스트

Creating an OData Endpoint in ASP.NET Web API

http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/creating-an-odata-endpoint

 

Requirements

This tutorial uses Visual Studio 2013.

Web API OData support was added in ASP.NET and Web Tools 2012.2 Update. However, this tutorial uses scaffolding that was added in Visual Studio 2013.

 

2. WebAPI vs WCF DataService

A New Option for Creating OData: Web API

http://msdn.microsoft.com/en-us/magazine/dn201742.aspx

데이터 서비스는 국내에서는 잘 사용되고 있지 않은 서비스 중에 하나라고 생각한다. 그런데도 MS에서는 WebAPI를 WCF DataService를 이용해서 OData Endpoint를 관리하도록 만들어 놓았다.

장점과 단점이 있지만 판단은 사용자가 하는 것이니 사용해 보고 판단하면 될 것 같다.

 

3. 셈플 프로젝트 구성

이전에 PCLSample 프로젝트를 확장했다.

PCLSample.DAL : Data Access Layer

PCLSample.PCL : Portable Class Library

PCLSample.SL5 : Silverlight 5

PCLSample.W8 : Windows 8 store app

PCLSample.Web : MVC4, Silverlight host, DataService host

PCLSample.WPF : WPF

--Windows Phone 7.1 프로젝트는 제외했다.(PCL 구성을 Windows Phone 8이상으로 변경했기 때문이다.)

 

4. Base

EntityFramework - Code-First

Repository Pattern

MVVM

Portable Class Library

WebAPI

IoC (Inversion of control) : http://en.wikipedia.org/wiki/Inversion_of_control

 

5. 셈플에 구현된 내용

이번 포스트에서는 하나의 DataService를 만들어 놓고, 각 플랫폼에서 조회하는 부분까지만 구현되어 있다.

Model : PersonModel

Mapping : PersonModelMap.cs , PCL 프로젝트에 있는 PersonModel을 Code-First로 MSSQL과 연동하도록 만듬

Repository : GenericRepository.cs, 기본적인 레파지토리

UnitOfWork : UnitOfWork.cs, 전체 레파지토리 관리

DbContext : PCLSampleContext.cs

 

6. PCLSample.DAL

.Net Framework 4.5, Class Library

 

Nuget packages

Microsoft.Bcl.Async  //모든 프로젝트에 공통으로 포함된다.

Microsoft.Bcl.Build    //모든 프로젝트에 공통으로 포함된다.

Microsoft.Bcl            //모든 프로젝트에 공통으로 포함된다.

Microsoft.Net.Http     //모든 프로젝트에 공통으로 포함된다.

EntityFramework

 

6-1. PCLSampleContext

EntityFramework를 Code-First로 사용하기 위한 기본 클래스

 

    /// <summary>
    /// DbContext
    /// </summary>
    public class PCLSampleContext : DbContext
    {
        /// <summary>
        /// 생성자
        /// </summary>
        public PCLSampleContext()
            : base("name=PCLSampleContext")
        {
        }

        /// <summary>
        /// People Table
        /// </summary>
        public DbSet<PersonModel> People { get; set; }

        /// <summary>
        /// OnModelCreating
        /// </summary>
        /// <param name="modelBuilder"></param>
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Configurations.Add(new PersonModelMap());
            base.OnModelCreating(modelBuilder);
        }
    }

 

6-2. PCLSampleContextInitializer

모델 생성시 초기화 모듈 - 여기서는 Seed값 입력

 

    public class PCLSampleContextInitializer : System.Data.Entity.DropCreateDatabaseIfModelChanges<PCLSampleContext>
    {
        /// <summary>
        /// Seed
        /// </summary>
        /// <param name="context"></param>
        protected override void Seed(PCLSampleContext context)
        {
            var people = new List<PersonModel>
                {
                    new PersonModel{ Id = 0, Name = "kaki104", Age = 20, Sex = true },
                    new PersonModel{ Id = 1, Name = "kaki105", Age = 21, Sex = false },
                    new PersonModel{ Id = 2, Name = "kaki106", Age = 22, Sex = true },
                    new PersonModel{ Id = 3, Name = "kaki107", Age = 23, Sex = false },
                    new PersonModel{ Id = 4, Name = "kaki108", Age = 24, Sex = true },
                };
            people.ForEach(p => context.People.Add(p));

            context.SaveChanges();

            base.Seed(context);
        }
    }

6-3. PersonModelMap

PersonModel의 맵핑 클래스

 

    /// <summary>
    /// PersonModel Mapping class
    /// </summary>
    class PersonModelMap : System.Data.Entity.ModelConfiguration.EntityTypeConfiguration<PersonModel>
    {
        public PersonModelMap()
        {
            this.HasKey(f => f.Id);

            this.ToTable("People");
            this.Property(f => f.Id)
                .IsRequired()
                .HasColumnName("Id");
            this.Property(f => f.Name)
                .HasMaxLength(50)
                .HasColumnName("Name");
            this.Property(f => f.Age)
                .HasColumnName("Age");
            this.Property(f => f.Sex)
                .HasColumnName("Sex");
        }
    }

 

6-4. GenericRepository

기본 레파지토리 클래스

 

public class GenericRepository<TEntity> : PCLSample.DAL.Repositories.IGenericRepository<TEntity> where TEntity : class
    {
        /// <summary>
        /// DbContext
        /// </summary>
        private DbContext _context;
        /// <summary>
        /// IDbSet
        /// </summary>
        private readonly IDbSet<TEntity> _dbSet;

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="context"></param>
        public GenericRepository(DbContext context)
        {
            _context = context;
            _dbSet = _context.Set<TEntity>();
        }

        /// <summary>
        /// 전체 조회 - WebAPI에서 사용
        /// </summary>
        /// <returns></returns>
        public virtual IQueryable<TEntity> AllGetting()
        {
            return _dbSet.AsQueryable();
        }

        /// <summary>
        /// 조회
        /// </summary>
        /// <param name="filter">조회조건</param>
        /// <param name="orderBy">정렬조건</param>
        /// <param name="includeProperties">Include속성</param>
        /// <returns></returns>
        public virtual IQueryable<TEntity> Getting(
            System.Linq.Expressions.Expression<Func<TEntity, bool>> filter = null,
            Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
            string includeProperties = "",
            int? page = null,
            int? pageSize = null)
        {
            IQueryable<TEntity> query = _dbSet.AsQueryable();

            //조건절이 있으면 추가
            if (filter != null)
            {
                query = query.Where(filter);
            }

            //Include가 있으면 추가
            foreach (var includeProperty in includeProperties.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
            {
                query = query.Include(includeProperty);
            }

            //정렬값이 있으면 정렬하고 아니면 그냥 보내고..
            if (orderBy != null)
            {
                orderBy(query).AsQueryable();
            }

            if (page != null && pageSize != null)
            {
                int pageNo = Convert.ToInt32(page) - 1;
                int pSize = Convert.ToInt32(pageSize);

                if (pageSize <= 0)
                {
                    pageSize = 10; // default
                }

                if (pageNo <= 0)
                {
                    pageNo = 0;
                }

                int skipSize = pageNo * pSize;

                return query.Skip(skipSize).Take(pSize).AsQueryable();
            }
            else
            {
                return query.AsQueryable();
            }
        }

        /// <summary>
        /// 키값을 가지고 조회
        /// </summary>
        /// <param name="id">키값</param>
        /// <returns></returns>
        public virtual TEntity GettingByID(object id)
        {
            return _dbSet.Find(id);
        }

        /// <summary>
        /// Entity를 받아와서 저장
        /// </summary>
        /// <param name="entity"></param>
        public virtual void Insert(TEntity entity)
        {
            _dbSet.Add(entity);
        }

        /// <summary>
        /// 키값으로 Entity를 찾아서 삭제
        /// </summary>
        /// <param name="id"></param>
        public virtual void Delete(object id)
        {
            TEntity entityToDelete = _dbSet.Find(id);
            Delete(entityToDelete);
        }

        /// <summary>
        /// Entity를 받아서 삭제
        /// </summary>
        /// <param name="entityToDelete"></param>
        public virtual void Delete(TEntity entityToDelete)
        {
            //DB에 없는 Entity를 삭제할 경우 DB에 추가한 후에 삭제
            if (_context.Entry(entityToDelete).State == EntityState.Detached)
            {
                _dbSet.Attach(entityToDelete);
            }
            _dbSet.Remove(entityToDelete);
        }

        /// <summary>
        /// Entity를 받아서 수정
        /// </summary>
        /// <param name="entityToUpdate"></param>
        public virtual void Update(TEntity entityToUpdate)
        {
            //DB에 Entity를 등록
            _dbSet.Attach(entityToUpdate);

            //context에 Entity를 등록한 후 상태를 Modified로 변경
            _context.Entry(entityToUpdate).State = EntityState.Modified;
        }

        public virtual void Detach(TEntity entityToDetach)
        {
            _context.Entry(entityToDetach).State = EntityState.Detached;
        }

        #region IDisposable
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!disposing) return;
            if (_context == null) return;
            _context.Dispose();
            _context = null;
        }
        #endregion

    }
}

 

6-5. UnitOfWork

UnitOfWork 클래스 - 이 클래스를 통해 각 테이블에 접근

TransactionScope을 사용하기 위해서는 System.Transactions 러퍼런스를 추가해 주어야함

 

    public class UnitOfWork : IDisposable, PCLSample.DAL.Repositories.IUnitOfWork
    {
        /// <summary>
        /// 트렌젝션
        /// </summary>
        private TransactionScope _tx;
        /// <summary>
        /// KTour Context
        /// </summary>
        private DbContext _context;

        private IGenericRepository<PersonModel> _peopleRepo;
        /// <summary>
        /// PersonModel Repository
        /// </summary>
        public IGenericRepository<PersonModel> PeopleRepo
        {
            get { return _peopleRepo = _peopleRepo ?? new GenericRepository<PersonModel>(_context); }
        }

        /// <summary>
        /// 생성자
        /// </summary>
        public UnitOfWork()
        {
            _context = new PCLSampleContext();
        }

        /// <summary>
        /// 저장
        /// </summary>
        /// <returns></returns>
        public bool Save()
        {
            try
            {
                //저장~~~~~~~~~~~~~~~
                _context.SaveChanges();
                return true;
            }
            catch (DbEntityValidationException dbEx)
            {
                foreach (var validationErrors in dbEx.EntityValidationErrors)
                {
                    foreach (var validationError in validationErrors.ValidationErrors)
                    {
                        System.Diagnostics.Trace.TraceInformation("Class: {0}, Property: {1}, Error: {2}", validationErrors.Entry.Entity.GetType().FullName,
                                      validationError.PropertyName, validationError.ErrorMessage);
                    }
                }
                return false;
            }
        }

        /// <summary>
        /// Begins the transaction.
        /// </summary>
        public void BeginTransaction()
        {
            try
            {
                this._tx = new TransactionScope();
            }
            catch (Exception ex)
            {
                throw new Exception(string.Format("An error occured during the Begin transaction.\r\n{0}", ex.Message));
            }
        }

        /// <summary>
        /// Commits the transaction.
        /// </summary>
        public void CommitTransaction()
        {
            try
            {
                if (this._tx == null)
                {
                    throw new TransactionException("The current transaction is not started!");
                }
                this._tx.Complete();
            }
            catch (Exception ex)
            {
                throw new Exception(string.Format("An error occured during the Commit transaction.\r\n{0}", ex.Message));
            }
            finally
            {
                this._tx.Dispose();
            }
        }

        /// <summary>
        /// Rollbacks the transaction.
        /// </summary>
        public void RollbackTransaction()
        {
            try
            {
                if (this._tx != null)
                {
                    this._tx.Dispose();
                }
            }
            catch (Exception ex)
            {
                throw new Exception(string.Format("An error occured during the Rollback transaction.\r\n{0}", ex.Message));
            }
        }


        #region ///Dispose

        private bool disposed = false;

        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    if (_tx != null)
                        _tx.Dispose();
                    _context.Dispose();
                }
            }
            this.disposed = true;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion
    }

이렇게 구성하면 DAL 프로젝트가 구성되며, Database와 연결하기 위해서

App.config 파일에 ConnectionString을 추가해 준다.

 

  <connectionStrings>
    <add name="PCLSampleContext" connectionString="Data Source=(local)\sqlexpress;Initial Catalog=PCLSample;Integrated Security=SSPI" providerName="System.Data.SqlClient" />
  </connectionStrings>

 

ConnectionString은 각자 컴퓨터의 환경에 맞게 지정한다.

 

반응형
댓글