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