Merge remote-tracking branch 'origin/v8/dev' into netcore/dev

# Conflicts:
#	src/Umbraco.Core/Services/UserServiceExtensions.cs
#	src/Umbraco.Infrastructure/Examine/ContentValueSetBuilder.cs
#	src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs
#	src/Umbraco.Infrastructure/Search/ExamineComposer.cs
#	src/Umbraco.PublishedCache.NuCache/DataSource/DatabaseDataSource.cs
#	src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs
This commit is contained in:
Bjarke Berg
2020-04-24 08:33:17 +02:00
15 changed files with 254 additions and 78 deletions

View File

@@ -3,7 +3,9 @@ using System.Collections.Generic;
using System.Linq;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
@@ -16,23 +18,41 @@ namespace Umbraco.Examine
{
private readonly UrlSegmentProviderCollection _urlSegmentProviders;
private readonly IUserService _userService;
private readonly IScopeProvider _scopeProvider;
private readonly IShortStringHelper _shortStringHelper;
public ContentValueSetBuilder(PropertyEditorCollection propertyEditors,
UrlSegmentProviderCollection urlSegmentProviders,
IUserService userService,
IShortStringHelper shortStringHelper,
IScopeProvider scopeProvider,
bool publishedValuesOnly)
: base(propertyEditors, publishedValuesOnly)
{
_urlSegmentProviders = urlSegmentProviders;
_userService = userService;
_shortStringHelper = shortStringHelper;
_scopeProvider = scopeProvider;
}
/// <inheritdoc />
public override IEnumerable<ValueSet> GetValueSets(params IContent[] content)
{
Dictionary<int, IProfile> creatorIds;
Dictionary<int, IProfile> writerIds;
// We can lookup all of the creator/writer names at once which can save some
// processing below instead of one by one.
using (var scope = _scopeProvider.CreateScope())
{
creatorIds = _userService.GetProfilesById(content.Select(x => x.CreatorId).ToArray())
.ToDictionary(x => x.Id, x => x);
writerIds = _userService.GetProfilesById(content.Select(x => x.WriterId).ToArray())
.ToDictionary(x => x.Id, x => x);
scope.Complete();
}
// TODO: There is a lot of boxing going on here and ultimately all values will be boxed by Lucene anyways
// but I wonder if there's a way to reduce the boxing that we have to do or if it will matter in the end since
// Lucene will do it no matter what? One idea was to create a `FieldValue` struct which would contain `object`, `object[]`, `ValueType` and `ValueType[]`
@@ -61,8 +81,8 @@ namespace Umbraco.Examine
{"urlName", urlValue?.Yield() ?? Enumerable.Empty<string>()}, //Always add invariant urlName
{"path", c.Path?.Yield() ?? Enumerable.Empty<string>()},
{"nodeType", c.ContentType.Id.ToString().Yield() ?? Enumerable.Empty<string>()},
{"creatorName", (c.GetCreatorProfile(_userService)?.Name ?? "??").Yield() },
{"writerName",(c.GetWriterProfile(_userService)?.Name ?? "??").Yield() },
{"creatorName", (creatorIds.TryGetValue(c.CreatorId, out var creatorProfile) ? creatorProfile.Name : "??").Yield() },
{"writerName", (writerIds.TryGetValue(c.WriterId, out var writerProfile) ? writerProfile.Name : "??").Yield() },
{"writerID", new object[] {c.WriterId}},
{"templateID", new object[] {c.TemplateId ?? 0}},
{UmbracoExamineFieldNames.VariesByCultureFieldName, new object[] {"n"}},

View File

@@ -15,6 +15,48 @@ namespace Umbraco.Core.Persistence
/// </summary>
public static partial class NPocoDatabaseExtensions
{
/// <summary>
/// Iterates over the result of a paged data set with a db reader
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="database"></param>
/// <param name="pageSize">
/// The number of rows to load per page
/// </param>
/// <param name="sql"></param>
/// <returns></returns>
/// <remarks>
/// NPoco's normal Page returns a List{T} but sometimes we don't want all that in memory and instead want to
/// iterate over each row with a reader using Query vs Fetch.
/// </remarks>
public static IEnumerable<T> QueryPaged<T>(this IDatabase database, long pageSize, Sql sql)
{
var sqlString = sql.SQL;
var sqlArgs = sql.Arguments;
int? itemCount = null;
long pageIndex = 0;
do
{
// Get the paged queries
database.BuildPageQueries<T>(pageIndex * pageSize, pageSize, sqlString, ref sqlArgs, out var sqlCount, out var sqlPage);
// get the item count once
if (itemCount == null)
{
itemCount = database.ExecuteScalar<int>(sqlCount, sqlArgs);
}
pageIndex++;
// iterate over rows without allocating all items to memory (Query vs Fetch)
foreach (var row in database.Query<T>(sqlPage, sqlArgs))
{
yield return row;
}
} while ((pageIndex * pageSize) < itemCount);
}
// NOTE
//
// proper way to do it with TSQL and SQLCE

View File

@@ -43,6 +43,12 @@ namespace Umbraco.Core.Runtime
public async Task<bool> AcquireLockAsync(int millisecondsTimeout)
{
if (!_dbFactory.Configured)
{
// if we aren't configured, then we're in an install state, in which case we have no choice but to assume we can acquire
return true;
}
if (!(_dbFactory.SqlContext.SqlSyntax is SqlServerSyntaxProvider sqlServerSyntaxProvider))
throw new NotSupportedException("SqlMainDomLock is only supported for Sql Server");
@@ -129,6 +135,12 @@ namespace Umbraco.Core.Runtime
// poll every 1 second
Thread.Sleep(1000);
if (!_dbFactory.Configured)
{
// if we aren't configured, we just keep looping since we can't query the db
continue;
}
lock (_locker)
{
// If cancellation has been requested we will just exit. Depending on timing of the shutdown,
@@ -361,41 +373,44 @@ namespace Umbraco.Core.Runtime
_cancellationTokenSource.Cancel();
_cancellationTokenSource.Dispose();
var db = GetDatabase();
try
if (_dbFactory.Configured)
{
db.BeginTransaction(IsolationLevel.ReadCommitted);
// get a write lock
_sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom);
// When we are disposed, it means we have released the MainDom lock
// and called all MainDom release callbacks, in this case
// if another maindom is actually coming online we need
// to signal to the MainDom coming online that we have shutdown.
// To do that, we update the existing main dom DB record with a suffixed "_updated" string.
// Otherwise, if we are just shutting down, we want to just delete the row.
if (_mainDomChanging)
var db = GetDatabase();
try
{
_logger.Debug<SqlMainDomLock>("Releasing MainDom, updating row, new application is booting.");
db.Execute($"UPDATE umbracoKeyValue SET [value] = [value] + '{UpdatedSuffix}' WHERE [key] = @key", new { key = MainDomKey });
db.BeginTransaction(IsolationLevel.ReadCommitted);
// get a write lock
_sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom);
// When we are disposed, it means we have released the MainDom lock
// and called all MainDom release callbacks, in this case
// if another maindom is actually coming online we need
// to signal to the MainDom coming online that we have shutdown.
// To do that, we update the existing main dom DB record with a suffixed "_updated" string.
// Otherwise, if we are just shutting down, we want to just delete the row.
if (_mainDomChanging)
{
_logger.Debug<SqlMainDomLock>("Releasing MainDom, updating row, new application is booting.");
db.Execute($"UPDATE umbracoKeyValue SET [value] = [value] + '{UpdatedSuffix}' WHERE [key] = @key", new { key = MainDomKey });
}
else
{
_logger.Debug<SqlMainDomLock>("Releasing MainDom, deleting row, application is shutting down.");
db.Execute("DELETE FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey });
}
}
else
catch (Exception ex)
{
_logger.Debug<SqlMainDomLock>("Releasing MainDom, deleting row, application is shutting down.");
db.Execute("DELETE FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey });
ResetDatabase();
_logger.Error<SqlMainDomLock>(ex, "Unexpected error during dipsose.");
_hasError = true;
}
finally
{
db?.CompleteTransaction();
ResetDatabase();
}
}
catch (Exception ex)
{
ResetDatabase();
_logger.Error<SqlMainDomLock>(ex, "Unexpected error during dipsose.");
_hasError = true;
}
finally
{
db?.CompleteTransaction();
ResetDatabase();
}
}
}

View File

@@ -4,6 +4,7 @@ using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
using Umbraco.Examine;
@@ -30,13 +31,14 @@ namespace Umbraco.Web.Search
composition.Register<IndexRebuilder>(Lifetime.Singleton);
composition.RegisterUnique<IUmbracoIndexConfig, UmbracoIndexConfig>();
composition.RegisterUnique<IIndexDiagnosticsFactory, IndexDiagnosticsFactory>();
composition.RegisterUnique<IIndexDiagnosticsFactory, IndexDiagnosticsFactory>();
composition.RegisterUnique<IPublishedContentValueSetBuilder>(factory =>
new ContentValueSetBuilder(
factory.GetInstance<PropertyEditorCollection>(),
factory.GetInstance<UrlSegmentProviderCollection>(),
factory.GetInstance<IUserService>(),
factory.GetInstance<IShortStringHelper>(),
factory.GetInstance<IScopeProvider>(),
true));
composition.RegisterUnique<IContentValueSetBuilder>(factory =>
new ContentValueSetBuilder(
@@ -44,6 +46,7 @@ namespace Umbraco.Web.Search
factory.GetInstance<UrlSegmentProviderCollection>(),
factory.GetInstance<IUserService>(),
factory.GetInstance<IShortStringHelper>(),
factory.GetInstance<IScopeProvider>(),
false));
composition.RegisterUnique<IValueSetBuilder<IMedia>, MediaValueSetBuilder>();
composition.RegisterUnique<IValueSetBuilder<IMember>, MemberValueSetBuilder>();