From 660ecfa91b57a57cabd44b7497aafff75450b3c3 Mon Sep 17 00:00:00 2001 From: "Morten@Thinkpad-X220" Date: Mon, 8 Oct 2012 08:42:57 -0200 Subject: [PATCH] Refactoring Unit Of Work, adding TransactionTypes and a new Repository structure. Adding new Factory structure. Related to U4-973, U4-955 and U4-979 --- .../Persistence/DatabaseFactory.cs | 47 +++ .../Persistence/Factories/ContentFactory.cs | 165 -------- .../Persistence/Factories/EntityFactory.cs | 127 ++++++ .../Persistence/Factories/IEntityFactory.cs | 12 + .../Persistence/Factories/PropertyFactory.cs | 75 ++++ .../Repositories/ContentRepository.cs | 384 ++++++++++-------- .../Repositories/IContentRepository.cs | 2 +- .../Repositories/IContentTypeRepository.cs | 2 +- .../Persistence/Repositories/IRepository.cs | 12 +- .../Repositories/PetaPocoRepositoryBase.cs | 64 +++ .../Persistence/Repositories/Repository.cs | 155 ------- .../Repositories/RepositoryBase.cs | 247 +++++++++++ .../Persistence/RepositoryResolver.cs | 8 +- .../Persistence/TransactionType.cs | 9 + .../Persistence/UnitOfWork/IUnitOfWork.cs | 10 +- .../UnitOfWork/IUnitOfWorkProvider.cs | 5 +- .../UnitOfWork/IUnitOfWorkRepository.cs | 11 + .../UnitOfWork/PetaPocoUnitOfWork.cs | 134 +++++- .../UnitOfWork/PetaPocoUnitOfWorkProvider.cs | 8 +- src/Umbraco.Core/Umbraco.Core.csproj | 10 +- 20 files changed, 958 insertions(+), 529 deletions(-) create mode 100644 src/Umbraco.Core/Persistence/DatabaseFactory.cs delete mode 100644 src/Umbraco.Core/Persistence/Factories/ContentFactory.cs create mode 100644 src/Umbraco.Core/Persistence/Factories/EntityFactory.cs create mode 100644 src/Umbraco.Core/Persistence/Factories/IEntityFactory.cs create mode 100644 src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs create mode 100644 src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs delete mode 100644 src/Umbraco.Core/Persistence/Repositories/Repository.cs create mode 100644 src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs create mode 100644 src/Umbraco.Core/Persistence/TransactionType.cs create mode 100644 src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWorkRepository.cs diff --git a/src/Umbraco.Core/Persistence/DatabaseFactory.cs b/src/Umbraco.Core/Persistence/DatabaseFactory.cs new file mode 100644 index 0000000000..422896288d --- /dev/null +++ b/src/Umbraco.Core/Persistence/DatabaseFactory.cs @@ -0,0 +1,47 @@ +using System.Threading; +using Umbraco.Core.Configuration; + +namespace Umbraco.Core.Persistence +{ + /// + /// Provides access to the PetaPoco database as Singleton, so the database is created once in app lifecycle. + /// This is necessary for transactions to work properly + /// + public sealed class DatabaseFactory + { + #region Singleton + + private static Database _database; + private static volatile DatabaseFactory _instance; + private static readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim(); + + private DatabaseFactory() { } + + public static DatabaseFactory Current + { + get + { + using (new WriteLock(Lock)) + { + if (_instance == null) + { + _instance = new DatabaseFactory(); + _database = new Database(GlobalSettings.DbDsn); + } + } + + return _instance; + } + } + + #endregion + + /// + /// Returns an instance of the PetaPoco database + /// + public Database Database + { + get { return _database; } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs deleted file mode 100644 index dea39db5e1..0000000000 --- a/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs +++ /dev/null @@ -1,165 +0,0 @@ -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, - CreateDate = documentDto.ContentVersionDto.ContentDto.NodeDto.CreateDate, - UpdateDate = 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.CreateDate, - 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.CreateDate, - 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.UpdateDate, - 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.UpdateDate, - 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 }; - //TODO Add complex (PropertyEditor) ValueModels to the Ntext/Nvarchar column as a serialized 'Object' (DataTypeDatabaseType.Object) - /*if (property.Value is IEditorModel) - { - var result = service.ToStream(property.Value); - dto.Text = result.ResultStream.ToJsonString(); - }*/ - if (property.DataTypeDatabaseType == DataTypeDatabaseType.Integer) - { - dto.Integer = int.Parse(property.Value.ToString()); - } - else if (property.DataTypeDatabaseType == DataTypeDatabaseType.Date) - { - dto.Date = DateTime.Parse(property.Value.ToString()); - } - else if (property.DataTypeDatabaseType == DataTypeDatabaseType.Ntext) - { - dto.Text = property.Value.ToString(); - } - else if (property.DataTypeDatabaseType == DataTypeDatabaseType.Nvarchar) - { - dto.VarChar = property.Value.ToString(); - } - - propertyDataDtos.Add(dto); - } - return propertyDataDtos; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Factories/EntityFactory.cs b/src/Umbraco.Core/Persistence/Factories/EntityFactory.cs new file mode 100644 index 0000000000..89274c36f5 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Factories/EntityFactory.cs @@ -0,0 +1,127 @@ +using System; +using System.Globalization; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Factories +{ + internal class EntityFactory : IEntityFactory + { + private readonly IContentType _contentType; + private readonly Guid _nodeObjectTypeId; + private readonly int _id; + private int _primaryKey; + + public EntityFactory(IContentType contentType, Guid nodeObjectTypeId, int id) + { + _contentType = contentType; + _nodeObjectTypeId = nodeObjectTypeId; + _id = id; + } + + #region Implementation of IEntityFactory + + public IContent BuildEntity(DocumentDto dto) + { + return new Content(dto.ContentVersionDto.ContentDto.NodeDto.ParentId, _contentType) + { + Id = _id, + Key = + dto.ContentVersionDto.ContentDto.NodeDto.UniqueId.HasValue + ? dto.ContentVersionDto.ContentDto.NodeDto.UniqueId.Value + : _id.ToGuid(), + Name = dto.ContentVersionDto.ContentDto.NodeDto.Text, + Path = dto.ContentVersionDto.ContentDto.NodeDto.Path, + UserId = + dto.ContentVersionDto.ContentDto.NodeDto.UserId.HasValue + ? dto.ContentVersionDto.ContentDto.NodeDto.UserId.Value + : dto.UserId, + Level = dto.ContentVersionDto.ContentDto.NodeDto.Level, + ParentId = dto.ContentVersionDto.ContentDto.NodeDto.ParentId, + SortOrder = dto.ContentVersionDto.ContentDto.NodeDto.SortOrder, + Trashed = dto.ContentVersionDto.ContentDto.NodeDto.Trashed, + Published = dto.Published, + CreateDate = dto.ContentVersionDto.ContentDto.NodeDto.CreateDate, + UpdateDate = dto.ContentVersionDto.VersionDate, + ExpireDate = dto.ExpiresDate, + ReleaseDate = dto.ReleaseDate, + Version = dto.ContentVersionDto.VersionId + }; + } + + public DocumentDto BuildDto(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.UpdateDate, + UserId = entity.UserId, + VersionId = entity.Version, + ContentVersionDto = BuildContentVersionDto(entity) + }; + return documentDto; + } + + #endregion + + private ContentVersionDto BuildContentVersionDto(IContent entity) + { + var contentVersionDto = new ContentVersionDto + { + NodeId = entity.Id, + VersionDate = entity.UpdateDate, + VersionId = entity.Version, + ContentDto = BuildContentDto(entity) + }; + return contentVersionDto; + } + + private ContentDto BuildContentDto(IContent entity) + { + var contentDto = new ContentDto + { + NodeId = entity.Id, + ContentType = entity.ContentTypeId, + NodeDto = BuildNodeDto(entity) + }; + + if (_primaryKey > 0) + { + contentDto.PrimaryKey = _primaryKey; + } + + return contentDto; + } + + private NodeDto BuildNodeDto(IContent entity) + { + var nodeDto = new NodeDto + { + CreateDate = entity.CreateDate, + NodeId = entity.Id, + Level = short.Parse(entity.Level.ToString(CultureInfo.InvariantCulture)), + NodeObjectType = _nodeObjectTypeId, + ParentId = entity.ParentId, + Path = entity.Path, + SortOrder = entity.SortOrder, + Text = entity.Name, + Trashed = entity.Trashed, + UniqueId = entity.Key, + UserId = entity.UserId + }; + + return nodeDto; + } + + public void SetPrimaryKey(int primaryKey) + { + _primaryKey = primaryKey; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Factories/IEntityFactory.cs b/src/Umbraco.Core/Persistence/Factories/IEntityFactory.cs new file mode 100644 index 0000000000..6fae6ff521 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Factories/IEntityFactory.cs @@ -0,0 +1,12 @@ +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Persistence.Factories +{ + internal interface IEntityFactory + where TEntity : class + where TDto : class + { + TEntity BuildEntity(TDto dto); + TDto BuildDto(TEntity entity); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs new file mode 100644 index 0000000000..26e67f3e1b --- /dev/null +++ b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Factories +{ + internal class PropertyFactory : IEntityFactory, IEnumerable> + { + private readonly IContentType _contentType; + private readonly Guid _version; + private readonly int _id; + + #region Implementation of IEntityFactory + + public PropertyFactory(IContentType contentType, Guid version, int id) + { + _contentType = contentType; + _version = version; + _id = id; + } + + public IEnumerable BuildEntity(IEnumerable dtos) + { + var properties = new List(); + foreach (var dto in dtos) + { + var propertyType = + _contentType.CompositionPropertyTypes.FirstOrDefault(x => x.Id == dto.PropertyTypeId); + properties.Add(propertyType.CreatePropertyFromRawValue(dto.GetValue)); + } + return properties; + } + + public IEnumerable BuildDto(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 }; + //TODO Add complex (PropertyEditor) ValueModels to the Ntext/Nvarchar column as a serialized 'Object' (DataTypeDatabaseType.Object) + /*if (property.Value is IEditorModel) + { + var result = service.ToStream(property.Value); + dto.Text = result.ResultStream.ToJsonString(); + }*/ + if (property.DataTypeDatabaseType == DataTypeDatabaseType.Integer) + { + dto.Integer = int.Parse(property.Value.ToString()); + } + else if (property.DataTypeDatabaseType == DataTypeDatabaseType.Date) + { + dto.Date = DateTime.Parse(property.Value.ToString()); + } + else if (property.DataTypeDatabaseType == DataTypeDatabaseType.Ntext) + { + dto.Text = property.Value.ToString(); + } + else if (property.DataTypeDatabaseType == DataTypeDatabaseType.Nvarchar) + { + dto.VarChar = property.Value.ToString(); + } + + propertyDataDtos.Add(dto); + } + return propertyDataDtos; + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 4ca549900f..710406ebdf 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; @@ -10,166 +11,31 @@ using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories { - /// - /// Represents a repository for doing CRUD operations for - /// - internal class ContentRepository : Repository, IContentRepository + internal class ContentRepository : PetaPocoRepositoryBase, IContentRepository { - private const string NodeObjectType = "C66BA18E-EAF3-4CFF-8A22-41B16D66A972"; private readonly IContentTypeRepository _contentTypeRepository; - public ContentRepository(IUnitOfWork work, IContentTypeRepository contentTypeRepository) + public ContentRepository(IUnitOfWork work, IContentTypeRepository contentTypeRepository) : base(work) { _contentTypeRepository = contentTypeRepository; } - internal ContentRepository(IUnitOfWork work, IContentTypeRepository contentTypeRepository, IRepositoryCacheProvider registry) - : base(work, registry) + public ContentRepository(IUnitOfWork work, IRepositoryCacheProvider cache, IContentTypeRepository contentTypeRepository) + : base(work, cache) { _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 }); - } + #region Overrides of RepositoryBase protected override IContent PerformGet(int id) { - var contentSql = BaseSqlClause(false); + var contentSql = GetBaseQuery(false); contentSql.Where("[cmsDocument].[nodeId] = @Id", new { Id = id }); contentSql.OrderBy("[cmsContentVersion].[VersionDate] DESC"); - var documentDto = UnitOfWork.Storage.Query(contentSql).FirstOrDefault(); + var documentDto = Database.Query(contentSql).FirstOrDefault(); if (documentDto == null) return null; @@ -181,11 +47,19 @@ namespace Umbraco.Core.Persistence.Repositories 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 propertyDataDtos = Database.Fetch(propertySql); var contentType = _contentTypeRepository.Get(documentDto.ContentVersionDto.ContentDto.ContentType); - var content = ContentFactory.CreateContent(id, contentType, documentDto, propertyDataDtos); - content.ResetDirtyProperties(); + //var content = ContentFactory.CreateContent(id, contentType, documentDto, propertyDataDtos); + + var factory = new EntityFactory(contentType, NodeObjectTypeId, id); + var content = factory.BuildEntity(documentDto); + + var propertyFactory = new PropertyFactory(contentType, documentDto.ContentVersionDto.VersionId, id); + var properties = propertyFactory.BuildEntity(propertyDataDtos); + content.Properties = new PropertyCollection(properties); + + ((Content)content).ResetDirtyProperties(); return content; } @@ -200,7 +74,7 @@ namespace Umbraco.Core.Persistence.Repositories } else { - var nodeDtos = UnitOfWork.Storage.Fetch("WHERE nodeObjectType = @NodeObjectType", new { NodeObjectType = NodeObjectType }); + var nodeDtos = Database.Fetch("WHERE nodeObjectType = @NodeObjectType", new { NodeObjectType = NodeObjectTypeId }); foreach (var nodeDto in nodeDtos) { yield return Get(nodeDto.NodeId); @@ -210,11 +84,11 @@ namespace Umbraco.Core.Persistence.Repositories protected override IEnumerable PerformGetByQuery(IQuery query) { - var sqlClause = BaseSqlClause(false); + var sqlClause = GetBaseQuery(false); var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate(); - var documentDtos = UnitOfWork.Storage.Fetch(sql); + var documentDtos = Database.Fetch(sql); foreach (var documentDto in documentDtos) { @@ -224,25 +98,189 @@ namespace Umbraco.Core.Persistence.Repositories protected override bool PerformExists(int id) { - return UnitOfWork.Storage.Exists(id); + return Database.Exists(id); } + + #endregion - protected override int PerformCount(IQuery query) + #region Overrides of PetaPocoRepositoryBase + + protected override Sql GetBaseQuery(bool isCount) { - var sqlClause = BaseSqlClause(true); - var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate(); - - return UnitOfWork.Storage.ExecuteScalar(sql); + var sql = new Sql(); + sql.Select(isCount ? "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])"); + return sql; } + protected override Sql GetBaseWhereClause() + { + var sql = new Sql(); + sql.Where("[umbracoNode].[nodeObjectType] = @NodeObjectType", new { NodeObjectType = NodeObjectTypeId }); + return sql; + } + + protected override IEnumerable GetDeleteClauses() + { + var list = new List + { + string.Format("DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id"), + string.Format("DELETE FROM umbracoUser2NodePermission WHERE nodeId = @Id"), + string.Format("DELETE FROM cmsTagRelationship WHERE nodeId = @Id"), + string.Format("DELETE FROM cmsDocument WHERE NodeId = @Id"), + string.Format("DELETE FROM cmsPropertyData WHERE contentNodeId = @Id"), + string.Format("DELETE FROM cmsPreviewXml WHERE nodeId = @Id"), + string.Format("DELETE FROM cmsContentVersion WHERE ContentId = @Id"), + string.Format("DELETE FROM cmsContentXml WHERE nodeID = @Id"), + string.Format("DELETE FROM cmsContent WHERE NodeId = @Id"), + string.Format("DELETE FROM umbracoNode WHERE id = @Id") + }; + return list; + } + + protected override Guid NodeObjectTypeId + { + get { return new Guid("C66BA18E-EAF3-4CFF-8A22-41B16D66A972"); } + } + + #endregion + + #region Unit of Work Implementation + + protected override void PersistNewItem(IContent entity) + { + ((Content)entity).AddingEntity(); + + var factory = new EntityFactory(null, NodeObjectTypeId, entity.Id); + var dto = factory.BuildDto(entity); + + //NOTE Should the logic below have some kind of fallback for empty parent ids ? + //Logic for setting Path, Level and SortOrder + var parent = Database.First("WHERE id = @ParentId", new { ParentId = entity.ParentId }); + int level = parent.Level + 1; + int sortOrder = + Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoNode WHERE parentID = @ParentId AND nodeObjectType = @NodeObjectType", + new { ParentId = entity.ParentId, NodeObjectType = NodeObjectTypeId }); + + //Create the (base) node data - umbracoNode + //var nodeDto = ContentFactory.CreateNodeDto(entity, NodeObjectTypeId, parent.Path, level, sortOrder); + var nodeDto = dto.ContentVersionDto.ContentDto.NodeDto; + nodeDto.Path = parent.Path; + nodeDto.Level = short.Parse(level.ToString(CultureInfo.InvariantCulture)); + nodeDto.SortOrder = sortOrder; + var o = Database.IsNew(nodeDto) ? Convert.ToInt32(Database.Insert(nodeDto)) : Database.Update(nodeDto); + + //Update with new correct path + nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId); + Database.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); + var contentDto = dto.ContentVersionDto.ContentDto; + Database.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); + var contentVersionDto = dto.ContentVersionDto; + Database.Insert(contentVersionDto); + + //Create the Document specific data for this version - cmsDocument + //Assumes a new Version guid has been generated + //var documentDto = ContentFactory.CreateDocumentDto(entity); + Database.Insert(dto); + + //Create the PropertyData for this version - cmsPropertyData + var propertyFactory = new PropertyFactory(null, entity.Version, entity.Id); + var propertyDataDtos = propertyFactory.BuildDto(entity.Properties); + //var propertyDataDtos = ContentFactory.CreateProperties(entity.Id, entity.Version, entity.Properties); + //Add Properties + foreach (var propertyDataDto in propertyDataDtos) + { + Database.Insert(propertyDataDto); + } + + ((Content)entity).ResetDirtyProperties(); + } + + protected override void PersistUpdatedItem(IContent entity) + { + //Updates Modified date and Version Guid + ((Content)entity).UpdatingEntity(); + + var factory = new EntityFactory(null, NodeObjectTypeId, entity.Id); + //Look up Content entry to get Primary for updating the DTO + var contentDto = Database.SingleOrDefault("WHERE nodeId = @Id", new { Id = entity.Id }); + factory.SetPrimaryKey(contentDto.PrimaryKey); + var dto = factory.BuildDto(entity); + + //Updates the (base) node data - umbracoNode + //var nodeDto = ContentFactory.CreateNodeDto(entity, NodeObjectTypeId); + var nodeDto = dto.ContentVersionDto.ContentDto.NodeDto; + var o = Database.Update(nodeDto); + + //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); + var newContentDto = dto.ContentVersionDto.ContentDto; + Database.Update(newContentDto); + } + + //Look up entries in cmsDocument table to set newest = false + var documentDtos = Database.Fetch("WHERE nodeId = @Id AND newest = '1'", new { Id = entity.Id }); + foreach (var documentDto in documentDtos) + { + var docDto = documentDto; + docDto.Newest = false; + Database.Update(docDto); + } + + //Create a new version - cmsContentVersion + //Assumes a new Version guid and Version date (modified date) has been set + //var contentVersionDto = ContentFactory.CreateContentVersionDto(entity); + var contentVersionDto = dto.ContentVersionDto; + Database.Insert(contentVersionDto); + + //Create the Document specific data for this version - cmsDocument + //Assumes a new Version guid has been generated + //var documentDto = ContentFactory.CreateDocumentDto(entity); + Database.Insert(dto); + + //Create the PropertyData for this version - cmsPropertyData + //var propertyDataDtos = ContentFactory.CreateProperties(entity.Id, entity.Version, entity.Properties); + var propertyFactory = new PropertyFactory(null, entity.Version, entity.Id); + var propertyDataDtos = propertyFactory.BuildDto(entity.Properties); + //Add Properties + foreach (var propertyDataDto in propertyDataDtos) + { + Database.Insert(propertyDataDto); + } + + ((Content)entity).ResetDirtyProperties(); + } + + #endregion + + #region Implementation of IContentRepository + public IEnumerable GetAllVersions(int id) { - var contentSql = BaseSqlClause(false); + var contentSql = GetBaseQuery(false); contentSql.Where("[cmsDocument].[nodeId] = @Id", new { Id = id }); contentSql.OrderBy("[cmsContentVersion].[VersionDate] DESC"); - var documentDtos = UnitOfWork.Storage.Fetch(contentSql); + var documentDtos = Database.Fetch(contentSql); foreach (var dto in documentDtos) { yield return GetByVersion(id, dto.ContentVersionDto.VersionId); @@ -251,12 +289,12 @@ namespace Umbraco.Core.Persistence.Repositories public IContent GetByVersion(int id, Guid versionId) { - var contentSql = BaseSqlClause(false); + var contentSql = GetBaseQuery(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(); + var documentDto = Database.Query(contentSql).FirstOrDefault(); if (documentDto == null) return null; @@ -268,26 +306,22 @@ namespace Umbraco.Core.Persistence.Repositories propertySql.Where("[cmsPropertyData].[contentNodeId] = @Id", new { Id = id }); propertySql.Where("[cmsPropertyData].[versionId] = @VersionId", new { VersionId = versionId }); - var propertyDataDtos = UnitOfWork.Storage.Query(propertySql); + var propertyDataDtos = Database.Query(propertySql); var contentType = _contentTypeRepository.Get(documentDto.ContentVersionDto.ContentDto.ContentType); - var content = ContentFactory.CreateContent(id, contentType, documentDto, propertyDataDtos); - content.ResetDirtyProperties(); + //var content = ContentFactory.CreateContent(id, contentType, documentDto, propertyDataDtos); + + var factory = new EntityFactory(contentType, NodeObjectTypeId, id); + var content = factory.BuildEntity(documentDto); + + var propertyFactory = new PropertyFactory(contentType, documentDto.ContentVersionDto.VersionId, id); + var properties = propertyFactory.BuildEntity(propertyDataDtos); + content.Properties = new PropertyCollection(properties); + + ((Content)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; - } + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs index 64f992917b..76c992cb65 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs @@ -4,7 +4,7 @@ using Umbraco.Core.Models; namespace Umbraco.Core.Persistence.Repositories { - public interface IContentRepository : IRepository + public interface IContentRepository : IRepository { IEnumerable GetAllVersions(int id); IContent GetByVersion(int id, Guid versionId); diff --git a/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepository.cs index 1f1cf39bc0..dcdabeb0e1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepository.cs @@ -2,7 +2,7 @@ namespace Umbraco.Core.Persistence.Repositories { - public interface IContentTypeRepository : IRepository + public interface IContentTypeRepository : IRepository { } diff --git a/src/Umbraco.Core/Persistence/Repositories/IRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IRepository.cs index 9f1888fece..d47ce7400c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IRepository.cs @@ -9,7 +9,9 @@ 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 + /// Type of the Id used for this entity + public interface IRepository + where TEntity : IAggregateRoot { /// /// Adds or Updates an Entity @@ -28,14 +30,14 @@ namespace Umbraco.Core.Persistence.Repositories /// /// /// - TEntity Get(int id); + TEntity Get(TId id); /// /// Gets all entities of the spefified type /// /// /// - IEnumerable GetAll(params int[] ids); + IEnumerable GetAll(params TId[] ids); /// /// Gets all entities of the spefified type and query @@ -49,7 +51,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// /// - bool Exists(int id); + bool Exists(TId id); /// /// Returns the count for the specified query @@ -62,6 +64,6 @@ namespace Umbraco.Core.Persistence.Repositories /// Sets the Unit Of Work for the Repository /// /// - void SetUnitOfWork(IUnitOfWork work); + void SetUnitOfWork(IUnitOfWork work); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs new file mode 100644 index 0000000000..7f0ab49c2a --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; +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 for PetaPoco based repositories + /// + /// + /// + internal abstract class PetaPocoRepositoryBase : RepositoryBase + where TEntity : IAggregateRoot + { + private Database _database; + + protected PetaPocoRepositoryBase(IUnitOfWork work) : base(work) + { + } + + protected PetaPocoRepositoryBase(IUnitOfWork work, IRepositoryCacheProvider cache) : base(work, cache) + { + _database = DatabaseFactory.Current.Database; + } + + protected Database Database + { + get { return _database; } + } + + #region Abstract Methods + + protected abstract Sql GetBaseQuery(bool isCount); + protected abstract Sql GetBaseWhereClause(); + protected abstract IEnumerable GetDeleteClauses(); + protected abstract Guid NodeObjectTypeId { get; } + protected abstract override void PersistNewItem(TEntity entity); + protected abstract override void PersistUpdatedItem(TEntity entity); + + #endregion + + protected override int PerformCount(IQuery query) + { + var sqlClause = GetBaseQuery(true); + var translator = new SqlTranslator(sqlClause, query); + var sql = translator.Translate(); + + return _database.ExecuteScalar(sql); + } + + protected override void PersistDeletedItem(TEntity entity) + { + var deletes = GetDeleteClauses(); + foreach (var delete in deletes) + { + _database.Execute(delete, new {Id = entity.Id}); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Repository.cs b/src/Umbraco.Core/Persistence/Repositories/Repository.cs deleted file mode 100644 index 8e4084adb6..0000000000 --- a/src/Umbraco.Core/Persistence/Repositories/Repository.cs +++ /dev/null @@ -1,155 +0,0 @@ -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 as IUnitOfWork; - } - - 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/Repositories/RepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs new file mode 100644 index 0000000000..1dd7df8e90 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs @@ -0,0 +1,247 @@ +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 + /// + /// Type of entity for which the repository is used + /// Type of the Id used for this entity + internal abstract class RepositoryBase : IRepository, + IUnitOfWorkRepository where TEntity : IAggregateRoot + { + private IUnitOfWork _work; + private readonly IRepositoryCacheProvider _cache; + + protected RepositoryBase(IUnitOfWork work) + : this(work, RuntimeCacheProvider.Current) + { + } + + internal RepositoryBase(IUnitOfWork work, IRepositoryCacheProvider cache) + { + _work = work; + _cache = cache; + } + + /// + /// Returns the Unit of Work added to the repository + /// + protected IUnitOfWork UnitOfWork + { + get { return _work; } + } + + #region IRepository Members + + /// + /// Adds or Updates an entity of type TEntity + /// + /// This method is backed by an cache + /// + public void AddOrUpdate(TEntity entity) + { + if (!entity.HasIdentity) + { + _work.RegisterAdded(entity, this); + } + else + { + _work.RegisterChanged(entity, this); + } + + _cache.Save(typeof(TEntity), entity); + } + + /// + /// Deletes the passed in entity + /// + /// + public void Delete(TEntity entity) + { + _cache.Delete(typeof(TEntity), entity); + if(_work != null) + { + _work.RegisterRemoved(entity, this); + } + } + + protected abstract TEntity PerformGet(TId id); + /// + /// Gets an entity by the passed in Id + /// + /// + /// + public TEntity Get(TId id) + { + Guid key = id is int ? ConvertIdToGuid(id) : ConvertStringIdToGuid(id.ToString()); + var rEntity = _cache.GetById(typeof(TEntity), key); + 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 TId[] ids); + /// + /// Gets all entities of type TEntity or a list according to the passed in Ids + /// + /// + /// + public IEnumerable GetAll(params TId[] ids) + { + if (ids.Any()) + { + var entities = _cache.GetByIds(typeof(TEntity), ids.Select(id => id is int ? ConvertIdToGuid(id) : ConvertStringIdToGuid(id.ToString())).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); + /// + /// Gets a list of entities by the passed in query + /// + /// + /// + public IEnumerable GetByQuery(IQuery query) + { + return PerformGetByQuery(query); + } + + protected abstract bool PerformExists(TId id); + /// + /// Returns a boolean indicating whether an entity with the passed Id exists + /// + /// + /// + public bool Exists(TId id) + { + Guid key = id is int ? ConvertIdToGuid(id) : ConvertStringIdToGuid(id.ToString()); + var rEntity = _cache.GetById(typeof(TEntity), key); + if (rEntity != null) + { + return true; + } + + return PerformExists(id); + } + + protected abstract int PerformCount(IQuery query); + /// + /// Returns an integer with the count of entities found with the passed in query + /// + /// + /// + public int Count(IQuery query) + { + return PerformCount(query); + } + + /// + /// Sets the repository's Unit Of Work with the passed in + /// + /// + public void SetUnitOfWork(IUnitOfWork work) + { + _work = work; + } + + #endregion + + #region IUnitOfWorkRepository Members + + /// + /// Unit of work method that tells the repository to persist the new entity + /// + /// + public virtual void PersistNewItem(IEntity item) + { + PersistNewItem((TEntity)item); + } + + /// + /// Unit of work method that tells the repository to persist the updated entity + /// + /// + public virtual void PersistUpdatedItem(IEntity item) + { + PersistUpdatedItem((TEntity)item); + } + + /// + /// Unit of work method that tells the repository to persist the deletion of the entity + /// + /// + public virtual void PersistDeletedItem(IEntity item) + { + PersistDeletedItem((TEntity)item); + } + + #endregion + + #region Abstract IUnitOfWorkRepository Methods + + protected abstract void PersistNewItem(TEntity item); + protected abstract void PersistUpdatedItem(TEntity item); + protected abstract void PersistDeletedItem(TEntity item); + + #endregion + + /// + /// 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(TId id) + { + int i = 0; + if(int.TryParse(id.ToString(), out i)) + { + return i.ToGuid(); + } + return ConvertStringIdToGuid(id.ToString()); + } + + protected virtual Guid ConvertStringIdToGuid(string id) + { + return id.EncodeAsGuid(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/RepositoryResolver.cs b/src/Umbraco.Core/Persistence/RepositoryResolver.cs index 57d7c15984..f939c0c852 100644 --- a/src/Umbraco.Core/Persistence/RepositoryResolver.cs +++ b/src/Umbraco.Core/Persistence/RepositoryResolver.cs @@ -22,8 +22,8 @@ namespace Umbraco.Core.Persistence //Otherwise look for an entity type in the config //- If type exists check dependencies, create new object, add it to dictionary and return it //If we have come this far the correct types wasn't found and we throw an exception - internal static TRepository ResolveByType(IUnitOfWork unitOfWork) - where TRepository : class, IRepository + internal static TRepository ResolveByType(IUnitOfWork unitOfWork) + where TRepository : class, IRepository where TEntity : class, IAggregateRoot { //Initialize the provider's default value @@ -36,7 +36,7 @@ namespace Umbraco.Core.Persistence if (Repositories.ContainsKey(interfaceShortName)) { repository = (TRepository)Repositories[interfaceShortName]; - if (unitOfWork != null && repository.GetType().IsSubclassOf(typeof(IRepository))) + if (unitOfWork != null && repository.GetType().IsSubclassOf(typeof(IRepository))) { repository.SetUnitOfWork(unitOfWork); } @@ -83,7 +83,7 @@ namespace Umbraco.Core.Persistence } //Recursive create and dependency check - private static object Resolve(Type repositoryType, IUnitOfWork unitOfWork) + private static object Resolve(Type repositoryType, IUnitOfWork unitOfWork) { var constructor = repositoryType.GetConstructors().SingleOrDefault(); if (constructor == null) diff --git a/src/Umbraco.Core/Persistence/TransactionType.cs b/src/Umbraco.Core/Persistence/TransactionType.cs new file mode 100644 index 0000000000..0dbe754eb1 --- /dev/null +++ b/src/Umbraco.Core/Persistence/TransactionType.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Core.Persistence +{ + internal enum TransactionType + { + Insert, + Update, + Delete + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWork.cs b/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWork.cs index 801cbd08af..809c2340e4 100644 --- a/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWork.cs +++ b/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWork.cs @@ -1,14 +1,16 @@ -using System; +using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Persistence.UnitOfWork { /// /// Defines a Unit Of Work /// - /// - public interface IUnitOfWork : IDisposable + public interface IUnitOfWork { + void RegisterAdded(IEntity entity, IUnitOfWorkRepository repository); + void RegisterChanged(IEntity entity, IUnitOfWorkRepository repository); + void RegisterRemoved(IEntity entity, IUnitOfWorkRepository repository); void Commit(); - T Storage { get; }//TODO This won't work! Need to change it + object Key { get; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWorkProvider.cs b/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWorkProvider.cs index 443b5421aa..7b87d9a2ee 100644 --- a/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWorkProvider.cs +++ b/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWorkProvider.cs @@ -3,9 +3,8 @@ /// /// Defines a Unit of Work Provider /// - /// - public interface IUnitOfWorkProvider + public interface IUnitOfWorkProvider { - IUnitOfWork GetUnitOfWork(); + IUnitOfWork GetUnitOfWork(); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWorkRepository.cs b/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWorkRepository.cs new file mode 100644 index 0000000000..e64bf79612 --- /dev/null +++ b/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWorkRepository.cs @@ -0,0 +1,11 @@ +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Persistence.UnitOfWork +{ + public interface IUnitOfWorkRepository + { + void PersistNewItem(IEntity entity); + void PersistUpdatedItem(IEntity entity); + void PersistDeletedItem(IEntity entity); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs b/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs index 0173d39883..b06be5df8c 100644 --- a/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs +++ b/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs @@ -1,32 +1,142 @@ -using Umbraco.Core.Configuration; +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Persistence.UnitOfWork { - internal class PetaPocoUnitOfWork : IUnitOfWork + /// + /// Represents the Unit of Work implementation for PetaPoco + /// + internal class PetaPocoUnitOfWork : IUnitOfWork { - private readonly Transaction _petaTransaction; - private readonly Database _storage; + private Guid _key; + private List _operations; public PetaPocoUnitOfWork() { - var connectionString = GlobalSettings.DbDsn; - _storage = new Database(connectionString); - _petaTransaction = new Transaction(_storage); + _key = Guid.NewGuid(); + _operations = new List(); } - public void Dispose() + /// + /// Registers an instance to be added through this + /// + /// The + /// The participating in the transaction + public void RegisterAdded(IEntity entity, IUnitOfWorkRepository repository) { - _petaTransaction.Dispose(); + _operations.Add( + new Operation + { + Entity = entity, + ProcessDate = DateTime.Now, + Repository = repository, + Type = TransactionType.Insert + }); } + /// + /// Registers an instance to be changed through this + /// + /// The + /// The participating in the transaction + public void RegisterChanged(IEntity entity, IUnitOfWorkRepository repository) + { + _operations.Add( + new Operation + { + Entity = entity, + ProcessDate = DateTime.Now, + Repository = repository, + Type = TransactionType.Update + }); + } + + /// + /// Registers an instance to be removed through this + /// + /// The + /// The participating in the transaction + public void RegisterRemoved(IEntity entity, IUnitOfWorkRepository repository) + { + _operations.Add( + new Operation + { + Entity = entity, + ProcessDate = DateTime.Now, + Repository = repository, + Type = TransactionType.Delete + }); + } + + /// + /// Commits all batched changes within the scope of a PetaPoco transaction + /// public void Commit() { - _petaTransaction.Complete(); + using(Transaction transaction = DatabaseFactory.Current.Database.GetTransaction()) + { + foreach (var operation in _operations.OrderBy(o => o.ProcessDate)) + { + switch (operation.Type) + { + case TransactionType.Insert: + operation.Repository.PersistNewItem(operation.Entity); + break; + case TransactionType.Delete: + operation.Repository.PersistDeletedItem(operation.Entity); + break; + case TransactionType.Update: + operation.Repository.PersistUpdatedItem(operation.Entity); + break; + } + } + transaction.Complete(); + } + + // Clear everything + _operations.Clear(); + _key = Guid.NewGuid(); } - public Database Storage + public object Key { - get { return _storage; } + get { return _key; } } + + #region Operation + + /// + /// Provides a snapshot of an entity and the repository reference it belongs to. + /// + private sealed class Operation + { + /// + /// Gets or sets the entity. + /// + /// The entity. + public IEntity Entity { get; set; } + + /// + /// Gets or sets the process date. + /// + /// The process date. + public DateTime ProcessDate { get; set; } + + /// + /// Gets or sets the repository. + /// + /// The repository. + public IUnitOfWorkRepository Repository { get; set; } + + /// + /// Gets or sets the type of operation. + /// + /// The type of operation. + public TransactionType Type { get; set; } + } + + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWorkProvider.cs b/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWorkProvider.cs index 87babcbc44..5fcdc0ad07 100644 --- a/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWorkProvider.cs +++ b/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWorkProvider.cs @@ -1,10 +1,14 @@ namespace Umbraco.Core.Persistence.UnitOfWork { - internal class PetaPocoUnitOfWorkProvider : IUnitOfWorkProvider + internal class PetaPocoUnitOfWorkProvider : IUnitOfWorkProvider { - public IUnitOfWork GetUnitOfWork() + #region Implementation of IUnitOfWorkProvider + + public IUnitOfWork GetUnitOfWork() { return new PetaPocoUnitOfWork(); } + + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index c4a83197a6..4ac179c981 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -91,7 +91,10 @@ - + + + + @@ -102,10 +105,13 @@ - + + + +