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;
- }
}
}