Random Dev Notes

November 3, 2009

IQToolKit Data Provider Repository

Filed under: Development — Tags: , — Tom Brothers @ 1:44 am

Inspired by the GenericRepository found in “ASP.NET MVC Framework Unleashed.”, I decided to write a Repository class to work with the IQToolKit Data Providers.

The first step was to create an interface named IRepository.

using System.Linq;
 
namespace IQToolkitContrib {
    public interface IRepository {
        T Get<T>(object id) where T : class;
        IQueryable<T> List<T>() where T : class;
        void Insert<T>(T entity) where T : class;
        void Update<T>(T entity) where T : class;
        void Delete<T>(T entity) where T : class;
    }
}

The second step was to create an abstract class name ARepository.  This class implements the IRepository interface by creating all the methods as abstract methods.  This class also includes a method CreateGetExpression which returns an Expression that will be used to get an Entity Instance by Primary Key.  The Entity’s Primary Key Property Name will be provided by an abstract method named GetPrimaryKeyPropertyName.

using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
 
namespace IQToolkitContrib {
    public abstract class ARepository : IRepository {
        protected Expression<Func<T, bool>> CreateGetExpression<T>(object id) {
            ParameterExpression e = Expression.Parameter(typeof(T), "e");
            PropertyInfo pi = typeof(T).GetProperty(this.GetPrimaryKeyPropertyName<T>());
            MemberExpression m = Expression.MakeMemberAccess(e, pi);
            ConstantExpression c = Expression.Constant(id, id.GetType());
            BinaryExpression b = Expression.Equal(m, c);
            Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(b, e);
            return lambda;
        }
 
        protected abstract string GetPrimaryKeyPropertyName<T>();
        public abstract T Get<T>(object id) where T : class;
        public abstract IQueryable<T> List<T>() where T : class;
        public abstract void Insert<T>(T entity) where T : class;
        public abstract void Update<T>(T entity) where T : class;
        public abstract void Delete<T>(T entity) where T : class;
    }
}

The third step was to create a class named DbEntityRepository.  This class inherits from the ARepository class by overriding the abstract methods with IQToolKit Data Provider specific code.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using IQToolkit;
using IQToolkit.Data.Common;
 
namespace IQToolkitContrib {
    public class DbEntityRepository : ARepository {
        public DbEntityProviderBase Provider { get; private set; }
 
        public DbEntityRepository(DbEntityProviderBase provider) {
            this.Provider = provider;
        }
 
        protected override string GetPrimaryKeyPropertyName<T>() {
            MappingEntity mappingEntity = this.Provider.Mapping.GetEntity(typeof(T));
            List<MemberInfo> memberInfoList = this.Provider.Mapping.GetPrimaryKeyMembers(mappingEntity).ToList();
 
            if (memberInfoList.Count != 1) {
                throw new ApplicationException(string.Format("Cannot determine the primary key for {0}", typeof(T)));
            }
 
            MemberInfo primaryKeyMemberInfo = memberInfoList[0];
            return primaryKeyMemberInfo.Name;
        }
 
        public override T Get<T>(object id) {
            //// The "FirstOrDefault" will not work with LINQtoVFP.  The following statement would cause an error because it does not include an OrderBy.
            //// return this.List<T>().FirstOrDefault(this.CreateGetExpression<T>(id, primaryKeyMemberInfo.Name));   
 
            List<T> list = this.List<T>().Where<T>(this.CreateGetExpression<T>(id)).ToList();
 
            if (list.Count == 0) {
                return default(T);
            }
 
            return list[0];
        }
        
        public override IQueryable<T> List<T>() {
            return this.GetEntityTable<T>();
        }
 
        public override void Insert<T>(T entity) {
            if (entity is IValidate) {
                ((IValidate)entity).Validate();
            }
 
            this.GetEntityTable<T>().Insert<T>(entity);
        }
        
        public override void Update<T>(T entity) {
            if (entity is IValidate) {
                ((IValidate)entity).Validate();
            }
 
            this.GetEntityTable<T>().Update<T>(entity);
        }
 
        public override void Delete<T>(T entity) {
            this.GetEntityTable<T>().Delete<T>(entity);
        }
 
        private IEntityTable<T> GetEntityTable<T>() {
            return this.Provider.GetTable<T>(this.Provider.Mapping.GetTableId(typeof(T)));
        }
    }
}


At this point, I have developed a very simple Repository to work with the IQToolKit Providers.  This Repository in itself has a few uses but I believe that it would be more useful (at lease for the purpose of TDD) if I added one more layer of abstraction.

So for the fourth step, I created class named DataContext.  This class implements the IRepository interface and has a constructor that is passed an IRepository Instance.  The purpose of this class is to simply wrap up the calls to the IRepository Instance.

(It helps to think of the IRepository Instance as the Data Access Layer and the DataContext as the Business Logic Layer.)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace IQToolkitContrib {
    public class DataContext : IRepository {
        private IRepository repository;
        
        public DataContext(IRepository repository) {
            this.repository = repository;
        }
 
        public T Get<T>(object id) where T : class {
            return this.repository.Get<T>(id);
        }
 
        public IQueryable<T> List<T>() where T : class {
            return this.repository.List<T>();
        }
 
        public void Insert<T>(T entity) where T : class {
            this.repository.Insert<T>(entity);
        }
 
        public void Update<T>(T entity) where T : class {
            this.repository.Update<T>(entity);
        }
 
        public void Delete<T>(T entity) where T : class {
            this.repository.Delete<T>(entity);
        }
    }
}

In the final step, I created a class named MemoryRepository.  This class inherits from the ARepository class by overriding the abstract methods to work with a List<object> Instance.  This class can be used to pass into the DataContext while creating tests using TDD.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
 
namespace IQToolkitContrib {
    public partial class MemoryRepository : ARepository {
        private List<object> entities = new List<object>();
 
        protected override string GetPrimaryKeyPropertyName<T>() {
            return Path.GetExtension(typeof(T).FullName).Substring(1) + "Id";
        }
 
        protected virtual void SetIdentityValue<T>(T entity) where T : class {
            PropertyInfo pi = typeof(T).GetProperty(this.GetPrimaryKeyPropertyName<T>());
 
            switch (pi.PropertyType.FullName) {
                case "System.Int32":
                case "System.Int64":
                    long id = Convert.ToInt64(pi.GetValue(entity, null));
 
                    if (id <= 0) {
                        pi.SetValue(entity, this.List<T>().Count() + 1, null);
                    }
 
                    break;
                case "System.String":
                    if (string.IsNullOrEmpty(pi.GetValue(entity, null) as string)) {
                        pi.SetValue(entity, (this.List<T>().Count() + 1).ToString(), null);
                    }
 
                    break;
                case "System.Guid":
                    Guid guidId = (Guid)pi.GetValue(entity, null);
 
                    if (guidId == default(Guid)) {
                        pi.SetValue(entity, Guid.NewGuid(), null);
                    }
 
                    break;
                default:
                    throw new NotImplementedException(string.Format("PropertyType {0} not handled.", pi.PropertyType.FullName));
            }
        }
 
        public override T Get<T>(object id) {
            return this.List<T>().FirstOrDefault(this.CreateGetExpression<T>(id));
        }
 
        public override IQueryable<T> List<T>() {
            return this.entities.OfType<T>().AsQueryable();
        }
 
        public override void Insert<T>(T entity) {
            if (entity is IValidate) {
                ((IValidate)entity).Validate();
            }
 
            this.SetIdentityValue<T>(entity);
            this.entities.Add(entity);
        }
 
        public override void Update<T>(T entity) {
            if (entity is IValidate) {
                ((IValidate)entity).Validate();
            }
 
            string primaryKeyPropertyName = this.GetPrimaryKeyPropertyName<T>();
            PropertyInfo pi = typeof(T).GetProperty(primaryKeyPropertyName);
            object id = pi.GetValue(entity, null);
            T originalEntity = this.Get<T>(id);
 
            var properties = typeof(T).GetProperties();
 
            foreach (var prop in properties) {
                if (prop.CanWrite && prop.Name != primaryKeyPropertyName) {
                    var value = prop.GetValue(entity, null);
                    prop.SetValue(originalEntity, value, null);
                }
            }
        }
 
        public override void Delete<T>(T entity) {
            this.entities.Remove(entity);
        }
    }
}


Source Code and Binaries can be found at IQToolkit Contrib.

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: