using System; using System.Collections.Generic; using System.Data; using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Data.Entity.Core.Metadata.Edm; using System.Data.Entity.Core.Objects; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; namespace Neo.Afx.ComponentModel { /// /// Ensures that when multiple repositories are used, they share a single database context. /// /// The type of the database context. public abstract class Database : IDisposable where TContext : DbContext, new() { /// /// The new record identifier. /// public readonly long NewID = -1; #region Initialization /// /// Initializes a new instance of the class. /// protected Database() : this(Activator.CreateInstance()) { } /// /// Initializes a new instance of the class. /// /// The context to be used. protected Database(TContext context) : this(context, null) { } /// /// Initializes a new instance of the class. /// /// The context to be used. /// The timeout value, in seconds, for all object context operations. /// A null value indicates that the default value of the underlying provider will be used. /// A zero value indicates an infinite timeout. protected Database(TContext context, int? commandTimeout) { Context = context; ProxyCreationEnabled = false; if(commandTimeout.HasValue) { // Get the ObjectContext related to this DbContext var objectContext = (context as IObjectContextAdapter).ObjectContext; objectContext.CommandTimeout = commandTimeout.Value; } } #endregion #region Context /// /// Gets the context for this instance. /// public TContext Context { get; private set; } #endregion #region ContextStateManager /// /// Gets the object state manager. /// /// /// The object state manager. /// public ObjectStateManager ContextStateManager { get { return ((IObjectContextAdapter)Context).ObjectContext.ObjectStateManager; } } #endregion #region ProxyCreationEnabled /// /// Gets or sets a value indicating whether proxy creation is enabled. /// If false, this will turn OFF lazy loading on all entities but will allow for json serialization!! ///

Note: When creating instances of POCO entity types, the Entity Framework creates /// instances of a dynamically generated derived type that acts as a proxy for /// the entity which can create complications when serializing proxy instances /// http://blogs.msdn.com/b/adonet/archive/2011/02/02/using-dbcontext-in-ef-feature-ctp5-part-8-working-with-proxies.aspx ///

///
///
/// /// true if proxy creation is enabled; false otherwise. Default is false. /// public bool ProxyCreationEnabled { get { return Context.Configuration.ProxyCreationEnabled; } set { Context.Configuration.ProxyCreationEnabled = value; } } #endregion #region Save /// /// Saves all changes made to the . /// public void Save() { try { Context.SaveChanges(); } catch(Exception ex) { // Log it Trace.WriteLine(string.Format("{0:yyyy-MM-dd HH:mm:ss} DATABASE ERROR --> {1}", DateTime.Now, ex.ToStringComplete())); // Re-throw preserving stack trace throw; } } public void Save(TEntity entity) where TEntity : class { var ctxSet = GetDbSet(); var fromEntityCtx = Context.Entry(entity); var keyMemebers = GetKeyMembers(entity); var ctxEntity = Context.Entry((GetByID(fromEntityCtx.Property(keyMemebers[0]).CurrentValue) ?? ctxSet.Add(ctxSet.Create()))); ctxEntity.CurrentValues.SetValues(entity); Context.SaveChanges(); } #endregion #region SaveEntity /// /// Saves all changes made to the current entity excluding related tables. /// public void SaveEntity(dynamic value, dynamic primaryId) { var entityType = value.GetType(); var entityState = System.Data.Entity.EntityState.Modified; DbEntityEntry valEntity = Context.Entry(value); if(valEntity == null) { throw new Exception("Error occured while accessing current entry."); } try { object dbEntity = Context.Set(entityType).Find(primaryId); if(dbEntity == null) { entityState = EntityState.Added; dbEntity = Context.Set(entityType).Create(); Context.Set(entityType).Add(dbEntity); } var entry = Context.Entry(dbEntity); foreach(var propName in entry.CurrentValues.PropertyNames) { entry.Property(propName).CurrentValue = valEntity.Property(propName).CurrentValue; } entry.State = entityState; Context.SaveChanges(); } catch(Exception ex) { // Log it Trace.WriteLine(string.Format("{0:yyyy-MM-dd HH:mm:ss} DATABASE ENTITY ERROR --> {1}", DateTime.Now, (ex.Message.ToLower().Contains("inner exception") ? ex.InnerException.Message : ex.Message))); // Re-throw preserving stack trace throw; } } #endregion #region GetByID /// /// Gets the entity with the given ID. /// /// The type of entity to be returned. /// The id of the entity. /// The entity with the given ID. public TEntity GetByID(object id) where TEntity : class { return GetDbSet().Find(id); } #endregion #region GetKeyMembers /// /// Gets the key members. /// /// The type of the entity. /// The entity. /// public string[] GetKeyMembers(TEntity entity) where TEntity : class { var entityName = entity.GetType().Name; var items = ContextStateManager.MetadataWorkspace.GetItems(DataSpace.CSpace); if(items != null && items.Count > 0) { return items.Where(e => e.FullName.Equals("Model." + entityName)).SelectMany(e => e.KeyMembers).Select(e => e.Name).ToArray(); } return null; } #endregion #region Get /// /// Gets a list of entities that match the given filter. /// /// The type of entity to be returned. /// The filter to be used. /// The order to be used. /// The properties to be included. /// A list of entities that match the given filter. public IEnumerable Get(Expression> filter = null, Func, IOrderedQueryable> orderBy = null, string includeProperties = "") where TEntity : class { IQueryable query = GetDbSet(); if(filter != null) { query = query.Where(filter); } query = includeProperties.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Aggregate(query, (current, includeProperty) => current.Include(includeProperty)); return orderBy != null ? orderBy(query).ToList() : query.ToList(); } #endregion #region Insert /// /// Inserts the specified entity. /// /// The type of the entity. /// The entity to be inserted. public void Insert(TEntity entity) where TEntity : class { ChangeToUpperCase(entity); GetDbSet().Add(entity); } /// /// Inserts the specified entity. /// /// The type of the entity. /// The entity to be inserted. /// Indicates if string text should be converted to uppercase. public void Insert(TEntity entity, bool changeToUppercase) where TEntity : class { if (changeToUppercase) { ChangeToUpperCase(entity); } GetDbSet().Add(entity); } #endregion #region Update /// /// Updates the specified entity. /// /// The type of the entity. /// The entity to be updated. public void Update(TEntity entityToUpdate) where TEntity : class { ChangeToUpperCase(entityToUpdate); GetDbSet().Attach(entityToUpdate); Context.Entry(entityToUpdate).State = System.Data.Entity.EntityState.Modified; } /// /// Updates the specified entity. /// /// The type of the entity. /// The entity to be updated. /// Indicates if string text should be converted to uppercase. public void Update(TEntity entityToUpdate, bool changeToUppercase) where TEntity : class { if (changeToUppercase) { ChangeToUpperCase(entityToUpdate); } GetDbSet().Attach(entityToUpdate); Context.Entry(entityToUpdate).State = System.Data.Entity.EntityState.Modified; } #endregion #region Attach /// /// Attaches the specified entity. /// /// The type of the entity. /// The entity to be attached. public void Attach(TEntity entity) where TEntity : class { if(Context.Entry(entity).State == EntityState.Detached) { GetDbSet().Attach(entity); } } #endregion #region GetDbSet DbSet GetDbSet() where TEntity : class { return Context.Set(); } #endregion #region IDisposable Members private bool _disposed; /// /// Releases unmanaged and - optionally - managed resources /// /// /// true to release both managed and unmanaged resources; /// false to release only unmanaged resources. /// protected virtual void Dispose(bool disposing) { if(!_disposed) { if(disposing) { Context.Dispose(); } } _disposed = true; } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases unmanaged resources and performs other cleanup operations before the /// is reclaimed by garbage collection. /// ~Database() { Dispose(false); } #endregion #region ChangeToUpperCase /// /// Changes all the string variables to uppercase /// /// /// public void ChangeToUpperCase(T entityObject) { var type = entityObject.GetType().ToString(); PropertyInfo[] properties = typeof(T).GetProperties(); List changes = new List(); foreach (PropertyInfo pi in properties) { object value = typeof(T).GetProperty(pi.Name).GetValue(entityObject, null); if (pi.PropertyType == typeof(string) && value != null) { var newValue = Convert.ToString(value).ToUpper(); pi.SetValue(entityObject, newValue, null); } } } #endregion } }