diff --git a/src/Umbraco.Core/Persistence/Caching/IRepositoryCacheProvider.cs b/src/Umbraco.Core/Persistence/Caching/IRepositoryCacheProvider.cs new file mode 100644 index 0000000000..d65a819871 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Caching/IRepositoryCacheProvider.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Persistence.Caching +{ + /// + /// Defines the implementation of a Cache Provider intented to back a repository + /// + internal interface IRepositoryCacheProvider + { + /// + /// Gets an Entity from the cache by Type and Id + /// + /// + /// + /// + IEntity GetById(Type type, Guid id); + + /// + /// Gets an Entity from the cache by Type and Ids + /// + /// + /// + /// + IEnumerable GetByIds(Type type, List ids); + + /// + /// Gets all Entities of specified type + /// + /// + /// + IEnumerable GetAllByType(Type type); + + /// + /// Saves the Entity + /// + /// + /// + void Save(Type type, IEntity entity); + + /// + /// Deletes the Entity from the cache + /// + /// + /// + void Delete(Type type, IEntity entity); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Caching/InMemoryCacheProvider.cs b/src/Umbraco.Core/Persistence/Caching/InMemoryCacheProvider.cs new file mode 100644 index 0000000000..b6f763fb36 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Caching/InMemoryCacheProvider.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Persistence.Caching +{ + /// + /// The InMemory registry looks up objects in an in-memory dictionary for fast retrival + /// + internal class InMemoryCacheProvider : IRepositoryCacheProvider + { + #region Singleton + private static volatile InMemoryCacheProvider _instance; + private static readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim(); + + private InMemoryCacheProvider() { } + + public static InMemoryCacheProvider Current + { + get + { + using (new WriteLock(Lock)) + { + if (_instance == null) _instance = new InMemoryCacheProvider(); + } + + return _instance; + } + } + #endregion + + private readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); + + /// + /// Retrives an object of the specified type by its Id + /// + /// The type of the object to retrive, which implements + /// The Guid Id of the Object to retrive + /// + public IEntity GetById(Type type, Guid id) + { + var compositeKey = GetCompositeId(type, id); + var containsKey = _cache.ContainsKey(compositeKey); + if (containsKey) + { + return _cache[compositeKey]; + } + + return null; + } + + /// + /// Retrives objects of the specified type by their Ids + /// + /// The type of the objects to retrive, which implements + /// The Guid Ids of the Objects to retrive + /// + public IEnumerable GetByIds(Type type, List ids) + { + var list = (from id in ids + select GetCompositeId(type, id) + into key + let containsKey = _cache.ContainsKey(key) + where containsKey + select _cache[key]).ToList(); + return list; + } + + /// + /// Retrives all objects of the specified type + /// + /// The type of the objects to retrive, which implements + /// + public IEnumerable GetAllByType(Type type) + { + var list = _cache.Keys.Where(key => key.Contains(type.Name)).Select(key => _cache[key]).ToList(); + return list; + } + + /// + /// Saves an object in the registry cache + /// + /// + /// + public void Save(Type type, IEntity entity) + { + _cache.AddOrUpdate(GetCompositeId(type, entity.Key), entity, (x, y) => entity); + } + + /// + /// Deletes an object from the registry cache + /// + /// + /// + public void Delete(Type type, IEntity entity) + { + IEntity entity1; + bool result = _cache.TryRemove(GetCompositeId(type, entity.Key), out entity1); + } + + public void Clear() + { + _cache.Clear(); + } + + private string GetCompositeId(Type type, Guid id) + { + return string.Format("{0}-{1}", type.Name, id.ToString()); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Caching/RuntimeCacheProvider.cs b/src/Umbraco.Core/Persistence/Caching/RuntimeCacheProvider.cs new file mode 100644 index 0000000000..35a735953d --- /dev/null +++ b/src/Umbraco.Core/Persistence/Caching/RuntimeCacheProvider.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Runtime.Caching; +using System.Threading; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Persistence.Caching +{ + /// + /// The Runtime Cache provider looks up objects in the Runtime cache for fast retrival + /// + internal sealed class RuntimeCacheProvider : IRepositoryCacheProvider + { + #region Singleton + private static volatile RuntimeCacheProvider _instance; + private static readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim(); + + private RuntimeCacheProvider() { } + + public static RuntimeCacheProvider Current + { + get + { + using (new WriteLock(Lock)) + { + if (_instance == null) _instance = new RuntimeCacheProvider(); + } + + return _instance; + } + } + #endregion + + private ObjectCache _memoryCache = new MemoryCache("in-memory"); + private ConcurrentDictionary _keyTracker = new ConcurrentDictionary(); + + public IEntity GetById(Type type, Guid id) + { + var key = GetCompositeId(type, id); + var item = _memoryCache.Get(key); + return item as IEntity; + } + + public IEnumerable GetByIds(Type type, List ids) + { + foreach (var guid in ids) + { + yield return _memoryCache.Get(GetCompositeId(type, guid)) as IEntity; + } + } + + public IEnumerable GetAllByType(Type type) + { + foreach (var key in _keyTracker.Keys) + { + if (key.StartsWith(type.Name)) + { + yield return _memoryCache.Get(key) as IEntity; + } + } + } + + public void Save(Type type, IEntity entity) + { + var key = GetCompositeId(type, entity.Key); + var exists = _memoryCache.GetCacheItem(key) != null; + + _keyTracker.TryAdd(key, key); + if (exists) + { + _memoryCache.Set(key, entity, new CacheItemPolicy { SlidingExpiration = TimeSpan.FromMinutes(5) }); + return; + } + + _memoryCache.Add(key, entity, new CacheItemPolicy { SlidingExpiration = TimeSpan.FromMinutes(5) }); + } + + public void Delete(Type type, IEntity entity) + { + string throwaway = null; + var key = GetCompositeId(type, entity.Key); + var keyBeSure = _keyTracker.TryGetValue(key, out throwaway); + object itemRemoved = _memoryCache.Remove(key); + _keyTracker.TryRemove(key, out throwaway); + } + + private string GetCompositeId(Type type, Guid id) + { + return string.Format("{0}-{1}", type.Name, id.ToString()); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs new file mode 100644 index 0000000000..f336f8330c --- /dev/null +++ b/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Factories +{ + internal class ContentFactory + { + internal static Content CreateContent(int key, IContentType contentType, DocumentDto documentDto, IEnumerable propertyDataDtos) + { + var properties = new List(); + foreach (var dto in propertyDataDtos) + { + var propertyType = + contentType.CompositionPropertyTypes.FirstOrDefault(x => x.Id == dto.PropertyTypeId); + properties.Add(propertyType.CreatePropertyFromRawValue(dto.GetValue)); + } + + return new Content(documentDto.ContentVersionDto.ContentDto.NodeDto.ParentId, contentType) + { + Id = key, + Key = documentDto.ContentVersionDto.ContentDto.NodeDto.UniqueId.HasValue ? documentDto.ContentVersionDto.ContentDto.NodeDto.UniqueId.Value : key.ToGuid(), + Name = documentDto.ContentVersionDto.ContentDto.NodeDto.Text, + Path = documentDto.ContentVersionDto.ContentDto.NodeDto.Path, + UserId = documentDto.ContentVersionDto.ContentDto.NodeDto.UserId.HasValue ? documentDto.ContentVersionDto.ContentDto.NodeDto.UserId.Value : documentDto.UserId, + Level = documentDto.ContentVersionDto.ContentDto.NodeDto.Level, + ParentId = documentDto.ContentVersionDto.ContentDto.NodeDto.ParentId, + SortOrder = documentDto.ContentVersionDto.ContentDto.NodeDto.SortOrder, + Trashed = documentDto.ContentVersionDto.ContentDto.NodeDto.Trashed, + Published = documentDto.Published, + CreatedDate = documentDto.ContentVersionDto.ContentDto.NodeDto.CreateDate, + ModifiedDate = documentDto.ContentVersionDto.VersionDate, + ExpireDate = documentDto.ExpiresDate, + ReleaseDate = documentDto.ReleaseDate, + Version = documentDto.ContentVersionDto.VersionId, + Properties = new PropertyCollection(properties) + }; + } + + internal static NodeDto CreateNodeDto(IContent entity, string nodeObjectType) + { + var nodeDto = new NodeDto + { + CreateDate = entity.CreatedDate, + NodeId = entity.Id, + Level = short.Parse(entity.Level.ToString(CultureInfo.InvariantCulture)), + NodeObjectType = new Guid(nodeObjectType), + ParentId = entity.ParentId, + Path = entity.Path, + SortOrder = entity.SortOrder, + Text = entity.Name, + Trashed = entity.Trashed, + UniqueId = entity.Key, + UserId = entity.UserId + }; + + return nodeDto; + } + + internal static NodeDto CreateNodeDto(IContent entity, string nodeObjectType, string path, int level, int sortOrder) + { + var nodeDto = new NodeDto + { + CreateDate = entity.CreatedDate, + NodeId = entity.Id, + Level = short.Parse(level.ToString(CultureInfo.InvariantCulture)), + NodeObjectType = new Guid(nodeObjectType), + ParentId = entity.ParentId, + Path = path, + SortOrder = sortOrder, + Text = entity.Name, + Trashed = entity.Trashed, + UniqueId = entity.Key, + UserId = entity.UserId + }; + + return nodeDto; + } + + internal static ContentDto CreateContentDto(IContent entity, int primaryKey = 0) + { + var contentDto = new ContentDto + { + NodeId = entity.Id, + ContentType = entity.ContentTypeId + }; + + if (primaryKey > 0) + { + contentDto.PrimaryKey = primaryKey; + } + + return contentDto; + } + + internal static ContentVersionDto CreateContentVersionDto(IContent entity) + { + var contentVersionDto = new ContentVersionDto + { + NodeId = entity.Id, + VersionDate = entity.ModifiedDate, + VersionId = entity.Version + }; + return contentVersionDto; + } + + internal static DocumentDto CreateDocumentDto(IContent entity) + { + //NOTE Currently doesn't add Alias and templateId (legacy stuff that eventually will go away) + var documentDto = new DocumentDto + { + ExpiresDate = entity.ExpireDate, + Newest = true, + NodeId = entity.Id, + Published = entity.Published, + ReleaseDate = entity.ReleaseDate, + Text = entity.Name, + UpdateDate = entity.ModifiedDate, + UserId = entity.UserId, + VersionId = entity.Version + }; + return documentDto; + } + + internal static List CreateProperties(int id, Guid version, IEnumerable properties) + { + var propertyDataDtos = new List(); + /*var serviceStackSerializer = new ServiceStackXmlSerializer(); + var service = new SerializationService(serviceStackSerializer);*/ + + foreach (var property in properties) + { + var dto = new PropertyDataDto { NodeId = id, PropertyTypeId = property.PropertyTypeId, VersionId = version }; + /*if (property.Value is IEditorModel) + { + var result = service.ToStream(property.Value); + dto.Text = result.ResultStream.ToJsonString(); + }*/ + if (property.Value is int) + { + dto.Integer = int.Parse(property.Value.ToString()); + } + else if (property.Value is DateTime) + { + dto.Date = DateTime.Parse(property.Value.ToString()); + } + else if (property.Value is string) + { + dto.Text = property.Value.ToString(); + } + else if (property.Value != null) + { + dto.VarChar = property.Value.ToString();//TODO Check how/when NVarChar is actually set/used + } + + propertyDataDtos.Add(dto); + } + return propertyDataDtos; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Mappers/ModelDtoMapper.cs b/src/Umbraco.Core/Persistence/Mappers/ModelDtoMapper.cs new file mode 100644 index 0000000000..d2ac50c91e --- /dev/null +++ b/src/Umbraco.Core/Persistence/Mappers/ModelDtoMapper.cs @@ -0,0 +1,86 @@ +using System; +using System.Reflection; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Mappers +{ + internal class ModelDtoMapper : IMapper + { + public void GetTableInfo(Type t, TableInfo ti) + { } + + public bool MapPropertyToColumn(PropertyInfo pi, ref string columnName, ref bool resultColumn) + { + if (pi.DeclaringType == typeof(Content) || pi.DeclaringType == typeof(IContent)) + { + switch (pi.Name) + { + case "Trashed": + columnName = "[umbracoNode].[trashed]"; + return true; + case "ParentId": + columnName = "[umbracoNode].[parentID]"; + return true; + case "UserId": + columnName = "[umbracoNode].[nodeUser]"; + return true; + case "Level": + columnName = "[umbracoNode].[level]"; + return true; + case "Path": + columnName = "[umbracoNode].[path]"; + return true; + case "SortOrder": + columnName = "[umbracoNode].[sortOrder]"; + return true; + case "NodeId": + columnName = "[umbracoNode].[id]"; + return true; + case "Published": + columnName = "[cmsDocument].[published]"; + return true; + case "Key": + columnName = "[umbracoNode].[uniqueID]"; + return true; + case "CreatedDate": + columnName = "[umbracoNode].[createDate]"; + return true; + case "Name": + columnName = "[umbracoNode].[text]"; + return true; + } + } + + if (pi.DeclaringType == typeof(ContentType) || pi.DeclaringType == typeof(IContentType)) + { + switch (pi.Name) + { + case "Alias": + columnName = "[cmsContentType].[alias]"; + return true; + case "Icon": + columnName = "[cmsContentType].[icon]"; + return true; + case "Thumbnail": + columnName = "[cmsContentType].[thumbnail]"; + return true; + case "Description": + columnName = "[cmsContentType].[description]"; + return true; + } + } + + return true; + } + + public Func GetFromDbConverter(PropertyInfo pi, Type sourceType) + { + return null; + } + + public Func GetToDbConverter(Type sourceType) + { + return null; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Querying/ExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/ExpressionHelper.cs new file mode 100644 index 0000000000..4a97dc571c --- /dev/null +++ b/src/Umbraco.Core/Persistence/Querying/ExpressionHelper.cs @@ -0,0 +1,572 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using Umbraco.Core.Persistence.Mappers; + +namespace Umbraco.Core.Persistence.Querying +{ + internal class ExpressionHelper + { + private string selectExpression = string.Empty; + private string whereExpression; + private string groupBy = string.Empty; + private string havingExpression; + private string orderBy = string.Empty; + + IList updateFields = new List(); + IList insertFields = new List(); + + private string sep = string.Empty; + private bool useFieldName = false; + private Database.PocoData pd; + + public ExpressionHelper() + { + Database.Mapper = new ModelDtoMapper(); + pd = new Database.PocoData(typeof(T)); + } + + protected internal virtual string Visit(Expression exp) + { + + if (exp == null) return string.Empty; + switch (exp.NodeType) + { + case ExpressionType.Lambda: + return VisitLambda(exp as LambdaExpression); + case ExpressionType.MemberAccess: + return VisitMemberAccess(exp as MemberExpression); + case ExpressionType.Constant: + return VisitConstant(exp as ConstantExpression); + case ExpressionType.Add: + case ExpressionType.AddChecked: + case ExpressionType.Subtract: + case ExpressionType.SubtractChecked: + case ExpressionType.Multiply: + case ExpressionType.MultiplyChecked: + case ExpressionType.Divide: + case ExpressionType.Modulo: + case ExpressionType.And: + case ExpressionType.AndAlso: + case ExpressionType.Or: + case ExpressionType.OrElse: + case ExpressionType.LessThan: + case ExpressionType.LessThanOrEqual: + case ExpressionType.GreaterThan: + case ExpressionType.GreaterThanOrEqual: + case ExpressionType.Equal: + case ExpressionType.NotEqual: + case ExpressionType.Coalesce: + case ExpressionType.ArrayIndex: + case ExpressionType.RightShift: + case ExpressionType.LeftShift: + case ExpressionType.ExclusiveOr: + return "(" + VisitBinary(exp as BinaryExpression) + ")"; + case ExpressionType.Negate: + case ExpressionType.NegateChecked: + case ExpressionType.Not: + case ExpressionType.Convert: + case ExpressionType.ConvertChecked: + case ExpressionType.ArrayLength: + case ExpressionType.Quote: + case ExpressionType.TypeAs: + return VisitUnary(exp as UnaryExpression); + case ExpressionType.Parameter: + return VisitParameter(exp as ParameterExpression); + case ExpressionType.Call: + return VisitMethodCall(exp as MethodCallExpression); + case ExpressionType.New: + return VisitNew(exp as NewExpression); + case ExpressionType.NewArrayInit: + case ExpressionType.NewArrayBounds: + return VisitNewArray(exp as NewArrayExpression); + default: + return exp.ToString(); + } + } + + protected virtual string VisitLambda(LambdaExpression lambda) + { + if (lambda.Body.NodeType == ExpressionType.MemberAccess && sep == " ") + { + MemberExpression m = lambda.Body as MemberExpression; + + if (m.Expression != null) + { + string r = VisitMemberAccess(m); + return string.Format("{0}={1}", r, GetQuotedTrueValue()); + } + + } + return Visit(lambda.Body); + } + + protected virtual string VisitBinary(BinaryExpression b) + { + string left, right; + var operand = BindOperant(b.NodeType); //sep= " " ?? + if (operand == "AND" || operand == "OR") + { + MemberExpression m = b.Left as MemberExpression; + if (m != null && m.Expression != null) + { + string r = VisitMemberAccess(m); + left = string.Format("{0}={1}", r, GetQuotedTrueValue()); + } + else + { + left = Visit(b.Left); + } + m = b.Right as MemberExpression; + if (m != null && m.Expression != null) + { + string r = VisitMemberAccess(m); + right = string.Format("{0}={1}", r, GetQuotedTrueValue()); + } + else + { + right = Visit(b.Right); + } + } + else + { + left = Visit(b.Left); + right = Visit(b.Right); + } + + if (operand == "=" && right == "null") operand = "is"; + else if (operand == "<>" && right == "null") operand = "is not"; + else if (operand == "=" || operand == "<>") + { + if (IsTrueExpression(right)) right = GetQuotedTrueValue(); + else if (IsFalseExpression(right)) right = GetQuotedFalseValue(); + + if (IsTrueExpression(left)) left = GetQuotedTrueValue(); + else if (IsFalseExpression(left)) left = GetQuotedFalseValue(); + + } + + switch (operand) + { + case "MOD": + case "COALESCE": + return string.Format("{0}({1},{2})", operand, left, right); + default: + return left + sep + operand + sep + right; + } + } + + protected virtual string VisitMemberAccess(MemberExpression m) + { + if (m.Expression != null && + m.Expression.NodeType == ExpressionType.Parameter + && m.Expression.Type == typeof(T)) + { + string field = GetFieldName(pd, m.Member.Name); + return field; + } + + if (m.Expression != null && m.Expression.NodeType != ExpressionType.Constant) + { + Database.Mapper = new ModelDtoMapper(); + var def = new Database.PocoData(m.Expression.Type); + string field = GetFieldName(def, m.Member.Name); + return field; + } + + + var member = Expression.Convert(m, typeof(object)); + var lambda = Expression.Lambda>(member); + var getter = lambda.Compile(); + object o = getter(); + return GetQuotedValue(o, o != null ? o.GetType() : null); + + } + + protected virtual string VisitNew(NewExpression nex) + { + // TODO : check ! + var member = Expression.Convert(nex, typeof(object)); + var lambda = Expression.Lambda>(member); + try + { + var getter = lambda.Compile(); + object o = getter(); + return GetQuotedValue(o, o.GetType()); + } + catch (System.InvalidOperationException) + { // FieldName ? + List exprs = VisitExpressionList(nex.Arguments); + var r = new StringBuilder(); + foreach (Object e in exprs) + { + r.AppendFormat("{0}{1}", + r.Length > 0 ? "," : "", + e); + } + return r.ToString(); + } + + } + + protected virtual string VisitParameter(ParameterExpression p) + { + return p.Name; + } + + protected virtual string VisitConstant(ConstantExpression c) + { + if (c.Value == null) + return "null"; + else if (c.Value.GetType() == typeof(bool)) + { + object o = GetQuotedValue(c.Value, c.Value.GetType()); + return string.Format("({0}={1})", GetQuotedTrueValue(), o); + } + else + return GetQuotedValue(c.Value, c.Value.GetType()); + } + + protected virtual string VisitUnary(UnaryExpression u) + { + switch (u.NodeType) + { + case ExpressionType.Not: + string o = Visit(u.Operand); + if (IsFieldName(o)) o = o + "=" + GetQuotedValue(true, typeof(bool)); + return "NOT (" + o + ")"; + default: + return Visit(u.Operand); + } + + } + + protected virtual string VisitMethodCall(MethodCallExpression m) + { + List args = this.VisitExpressionList(m.Arguments); + + Object r; + if (m.Object != null) + r = Visit(m.Object); + else + { + r = args[0]; + args.RemoveAt(0); + } + + switch (m.Method.Name) + { + case "ToUpper": + return string.Format("upper({0})", r); + case "ToLower": + return string.Format("lower({0})", r); + case "StartsWith": + return string.Format("upper({0}) starting with {1} ", r, args[0].ToString().ToUpper()); + case "EndsWith": + return string.Format("upper({0}) like '%{1}'", r, RemoveQuote(args[0].ToString()).ToUpper()); + case "Contains": + return string.Format("upper({0}) like '%{1}%'", r, RemoveQuote(args[0].ToString()).ToUpper()); + case "Substring": + var startIndex = Int32.Parse(args[0].ToString()) + 1; + if (args.Count == 2) + { + var length = Int32.Parse(args[1].ToString()); + return string.Format("substring({0} from {1} for {2})", + r, + startIndex, + length); + } + else + return string.Format("substring({0} from {1})", + r, + startIndex); + case "Round": + case "Floor": + case "Ceiling": + case "Coalesce": + case "Abs": + case "Sum": + return string.Format("{0}({1}{2})", + m.Method.Name, + r, + args.Count == 1 ? string.Format(",{0}", args[0]) : ""); + case "Concat": + var s = new StringBuilder(); + foreach (Object e in args) + { + s.AppendFormat(" || {0}", e); + } + return string.Format("{0}{1}", r, s.ToString()); + + case "In": + + var member = Expression.Convert(m.Arguments[1], typeof(object)); + var lambda = Expression.Lambda>(member); + var getter = lambda.Compile(); + + var inArgs = getter() as object[]; + + var sIn = new StringBuilder(); + foreach (Object e in inArgs) + { + if (e.GetType().ToString() != "System.Collections.Generic.List`1[System.Object]") + { + sIn.AppendFormat("{0}{1}", + sIn.Length > 0 ? "," : "", + GetQuotedValue(e, e.GetType())); + } + else + { + var listArgs = e as IList; + foreach (Object el in listArgs) + { + sIn.AppendFormat("{0}{1}", + sIn.Length > 0 ? "," : "", + GetQuotedValue(el, el.GetType())); + } + } + } + + return string.Format("{0} {1} ({2})", r, m.Method.Name, sIn.ToString()); + case "Desc": + return string.Format("{0} DESC", r); + case "Alias": + case "As": + return string.Format("{0} As {1}", r, + GetQuotedColumnName(RemoveQuoteFromAlias(RemoveQuote(args[0].ToString())))); + case "ToString": + return r.ToString(); + default: + var s2 = new StringBuilder(); + foreach (Object e in args) + { + s2.AppendFormat(",{0}", GetQuotedValue(e, e.GetType())); + } + return string.Format("{0}({1}{2})", m.Method.Name, r, s2.ToString()); + } + } + + protected virtual List VisitExpressionList(ReadOnlyCollection original) + { + var list = new List(); + for (int i = 0, n = original.Count; i < n; i++) + { + if (original[i].NodeType == ExpressionType.NewArrayInit || + original[i].NodeType == ExpressionType.NewArrayBounds) + { + + list.AddRange(VisitNewArrayFromExpressionList(original[i] as NewArrayExpression)); + } + else + list.Add(Visit(original[i])); + + } + return list; + } + + protected virtual string VisitNewArray(NewArrayExpression na) + { + + List exprs = VisitExpressionList(na.Expressions); + var r = new StringBuilder(); + foreach (Object e in exprs) + { + r.Append(r.Length > 0 ? "," + e : e); + } + + return r.ToString(); + } + + protected virtual List VisitNewArrayFromExpressionList(NewArrayExpression na) + { + + List exprs = VisitExpressionList(na.Expressions); + return exprs; + } + + + protected virtual string BindOperant(ExpressionType e) + { + + switch (e) + { + case ExpressionType.Equal: + return "="; + case ExpressionType.NotEqual: + return "<>"; + case ExpressionType.GreaterThan: + return ">"; + case ExpressionType.GreaterThanOrEqual: + return ">="; + case ExpressionType.LessThan: + return "<"; + case ExpressionType.LessThanOrEqual: + return "<="; + case ExpressionType.AndAlso: + return "AND"; + case ExpressionType.OrElse: + return "OR"; + case ExpressionType.Add: + return "+"; + case ExpressionType.Subtract: + return "-"; + case ExpressionType.Multiply: + return "*"; + case ExpressionType.Divide: + return "/"; + case ExpressionType.Modulo: + return "MOD"; + case ExpressionType.Coalesce: + return "COALESCE"; + default: + return e.ToString(); + } + } + + public virtual string GetQuotedTableName(string tableName) + { + return string.Format("\"{0}\"", tableName); + } + + public virtual string GetQuotedColumnName(string columnName) + { + return string.Format("\"{0}\"", columnName); + } + + public virtual string GetQuotedName(string name) + { + return string.Format("\"{0}\"", name); + } + + private string GetQuotedTrueValue() + { + return GetQuotedValue(true, typeof(bool)); + } + + private string GetQuotedFalseValue() + { + return GetQuotedValue(false, typeof(bool)); + } + + public virtual string GetQuotedValue(object value, Type fieldType) + { + if (value == null) return "NULL"; + + if (!fieldType.UnderlyingSystemType.IsValueType && fieldType != typeof(string)) + { + //if (TypeSerializer.CanCreateFromString(fieldType)) + //{ + // return "'" + EscapeParam(TypeSerializer.SerializeToString(value)) + "'"; + //} + + throw new NotSupportedException( + string.Format("Property of type: {0} is not supported", fieldType.FullName)); + } + + if (fieldType == typeof(int)) + return ((int)value).ToString(CultureInfo.InvariantCulture); + + if (fieldType == typeof(float)) + return ((float)value).ToString(CultureInfo.InvariantCulture); + + if (fieldType == typeof(double)) + return ((double)value).ToString(CultureInfo.InvariantCulture); + + if (fieldType == typeof(decimal)) + return ((decimal)value).ToString(CultureInfo.InvariantCulture); + + return ShouldQuoteValue(fieldType) + ? "'" + EscapeParam(value) + "'" + : value.ToString(); + } + + public virtual string EscapeParam(object paramValue) + { + return paramValue.ToString().Replace("'", "''"); + } + + public virtual bool ShouldQuoteValue(Type fieldType) + { + return true; + } + + protected virtual string GetFieldName(Database.PocoData pocoData, string name) + { + var column = pocoData.Columns.FirstOrDefault(x => x.Value.PropertyInfo.Name == name); + return column.Value.ColumnName; + } + + protected virtual string GetFieldName(string name) + { + + if (useFieldName) + { + //FieldDefinition fd = modelDef.FieldDefinitions.FirstOrDefault(x => x.Name == name); + //string fn = fd != default(FieldDefinition) ? fd.FieldName : name; + //return OrmLiteConfig.DialectProvider.GetQuotedColumnName(fn); + return "[" + name + "]"; + } + else + { + return name; + } + } + + protected string RemoveQuote(string exp) + { + + if (exp.StartsWith("'") && exp.EndsWith("'")) + { + exp = exp.Remove(0, 1); + exp = exp.Remove(exp.Length - 1, 1); + } + return exp; + } + + protected string RemoveQuoteFromAlias(string exp) + { + + if ((exp.StartsWith("\"") || exp.StartsWith("`") || exp.StartsWith("'")) + && + (exp.EndsWith("\"") || exp.EndsWith("`") || exp.EndsWith("'"))) + { + exp = exp.Remove(0, 1); + exp = exp.Remove(exp.Length - 1, 1); + } + return exp; + } + + private string GetTrueExpression() + { + object o = GetQuotedTrueValue(); + return string.Format("({0}={1})", o, o); + } + + private string GetFalseExpression() + { + + return string.Format("({0}={1})", + GetQuotedTrueValue(), + GetQuotedFalseValue()); + } + + private bool IsTrueExpression(string exp) + { + return (exp == GetTrueExpression()); + } + + private bool IsFalseExpression(string exp) + { + return (exp == GetFalseExpression()); + } + + protected bool IsFieldName(string quotedExp) + { + return true; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Querying/IQuery.cs b/src/Umbraco.Core/Persistence/Querying/IQuery.cs new file mode 100644 index 0000000000..1d634b9d90 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Querying/IQuery.cs @@ -0,0 +1,10 @@ +using System; +using System.Linq.Expressions; + +namespace Umbraco.Core.Persistence.Querying +{ + public interface IQuery + { + IQuery Where(Expression> predicate); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Querying/Query.cs b/src/Umbraco.Core/Persistence/Querying/Query.cs new file mode 100644 index 0000000000..6d2bd5c769 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Querying/Query.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace Umbraco.Core.Persistence.Querying +{ + public class Query : IQuery + { + private readonly ExpressionHelper _expresionist = new ExpressionHelper(); + private readonly List _wheres = new List(); + + public Query() + : base() + { + + } + + public static IQuery Builder + { + get + { + return new Query(); + } + } + + public virtual IQuery Where(Expression> predicate) + { + if (predicate != null) + { + string whereExpression = _expresionist.Visit(predicate); + _wheres.Add(whereExpression); + } + return this; + } + + public List WhereClauses() + { + return _wheres; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Querying/SqlTranslator.cs b/src/Umbraco.Core/Persistence/Querying/SqlTranslator.cs new file mode 100644 index 0000000000..1e08b08b1e --- /dev/null +++ b/src/Umbraco.Core/Persistence/Querying/SqlTranslator.cs @@ -0,0 +1,35 @@ +using System; + +namespace Umbraco.Core.Persistence.Querying +{ + public class SqlTranslator + { + private readonly Sql _sql; + + public SqlTranslator(Sql sql, IQuery query) + { + if (sql == null) + throw new Exception("Sql cannot be null"); + + var query1 = query as Query; + if (query1 == null) + throw new Exception("Query cannot be null"); + + _sql = sql; + foreach (var clause in query1.WhereClauses()) + { + _sql.Where(clause); + } + } + + public Sql Translate() + { + return _sql; + } + + public override string ToString() + { + return _sql.SQL; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Relators/TabPropertyTypeRelator.cs b/src/Umbraco.Core/Persistence/Relators/TabPropertyTypeRelator.cs new file mode 100644 index 0000000000..295e39dccc --- /dev/null +++ b/src/Umbraco.Core/Persistence/Relators/TabPropertyTypeRelator.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Relators +{ + internal class TabPropertyTypeRelator + { + internal TabDto current; + + internal TabDto Map(TabDto a, PropertyTypeDto p, DataTypeDto d) + { + // Terminating call. Since we can return null from this function + // we need to be ready for PetaPoco to callback later with null + // parameters + if (a == null) + return current; + + //Set the PropertyTypeDto's DataTypeDto object + if (p.DataTypeId == d.DataTypeId) + p.DataTypeDto = d; + + // Is this the same Tab as the current one we're processing + if (current != null && current.Id == a.Id) + { + // Yes, just add this PropertyType to the current Tab's collection of PropertyTypes + current.PropertyTypeDtos.Add(p); + + // Return null to indicate we're not done with this Tab yet + return null; + } + + // This is a different Tab to the current one, or this is the + // first time through and we don't have a Tab yet + + // Save the current Tab + var prev = current; + + // Setup the new current Tab + current = a; + current.PropertyTypeDtos = new List(); + current.PropertyTypeDtos.Add(p); + + // Return the now populated previous Tab (or null if first time through) + return prev; + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs new file mode 100644 index 0000000000..7dfb991767 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -0,0 +1,293 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Caching; +using Umbraco.Core.Persistence.Factories; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Core.Persistence.Repositories +{ + /// + /// Represents a repository for doing CRUD operations for + /// + internal class ContentRepository : Repository, IContentRepository + { + private const string NodeObjectType = "C66BA18E-EAF3-4CFF-8A22-41B16D66A972"; + private readonly IContentTypeRepository _contentTypeRepository; + + public ContentRepository(IUnitOfWork work, IContentTypeRepository contentTypeRepository) + : base(work) + { + _contentTypeRepository = contentTypeRepository; + } + + internal ContentRepository(IUnitOfWork work, IContentTypeRepository contentTypeRepository, IRepositoryCacheProvider registry) + : base(work, registry) + { + _contentTypeRepository = contentTypeRepository; + } + + protected override void PerformAdd(IContent entity) + { + ((Content)entity).AddingEntity(); + + //NOTE Should the logic below have some kind of fallback for empty parent ids ? + //Logic for setting Path, Level and SortOrder + var parent = UnitOfWork.Storage.First("WHERE id = @ParentId", new { ParentId = entity.ParentId }); + int level = parent.Level + 1; + int sortOrder = + UnitOfWork.Storage.ExecuteScalar("SELECT COUNT(*) FROM umbracoNode WHERE parentID = @ParentId AND nodeObjectType = @NodeObjectType", + new { ParentId = entity.ParentId, NodeObjectType = NodeObjectType }); + + //Create the (base) node data - umbracoNode + var nodeDto = ContentFactory.CreateNodeDto(entity, NodeObjectType, parent.Path, level, sortOrder); + var o = UnitOfWork.Storage.IsNew(nodeDto) ? Convert.ToInt32(UnitOfWork.Storage.Insert(nodeDto)) : UnitOfWork.Storage.Update(nodeDto); + + //Update with new correct path + nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId); + UnitOfWork.Storage.Update(nodeDto); + + //Update entity with correct values + entity.Id = nodeDto.NodeId; //Set Id on entity to ensure an Id is set + entity.Path = nodeDto.Path; + entity.SortOrder = sortOrder; + entity.Level = level; + + //Create the Content specific data - cmsContent + var contentDto = ContentFactory.CreateContentDto(entity); + UnitOfWork.Storage.Insert(contentDto); + + //Create the first version - cmsContentVersion + //Assumes a new Version guid and Version date (modified date) has been set + var contentVersionDto = ContentFactory.CreateContentVersionDto(entity); + UnitOfWork.Storage.Insert(contentVersionDto); + + //Create the Document specific data for this version - cmsDocument + //Assumes a new Version guid has been generated + var documentDto = ContentFactory.CreateDocumentDto(entity); + UnitOfWork.Storage.Insert(documentDto); + + //Create the PropertyData for this version - cmsPropertyData + var propertyDataDtos = ContentFactory.CreateProperties(entity.Id, entity.Version, entity.Properties); + //Add Properties + foreach (var propertyDataDto in propertyDataDtos) + { + UnitOfWork.Storage.Insert(propertyDataDto); + } + + ((Content)entity).ResetDirtyProperties(); + } + + protected override void PerformUpdate(IContent entity) + { + //Updates Modified date and Version Guid + ((Content)entity).UpdatingEntity(); + + //Updates the (base) node data - umbracoNode + var nodeDto = ContentFactory.CreateNodeDto(entity, NodeObjectType); + var o = UnitOfWork.Storage.Update(nodeDto); + + //Look up Content entry to get Primary for updating the DTO + var contentDto = UnitOfWork.Storage.SingleOrDefault("WHERE nodeId = @Id", new { Id = entity.Id }); + //Only update this DTO if the contentType has actually changed + if (contentDto.ContentType != entity.ContentTypeId) + { + //Create the Content specific data - cmsContent + var newContentDto = ContentFactory.CreateContentDto(entity, contentDto.PrimaryKey); + UnitOfWork.Storage.Update(newContentDto); + } + + //Look up entries in cmsDocument table to set newest = false + var documentDtos = UnitOfWork.Storage.Fetch("WHERE nodeId = @Id AND newest = '1'", new { Id = entity.Id }); + foreach (var docDto in documentDtos) + { + var dto = docDto; + dto.Newest = false; + UnitOfWork.Storage.Update(dto); + } + + //Create a new version - cmsContentVersion + //Assumes a new Version guid and Version date (modified date) has been set + var contentVersionDto = ContentFactory.CreateContentVersionDto(entity); + UnitOfWork.Storage.Insert(contentVersionDto); + + //Create the Document specific data for this version - cmsDocument + //Assumes a new Version guid has been generated + var documentDto = ContentFactory.CreateDocumentDto(entity); + UnitOfWork.Storage.Insert(documentDto); + + //Create the PropertyData for this version - cmsPropertyData + var propertyDataDtos = ContentFactory.CreateProperties(entity.Id, entity.Version, entity.Properties); + //Add Properties + foreach (var propertyDataDto in propertyDataDtos) + { + UnitOfWork.Storage.Insert(propertyDataDto); + } + + ((Content)entity).ResetDirtyProperties(); + } + + protected override void PerformDelete(IContent entity) + { + //Remove Notifications + UnitOfWork.Storage.Delete("WHERE nodeId = @Id", new { Id = entity.Id }); + + //Remove Permissions + UnitOfWork.Storage.Delete("WHERE nodeId = @Id", new { Id = entity.Id }); + + //Remove associated tags + UnitOfWork.Storage.Delete("WHERE nodeId = @Id", new { Id = entity.Id }); + + //Delete entry in Document table + UnitOfWork.Storage.Delete("WHERE NodeId = @Id", new { Id = entity.Id }); + + //Delete Properties + UnitOfWork.Storage.Delete("WHERE contentNodeId = @Id", new { Id = entity.Id }); + + //Delete Preview Xml + UnitOfWork.Storage.Delete("WHERE nodeId = @Id", new { Id = entity.Id }); + + //Delete Version history + UnitOfWork.Storage.Delete("WHERE ContentId = @Id", new { Id = entity.Id }); + + //Delete Content Xml + UnitOfWork.Storage.Delete("WHERE nodeID = @Id", new { Id = entity.Id }); + + //Delete Content specific data + UnitOfWork.Storage.Delete("WHERE NodeId = @Id", new { Id = entity.Id }); + + //Delete (base) node data + UnitOfWork.Storage.Delete("WHERE uniqueID = @Id", new { Id = entity.Key }); + } + + protected override IContent PerformGet(int id) + { + var contentSql = BaseSqlClause(false); + contentSql.Where("[cmsDocument].[nodeId] = @Id", new { Id = id }); + contentSql.OrderBy("[cmsContentVersion].[VersionDate] DESC"); + + var documentDto = UnitOfWork.Storage.Query(contentSql).FirstOrDefault(); + + if (documentDto == null) + return null; + + var propertySql = new Sql(); + propertySql.Select("*"); + propertySql.From("cmsPropertyData"); + propertySql.InnerJoin("cmsPropertyType ON ([cmsPropertyData].[propertytypeid] = [cmsPropertyType].[id])"); + propertySql.Where("[cmsPropertyData].[contentNodeId] = @Id", new { Id = id }); + propertySql.Where("[cmsPropertyData].[versionId] = @VersionId", new { VersionId = documentDto.ContentVersionDto.VersionId }); + + var propertyDataDtos = UnitOfWork.Storage.Fetch(propertySql); + + var contentType = _contentTypeRepository.Get(documentDto.ContentVersionDto.ContentDto.ContentType); + var content = ContentFactory.CreateContent(id, contentType, documentDto, propertyDataDtos); + content.ResetDirtyProperties(); + return content; + } + + protected override IEnumerable PerformGetAll(params int[] ids) + { + if (ids.Any()) + { + foreach (var id in ids) + { + yield return Get(id); + } + } + else + { + var nodeDtos = UnitOfWork.Storage.Fetch("WHERE nodeObjectType = @NodeObjectType", new { NodeObjectType = NodeObjectType }); + foreach (var nodeDto in nodeDtos) + { + yield return Get(nodeDto.NodeId); + } + } + } + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + var sqlClause = BaseSqlClause(false); + var translator = new SqlTranslator(sqlClause, query); + var sql = translator.Translate(); + + var documentDtos = UnitOfWork.Storage.Fetch(sql); + + foreach (var documentDto in documentDtos) + { + yield return Get(documentDto.NodeId); + } + } + + protected override bool PerformExists(int id) + { + return UnitOfWork.Storage.Exists(id); + } + + protected override int PerformCount(IQuery query) + { + var sqlClause = BaseSqlClause(true); + var translator = new SqlTranslator(sqlClause, query); + var sql = translator.Translate(); + + return UnitOfWork.Storage.ExecuteScalar(sql); + } + + public IEnumerable GetAllVersions(int id) + { + var contentSql = BaseSqlClause(false); + contentSql.Where("[cmsDocument].[nodeId] = @Id", new { Id = id }); + contentSql.OrderBy("[cmsContentVersion].[VersionDate] DESC"); + + var documentDtos = UnitOfWork.Storage.Fetch(contentSql); + foreach (var dto in documentDtos) + { + yield return GetByVersion(id, dto.ContentVersionDto.VersionId); + } + } + + public IContent GetByVersion(int id, Guid versionId) + { + var contentSql = BaseSqlClause(false); + contentSql.Where("[cmsDocument].[nodeId] = @Id", new { Id = id }); + contentSql.Where("[cmsContentVersion].[VersionId] = @VersionId", new { VersionId = versionId }); + contentSql.OrderBy("[cmsContentVersion].[VersionDate] DESC"); + + var documentDto = UnitOfWork.Storage.Query(contentSql).FirstOrDefault(); + + if (documentDto == null) + return null; + + var propertySql = new Sql(); + propertySql.Select("*"); + propertySql.From("cmsPropertyData"); + propertySql.InnerJoin("cmsPropertyType ON [cmsPropertyData].[propertytypeid] = [cmsPropertyType].[id]"); + propertySql.Where("[cmsPropertyData].[contentNodeId] = @Id", new { Id = id }); + propertySql.Where("[cmsPropertyData].[versionId] = @VersionId", new { VersionId = versionId }); + + var propertyDataDtos = UnitOfWork.Storage.Query(propertySql); + + var contentType = _contentTypeRepository.Get(documentDto.ContentVersionDto.ContentDto.ContentType); + var content = ContentFactory.CreateContent(id, contentType, documentDto, propertyDataDtos); + content.ResetDirtyProperties(); + return content; + } + + private Sql BaseSqlClause(bool doCount) + { + var sql = new Sql(); + + sql.Select(doCount ? "COUNT(*)" : "*"); + sql.From("cmsDocument"); + sql.InnerJoin("cmsContentVersion ON ([cmsDocument].[versionId]=[cmsContentVersion].[VersionId])"); + sql.InnerJoin("cmsContent ON ([cmsContentVersion].[ContentId]=[cmsContent].[nodeId])"); + sql.InnerJoin("umbracoNode ON ([cmsContent].[nodeId]=[umbracoNode].[id])"); + sql.Where("[umbracoNode].[nodeObjectType]=@NodeObjectType", new { NodeObjectType = NodeObjectType }); + + return sql; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs new file mode 100644 index 0000000000..64f992917b --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface IContentRepository : IRepository + { + IEnumerable GetAllVersions(int id); + IContent GetByVersion(int id, Guid versionId); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepository.cs new file mode 100644 index 0000000000..1f1cf39bc0 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepository.cs @@ -0,0 +1,9 @@ +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface IContentTypeRepository : IRepository + { + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/IRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IRepository.cs new file mode 100644 index 0000000000..e8902ebe5c --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IRepository.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Core.Persistence.Repositories +{ + /// + /// Defines the implementation of a Repository + /// + /// Type of entity for which the repository is used + public interface IRepository where TEntity : class, IAggregateRoot + { + /// + /// Adds or Updates an Entity + /// + /// + void AddOrUpdate(TEntity entity); + + /// + /// Deletes an Entity + /// + /// + void Delete(TEntity entity); + + /// + /// Gets an Entity by Id + /// + /// + /// + TEntity Get(int id); + + /// + /// Gets all entities of the spefified type + /// + /// + /// + IEnumerable GetAll(params int[] ids); + + /// + /// Gets all entities of the spefified type and query + /// + /// + /// + IEnumerable GetByQuery(IQuery query); + + /// + /// Boolean indicating whether an Entity with the specified Id exists + /// + /// + /// + bool Exists(int id); + + /// + /// Returns the count for the specified query + /// + /// + /// + int Count(IQuery query); + + /// + /// Sets the Unit Of Work for the Repository + /// + /// + void SetUnitOfWork(IUnitOfWork work); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Repository.cs b/src/Umbraco.Core/Persistence/Repositories/Repository.cs new file mode 100644 index 0000000000..1747a482a9 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/Repository.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Persistence.Caching; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Core.Persistence.Repositories +{ + /// + /// Represent an abstract Repository, which is the base of the Repository implementations + /// + /// + internal abstract class Repository : IDisposable, IRepository where TEntity : class, IAggregateRoot + { + private IUnitOfWork _work; + private readonly IRepositoryCacheProvider _cache; + + protected Repository(IUnitOfWork work) + : this(work, RuntimeCacheProvider.Current) + { + } + + internal Repository(IUnitOfWork work, IRepositoryCacheProvider cache) + { + _work = work; + _cache = cache; + } + + internal IUnitOfWork UnitOfWork + { + get { return _work; } + } + + protected abstract void PerformAdd(TEntity entity); + protected abstract void PerformUpdate(TEntity entity); + public void AddOrUpdate(TEntity entity) + { + if (!entity.HasIdentity) + { + PerformAdd(entity); + } + else + { + PerformUpdate(entity); + } + + _cache.Save(typeof(TEntity), entity); + } + + protected abstract void PerformDelete(TEntity entity); + public void Delete(TEntity entity) + { + _cache.Delete(typeof(TEntity), entity); + PerformDelete(entity); + } + + protected abstract TEntity PerformGet(int id); + public TEntity Get(int id) + { + var rEntity = _cache.GetById(typeof(TEntity), ConvertIdToGuid(id)); + if (rEntity != null) + { + return (TEntity)rEntity; + } + + var entity = PerformGet(id); + if (entity != null) + { + _cache.Save(typeof(TEntity), entity); + } + + return entity; + } + + protected abstract IEnumerable PerformGetAll(params int[] ids); + public IEnumerable GetAll(params int[] ids) + { + if (ids.Any()) + { + var entities = _cache.GetByIds(typeof(TEntity), ids.Select(ConvertIdToGuid).ToList()); + if (ids.Count().Equals(entities.Count())) + return entities.Select(x => (TEntity)x); + } + else + { + var allEntities = _cache.GetAllByType(typeof(TEntity)); + if (allEntities.Any()) + return allEntities.Select(x => (TEntity)x); + } + + var entityCollection = PerformGetAll(ids); + + foreach (var entity in entityCollection) + { + if (entity != null) + { + _cache.Save(typeof(TEntity), entity); + } + } + + return entityCollection; + } + + protected abstract IEnumerable PerformGetByQuery(IQuery query); + public IEnumerable GetByQuery(IQuery query) + { + return PerformGetByQuery(query); + } + + protected abstract bool PerformExists(int id); + public bool Exists(int id) + { + var rEntity = _cache.GetById(typeof(TEntity), ConvertIdToGuid(id)); + if (rEntity != null) + { + return true; + } + + return PerformExists(id); + } + + protected abstract int PerformCount(IQuery query); + public int Count(IQuery query) + { + return PerformCount(query); + } + + public void SetUnitOfWork(IUnitOfWork work) + { + _work = work; + } + + public virtual void Dispose() + { + _work.Dispose(); + } + + /// + /// Internal method that handles the convertion of an object Id + /// to an Integer and then a Guid Id. + /// + /// In the future it should be possible to change this method + /// so it converts from object to guid if/when we decide to go from + /// int to guid based ids. + /// + /// + protected virtual Guid ConvertIdToGuid(int id) + { + return id.ToGuid(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWork.cs b/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWork.cs new file mode 100644 index 0000000000..1f707f84e1 --- /dev/null +++ b/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWork.cs @@ -0,0 +1,10 @@ +using System; + +namespace Umbraco.Core.Persistence.UnitOfWork +{ + public interface IUnitOfWork : IDisposable + { + void Commit(); + Database Storage { get; } //TODO consider replacing 'Database' with a datastorage adapter, so there is no direct dependency on PetaPoco + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWorkProvider.cs b/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWorkProvider.cs new file mode 100644 index 0000000000..ee3404e8f2 --- /dev/null +++ b/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWorkProvider.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Core.Persistence.UnitOfWork +{ + public interface IUnitOfWorkProvider + { + IUnitOfWork GetUnitOfWork(); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index c6d6b0f65a..273d16a080 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -37,6 +37,7 @@ + @@ -81,6 +82,23 @@ + + + + + + + + + + + + + + + + + @@ -271,13 +289,7 @@ umbraco.interfaces - - - - - - - +