From 40d7baca7909038f809da936289892f70b0e95a0 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Sat, 15 Feb 2020 11:08:32 +0100 Subject: [PATCH] Migrated database concerns from KeyValueService to new repository. --- src/Umbraco.Abstractions/Models/IKeyValue.cs | 14 ++ src/Umbraco.Abstractions/Models/KeyValue.cs | 39 +++ .../Repositories/IKeyValueRepository.cs | 13 + .../Migrations/Install/DatabaseDataCreator.cs | 2 +- .../Persistence/Dtos/KeyValueDto.cs | 2 +- .../Implement/KeyValueRepository.cs | 233 ++++++++++++++++++ .../Runtime/SqlMainDomLock.cs | 2 +- src/Umbraco.Infrastructure/RuntimeState.cs | 4 +- .../Services/Implement/KeyValueService.cs | 125 +--------- 9 files changed, 316 insertions(+), 118 deletions(-) create mode 100644 src/Umbraco.Abstractions/Models/IKeyValue.cs create mode 100644 src/Umbraco.Abstractions/Models/KeyValue.cs create mode 100644 src/Umbraco.Abstractions/Persistence/Repositories/IKeyValueRepository.cs create mode 100644 src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs diff --git a/src/Umbraco.Abstractions/Models/IKeyValue.cs b/src/Umbraco.Abstractions/Models/IKeyValue.cs new file mode 100644 index 0000000000..7396ae1b68 --- /dev/null +++ b/src/Umbraco.Abstractions/Models/IKeyValue.cs @@ -0,0 +1,14 @@ +using System; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + public interface IKeyValue : IEntity + { + string Identifier { get; set; } + + string Value { get; set; } + + DateTime UpdateDate { get; set; } + } +} diff --git a/src/Umbraco.Abstractions/Models/KeyValue.cs b/src/Umbraco.Abstractions/Models/KeyValue.cs new file mode 100644 index 0000000000..4ad906d91e --- /dev/null +++ b/src/Umbraco.Abstractions/Models/KeyValue.cs @@ -0,0 +1,39 @@ +using System; +using System.Runtime.Serialization; +using Umbraco.Core.Models.Entities; + +namespace Umbraco.Core.Models +{ + /// + /// Implements . + /// + [Serializable] + [DataContract(IsReference = true)] + public class KeyValue : EntityBase, IKeyValue + { + private string _identifier; + private string _value; + private DateTime _updateDate; + + /// + public string Identifier + { + get => _identifier; + set => SetPropertyValueAndDetectChanges(value, ref _identifier, nameof(Identifier)); + } + + /// + public string Value + { + get => _value; + set => SetPropertyValueAndDetectChanges(value, ref _value, nameof(Value)); + } + + /// + public DateTime UpdateDate + { + get => _updateDate; + set => SetPropertyValueAndDetectChanges(value, ref _updateDate, nameof(UpdateDate)); + } + } +} diff --git a/src/Umbraco.Abstractions/Persistence/Repositories/IKeyValueRepository.cs b/src/Umbraco.Abstractions/Persistence/Repositories/IKeyValueRepository.cs new file mode 100644 index 0000000000..baae2a1724 --- /dev/null +++ b/src/Umbraco.Abstractions/Persistence/Repositories/IKeyValueRepository.cs @@ -0,0 +1,13 @@ +namespace Umbraco.Infrastructure.Persistence.Repositories +{ + public interface IKeyValueRepository + { + void Initialize(); + + string GetValue(string key); + + void SetValue(string key, string value); + + bool TrySetValue(string key, string originalValue, string newValue); + } +} diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs index da6574670b..b4328c973d 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs @@ -344,7 +344,7 @@ namespace Umbraco.Core.Migrations.Install var stateValueKey = upgrader.StateValueKey; var finalState = upgrader.Plan.FinalState; - _database.Insert(Constants.DatabaseSchema.Tables.KeyValue, "key", false, new KeyValueDto { Key = stateValueKey, Value = finalState, Updated = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.KeyValue, "key", false, new KeyValueDto { Key = stateValueKey, Value = finalState, UpdateDate = DateTime.Now }); } } } diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/KeyValueDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/KeyValueDto.cs index b74039c388..5ead6d0d26 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/KeyValueDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/KeyValueDto.cs @@ -21,6 +21,6 @@ namespace Umbraco.Core.Persistence.Dtos [Column("updated")] [Constraint(Default = SystemMethods.CurrentDateTime)] - public DateTime Updated { get; set; } + public DateTime UpdateDate { get; set; } } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs new file mode 100644 index 0000000000..ebe101bbee --- /dev/null +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs @@ -0,0 +1,233 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NPoco; +using Umbraco.Core.Cache; +using Umbraco.Core.Logging; +using Umbraco.Core.Migrations; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Scoping; +using Umbraco.Infrastructure.Persistence.Repositories; + +namespace Umbraco.Core.Persistence.Repositories.Implement +{ + internal class KeyValueRepository : NPocoRepositoryBase, IKeyValueRepository + { + public KeyValueRepository(IScopeAccessor scopeAccessor, ILogger logger) + : base(scopeAccessor, AppCaches.NoCache, logger) + { } + + public void Initialize() + { + var context = new MigrationContext(Database, Logger); + var initMigration = new InitialMigration(context); + initMigration.Migrate(); + } + + public string GetValue(string key) + { + return GetDtoByKey(key)?.Value; + } + + public void SetValue(string key, string value) + { + var dto = GetDtoByKey(key); + if (dto == null) + { + dto = new KeyValueDto + { + Key = key, + Value = value, + UpdateDate = DateTime.Now + }; + + Database.Insert(dto); + } + else + { + UpdateDtoValue(dto, value); + } + } + + public bool TrySetValue(string key, string originalValue, string newValue) + { + var dto = GetDtoByKey(key); + + if (dto == null || dto.Value != originalValue) + return false; + + UpdateDtoValue(dto, newValue); + return true; + } + + private void UpdateDtoValue(KeyValueDto dto, string value) + { + dto.Value = value; + dto.UpdateDate = DateTime.Now; + Database.Update(dto); + } + + /// + /// Gets a value directly from the database, no scope, nothing. + /// + /// Used by to determine the runtime state. + internal static string GetValue(IUmbracoDatabase database, string key) + { + if (database is null) return null; + + var sql = database.SqlContext.Sql() + .Select() + .From() + .Where(x => x.Key == key); + return database.FirstOrDefault(sql)?.Value; + } + + #region Overrides of NPocoRepositoryBase + + protected override Guid NodeObjectTypeId => throw new NotImplementedException(); + + protected override Sql GetBaseQuery(bool isCount) + { + var sql = SqlContext.Sql(); + + sql = isCount + ? sql.SelectCount() + : sql.Select(); + + sql + .From(); + + return sql; + } + + protected override string GetBaseWhereClause() + { + return Constants.DatabaseSchema.Tables.KeyValue + ".key = @id"; + } + + protected override IEnumerable GetDeleteClauses() + { + return new List(); + } + + protected override IKeyValue PerformGet(string id) + { + var dto = GetDtoByKey(id); + return dto == null ? null : Map(dto); + } + + private KeyValueDto GetDtoByKey(string key) + { + var sql = GetBaseQuery(false).Where(x => x.Key == key); + return Database.Fetch(sql).FirstOrDefault(); + } + + protected override IEnumerable PerformGetAll(params string[] ids) + { + var sql = GetBaseQuery(false).WhereIn(x => x.Key, ids); + var dtos = Database.Fetch(sql); + return dtos.WhereNotNull().Select(Map); + } + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + throw new NotImplementedException(); + } + + protected override void PersistNewItem(IKeyValue entity) + { + var dto = Map(entity); + Database.Insert(dto); + } + + protected override void PersistUpdatedItem(IKeyValue entity) + { + var dto = Map(entity); + Database.Update(dto); + } + + private static KeyValueDto Map(IKeyValue keyValue) + { + if (keyValue == null) return null; + + return new KeyValueDto + { + Key = keyValue.Identifier, + Value = keyValue.Value, + UpdateDate = keyValue.UpdateDate, + }; + } + + private static IKeyValue Map(KeyValueDto dto) + { + if (dto == null) return null; + + return new KeyValue + { + Identifier = dto.Key, + Value = dto.Value, + UpdateDate = dto.UpdateDate, + }; + } + + #endregion + + + /// + /// A custom migration that executes standalone during the Initialize phase of the KeyValueService. + /// + internal class InitialMigration : MigrationBase + { + public InitialMigration(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + // as long as we are still running 7 this migration will be invoked, + // but due to multiple restarts during upgrades, maybe the table + // exists already + if (TableExists(Constants.DatabaseSchema.Tables.KeyValue)) + return; + + Logger.Info("Creating KeyValue structure."); + + // the locks table was initially created with an identity (auto-increment) primary key, + // but we don't want this, especially as we are about to insert a new row into the table, + // so here we drop that identity + DropLockTableIdentity(); + + // insert the lock object for key/value + Insert.IntoTable(Constants.DatabaseSchema.Tables.Lock).Row(new { id = Constants.Locks.KeyValues, name = "KeyValues", value = 1 }).Do(); + + // create the key-value table + Create.Table().Do(); + } + + private void DropLockTableIdentity() + { + // one cannot simply drop an identity, that requires a bit of work + + // create a temp. id column and copy values + Alter.Table(Constants.DatabaseSchema.Tables.Lock).AddColumn("nid").AsInt32().Nullable().Do(); + Execute.Sql("update umbracoLock set nid = id").Do(); + + // drop the id column entirely (cannot just drop identity) + Delete.PrimaryKey("PK_umbracoLock").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); + Delete.Column("id").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); + + // recreate the id column without identity and copy values + Alter.Table(Constants.DatabaseSchema.Tables.Lock).AddColumn("id").AsInt32().Nullable().Do(); + Execute.Sql("update umbracoLock set id = nid").Do(); + + // drop the temp. id column + Delete.Column("nid").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); + + // complete the primary key + Alter.Table(Constants.DatabaseSchema.Tables.Lock).AlterColumn("id").AsInt32().NotNullable().PrimaryKey("PK_umbracoLock").Do(); + } + } + } +} diff --git a/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs b/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs index 4e1feb221a..d8a66b4198 100644 --- a/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs +++ b/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs @@ -324,7 +324,7 @@ namespace Umbraco.Core.Runtime { Key = MainDomKey, Value = id, - Updated = DateTime.Now + UpdateDate = DateTime.Now }); } diff --git a/src/Umbraco.Infrastructure/RuntimeState.cs b/src/Umbraco.Infrastructure/RuntimeState.cs index 5952e73e62..b6ffecbb0d 100644 --- a/src/Umbraco.Infrastructure/RuntimeState.cs +++ b/src/Umbraco.Infrastructure/RuntimeState.cs @@ -9,7 +9,7 @@ using Umbraco.Core.Hosting; using Umbraco.Core.Logging; using Umbraco.Core.Migrations.Upgrade; using Umbraco.Core.Persistence; -using Umbraco.Core.Services.Implement; +using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Sync; namespace Umbraco.Core @@ -263,7 +263,7 @@ namespace Umbraco.Core // no scope, no service - just directly accessing the database using (var database = databaseFactory.CreateDatabase()) { - CurrentMigrationState = KeyValueService.GetValue(database, stateValueKey); + CurrentMigrationState = KeyValueRepository.GetValue(database, stateValueKey); FinalMigrationState = upgrader.Plan.FinalState; } diff --git a/src/Umbraco.Infrastructure/Services/Implement/KeyValueService.cs b/src/Umbraco.Infrastructure/Services/Implement/KeyValueService.cs index d5d8e66525..bdb65091de 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/KeyValueService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/KeyValueService.cs @@ -1,12 +1,8 @@ using System; -using System.Linq; -using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; -using Umbraco.Core.Migrations; using Umbraco.Core.Scoping; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Dtos; +using Umbraco.Infrastructure.Persistence.Repositories; namespace Umbraco.Core.Services.Implement { @@ -14,13 +10,15 @@ namespace Umbraco.Core.Services.Implement { private readonly object _initialock = new object(); private readonly IScopeProvider _scopeProvider; + private readonly IKeyValueRepository _repository; private readonly ILogger _logger; private readonly IUmbracoVersion _umbracoVersion; private bool _initialized; - public KeyValueService(IScopeProvider scopeProvider, ILogger logger, IUmbracoVersion umbracoVersion) + public KeyValueService(IScopeProvider scopeProvider, IKeyValueRepository repository, ILogger logger, IUmbracoVersion umbracoVersion) { _scopeProvider = scopeProvider; + _repository = repository; _logger = logger; _umbracoVersion = umbracoVersion; } @@ -53,71 +51,14 @@ namespace Umbraco.Core.Services.Implement using (var scope = _scopeProvider.CreateScope()) { - var context = new MigrationContext(scope.Database, _logger); - var initMigration = new InitializeMigration(context); - initMigration.Migrate(); + _repository.Initialize(); scope.Complete(); } // but don't assume we are initializing // we are upgrading from v7 and if anything goes wrong, // the table and everything will be rolled back - } - - /// - /// A custom migration that executes standalone during the Initialize phase of this service. - /// - internal class InitializeMigration : MigrationBase - { - public InitializeMigration(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - // as long as we are still running 7 this migration will be invoked, - // but due to multiple restarts during upgrades, maybe the table - // exists already - if (TableExists(Constants.DatabaseSchema.Tables.KeyValue)) - return; - - Logger.Info("Creating KeyValue structure."); - - // the locks table was initially created with an identity (auto-increment) primary key, - // but we don't want this, especially as we are about to insert a new row into the table, - // so here we drop that identity - DropLockTableIdentity(); - - // insert the lock object for key/value - Insert.IntoTable(Constants.DatabaseSchema.Tables.Lock).Row(new {id = Constants.Locks.KeyValues, name = "KeyValues", value = 1}).Do(); - - // create the key-value table - Create.Table().Do(); - } - - private void DropLockTableIdentity() - { - // one cannot simply drop an identity, that requires a bit of work - - // create a temp. id column and copy values - Alter.Table(Constants.DatabaseSchema.Tables.Lock).AddColumn("nid").AsInt32().Nullable().Do(); - Execute.Sql("update umbracoLock set nid = id").Do(); - - // drop the id column entirely (cannot just drop identity) - Delete.PrimaryKey("PK_umbracoLock").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); - Delete.Column("id").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); - - // recreate the id column without identity and copy values - Alter.Table(Constants.DatabaseSchema.Tables.Lock).AddColumn("id").AsInt32().Nullable().Do(); - Execute.Sql("update umbracoLock set id = nid").Do(); - - // drop the temp. id column - Delete.Column("nid").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); - - // complete the primary key - Alter.Table(Constants.DatabaseSchema.Tables.Lock).AlterColumn("id").AsInt32().NotNullable().PrimaryKey("PK_umbracoLock").Do(); - } - } + } /// public string GetValue(string key) @@ -126,10 +67,7 @@ namespace Umbraco.Core.Services.Implement using (var scope = _scopeProvider.CreateScope()) { - var sql = scope.SqlContext.Sql().Select().From().Where(x => x.Key == key); - var dto = scope.Database.Fetch(sql).FirstOrDefault(); - scope.Complete(); - return dto?.Value; + return _repository.GetValue(key); } } @@ -142,26 +80,7 @@ namespace Umbraco.Core.Services.Implement { scope.WriteLock(Constants.Locks.KeyValues); - var sql = scope.SqlContext.Sql().Select().From().Where(x => x.Key == key); - var dto = scope.Database.Fetch(sql).FirstOrDefault(); - - if (dto == null) - { - dto = new KeyValueDto - { - Key = key, - Value = value, - Updated = DateTime.Now - }; - - scope.Database.Insert(dto); - } - else - { - dto.Value = value; - dto.Updated = DateTime.Now; - scope.Database.Update(dto); - } + _repository.SetValue(key, value); scope.Complete(); } @@ -175,7 +94,7 @@ namespace Umbraco.Core.Services.Implement } /// - public bool TrySetValue(string key, string originValue, string newValue) + public bool TrySetValue(string key, string originalValue, string newValue) { EnsureInitialized(); @@ -183,35 +102,15 @@ namespace Umbraco.Core.Services.Implement { scope.WriteLock(Constants.Locks.KeyValues); - var sql = scope.SqlContext.Sql().Select().From().Where(x => x.Key == key); - var dto = scope.Database.Fetch(sql).FirstOrDefault(); - - if (dto == null || dto.Value != originValue) + if (!_repository.TrySetValue(key, originalValue, newValue)) + { return false; - - dto.Value = newValue; - dto.Updated = DateTime.Now; - scope.Database.Update(dto); + } scope.Complete(); } return true; } - - /// - /// Gets a value directly from the database, no scope, nothing. - /// - /// Used by to determine the runtime state. - internal static string GetValue(IUmbracoDatabase database, string key) - { - if (database is null) return null; - - var sql = database.SqlContext.Sql() - .Select() - .From() - .Where(x => x.Key == key); - return database.FirstOrDefault(sql)?.Value; - } } }