diff --git a/Directory.Packages.props b/Directory.Packages.props index 5107faad00..e9c39aeae8 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -60,8 +60,8 @@ - - + + diff --git a/global.json b/global.json index d5eb15969c..5f3c376000 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "10.0.100-preview.7.25380.108", + "version": "10.0.100-rc.1.25451.107", "rollForward": "latestFeature", "allowPrerelease": false } diff --git a/src/Umbraco.Cms.Api.Delivery/Routing/DeliveryApiItemsEndpointsMatcherPolicy.cs b/src/Umbraco.Cms.Api.Delivery/Routing/DeliveryApiItemsEndpointsMatcherPolicy.cs index 186cd4f555..89289104e1 100644 --- a/src/Umbraco.Cms.Api.Delivery/Routing/DeliveryApiItemsEndpointsMatcherPolicy.cs +++ b/src/Umbraco.Cms.Api.Delivery/Routing/DeliveryApiItemsEndpointsMatcherPolicy.cs @@ -66,9 +66,14 @@ internal sealed class DeliveryApiItemsEndpointsMatcherPolicy : MatcherPolicy, IE { ApiVersion[]? supportedApiVersions = endpoint.Metadata.GetMetadata()?.Versions.ToArray(); - // if the endpoint is versioned, the requested API version must be among the API versions supported by the endpoint. - // if the endpoint is NOT versioned, it cannot be used with a requested API version - return supportedApiVersions?.Contains(requestedApiVersion) ?? requestedApiVersion is null; + // If the endpoint is versioned, the requested API version must be among the API versions supported by the endpoint. + // If the endpoint is NOT versioned, it cannot be used with a requested API version. + if (supportedApiVersions is not null && requestedApiVersion is not null) + { + return supportedApiVersions.Contains(requestedApiVersion); + } + + return requestedApiVersion is null; } private static bool IsByIdsController(ControllerActionDescriptor? controllerActionDescriptor) diff --git a/src/Umbraco.Cms.Api.Management/Services/Entities/UserStartNodeEntitiesService.cs b/src/Umbraco.Cms.Api.Management/Services/Entities/UserStartNodeEntitiesService.cs index 6fda490b61..96b547ec31 100644 --- a/src/Umbraco.Cms.Api.Management/Services/Entities/UserStartNodeEntitiesService.cs +++ b/src/Umbraco.Cms.Api.Management/Services/Entities/UserStartNodeEntitiesService.cs @@ -87,10 +87,12 @@ public class UserStartNodeEntitiesService : IUserStartNodeEntitiesService return ChildUserAccessEntities(children, userStartNodePaths); } - int[] allowedChildIds = GetAllowedIds(userStartNodePaths, parentId); + // Need to use a List here because the expression tree cannot convert an array when used in Contains. + // See ExpressionTests.Sql_In(). + List allowedChildIds = GetAllowedIds(userStartNodePaths, parentId); - totalItems = allowedChildIds.Length; - if (allowedChildIds.Length == 0) + totalItems = allowedChildIds.Count; + if (allowedChildIds.Count == 0) { // The requested parent is outside the scope of any user start nodes. return []; @@ -102,7 +104,7 @@ public class UserStartNodeEntitiesService : IUserStartNodeEntitiesService return ChildUserAccessEntities(children, userStartNodePaths); } - private static int[] GetAllowedIds(string[] userStartNodePaths, int parentId) + private static List GetAllowedIds(string[] userStartNodePaths, int parentId) { // If one or more of the user start nodes are descendants of the requested parent, find the "next child IDs" in those user start node paths // that are the final entries in the path. @@ -112,7 +114,7 @@ public class UserStartNodeEntitiesService : IUserStartNodeEntitiesService .Where(ids => ids.Contains(parentId)) .Select(ids => ids[ids.IndexOf(parentId) + 1]) // Given the previous checks, the parent ID can never be the last in the user start node path, so this is safe .Distinct() - .ToArray(); + .ToList(); } /// @@ -185,9 +187,9 @@ public class UserStartNodeEntitiesService : IUserStartNodeEntitiesService return ChildUserAccessEntities(siblings, userStartNodePaths); } - int[] allowedSiblingIds = GetAllowedIds(userStartNodePaths, targetParent.Id); + List allowedSiblingIds = GetAllowedIds(userStartNodePaths, targetParent.Id); - if (allowedSiblingIds.Length == 0) + if (allowedSiblingIds.Count == 0) { // The requested target is outside the scope of any user start nodes. totalBefore = 0; diff --git a/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerDistributedLockingMechanism.cs b/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerDistributedLockingMechanism.cs index d0ffd7ee4f..fb0cb9133e 100644 --- a/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerDistributedLockingMechanism.cs +++ b/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerDistributedLockingMechanism.cs @@ -132,7 +132,7 @@ public class SqlServerDistributedLockingMechanism : IDistributedLockingMechanism throw new PanicException("Could not find a database"); } - if (!db.InTransaction) + if (db.InTransaction is false || db.Transaction is null) { throw new InvalidOperationException( "SqlServerDistributedLockingMechanism requires a transaction to function."); @@ -167,7 +167,7 @@ public class SqlServerDistributedLockingMechanism : IDistributedLockingMechanism throw new PanicException("Could not find a database"); } - if (!db.InTransaction) + if (db.InTransaction is false || db.Transaction is null) { throw new InvalidOperationException( "SqlServerDistributedLockingMechanism requires a transaction to function."); diff --git a/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerSyntaxProvider.cs b/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerSyntaxProvider.cs index 28ff4806a4..1464b049da 100644 --- a/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerSyntaxProvider.cs @@ -262,6 +262,12 @@ order by T.name, I.name"); /// public override bool TryGetDefaultConstraint(IDatabase db, string? tableName, string columnName, [MaybeNullWhen(false)] out string constraintName) { + if (string.IsNullOrWhiteSpace(tableName)) + { + constraintName = null; + return false; + } + constraintName = db.Fetch( @"select con.[name] as [constraintName] from sys.default_constraints con diff --git a/src/Umbraco.Core/Models/Notification.cs b/src/Umbraco.Core/Models/Notification.cs index 31d17513a6..e93a3bdeb6 100644 --- a/src/Umbraco.Core/Models/Notification.cs +++ b/src/Umbraco.Core/Models/Notification.cs @@ -2,7 +2,7 @@ namespace Umbraco.Cms.Core.Models; public class Notification { - public Notification(int entityId, int userId, string action, Guid? entityType) + public Notification(int entityId, int userId, string action, Guid entityType) { EntityId = entityId; UserId = userId; @@ -16,5 +16,5 @@ public class Notification public string Action { get; } - public Guid? EntityType { get; } + public Guid EntityType { get; } } diff --git a/src/Umbraco.Core/Persistence/Repositories/INotificationsRepository.cs b/src/Umbraco.Core/Persistence/Repositories/INotificationsRepository.cs index 5a3f63f8cb..0c36e233e1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/INotificationsRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/INotificationsRepository.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Membership; @@ -6,7 +7,7 @@ namespace Umbraco.Cms.Core.Persistence.Repositories; public interface INotificationsRepository : IRepository { - Notification CreateNotification(IUser user, IEntity entity, string action); + bool TryCreateNotification(IUser user, IEntity entity, string action, [NotNullWhen(true)] out Notification? notification); int DeleteNotifications(IUser user); @@ -14,11 +15,11 @@ public interface INotificationsRepository : IRepository int DeleteNotifications(IUser user, IEntity entity); - IEnumerable? GetEntityNotifications(IEntity entity); + IEnumerable GetEntityNotifications(IEntity entity); - IEnumerable? GetUserNotifications(IUser user); + IEnumerable GetUserNotifications(IUser user); - IEnumerable? GetUsersNotifications(IEnumerable userIds, string? action, IEnumerable nodeIds, Guid objectType); + IEnumerable GetUsersNotifications(IEnumerable userIds, string? action, IEnumerable nodeIds, Guid objectType); IEnumerable SetNotifications(IUser user, IEntity entity, string[] actions); } diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 139b057ba0..884eac7dd7 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -695,9 +695,13 @@ public class ContentService : RepositoryService, IContentService using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { + // Need to use a List here because the expression tree cannot convert the array when used in Contains. + // See ExpressionTests.Sql_In(). + List contentTypeIdsAsList = [.. contentTypeIds]; + scope.ReadLock(Constants.Locks.ContentTree); return _documentRepository.GetPage( - Query()?.Where(x => contentTypeIds.Contains(x.ContentTypeId)), + Query()?.Where(x => contentTypeIdsAsList.Contains(x.ContentTypeId)), pageIndex, pageSize, out totalRecords, @@ -3759,7 +3763,10 @@ public class ContentService : RepositoryService, IContentService IQuery query = Query(); if (contentTypeId.Length > 0) { - query.Where(x => contentTypeId.Contains(x.ContentTypeId)); + // Need to use a List here because the expression tree cannot convert the array when used in Contains. + // See ExpressionTests.Sql_In(). + List contentTypeIdsAsList = [.. contentTypeId]; + query.Where(x => contentTypeIdsAsList.Contains(x.ContentTypeId)); } return _documentBlueprintRepository.Get(query).Select(x => @@ -3778,11 +3785,14 @@ public class ContentService : RepositoryService, IContentService { scope.WriteLock(Constants.Locks.ContentTree); - var contentTypeIdsA = contentTypeIds.ToArray(); + // Need to use a List here because the expression tree cannot convert an array when used in Contains. + // See ExpressionTests.Sql_In(). + var contentTypeIdsAsList = contentTypeIds.ToList(); + IQuery query = Query(); - if (contentTypeIdsA.Length > 0) + if (contentTypeIdsAsList.Count > 0) { - query.Where(x => contentTypeIdsA.Contains(x.ContentTypeId)); + query.Where(x => contentTypeIdsAsList.Contains(x.ContentTypeId)); } IContent[]? blueprints = _documentBlueprintRepository.Get(query)?.Select(x => diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 315cd430ef..04c8a91060 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -315,7 +315,10 @@ namespace Umbraco.Cms.Core.Services.Implement IQuery query = Query(); if (keys.Length > 0) { - query = query.Where(x => keys.Contains(x.Key)); + // Need to use a List here because the expression tree cannot convert the array when used in Contains. + // See ExpressionTests.Sql_In(). + List keysAsList = [.. keys]; + query = query.Where(x => keysAsList.Contains(x.Key)); } IDataType[] dataTypes = _dataTypeRepository.Get(query).ToArray(); @@ -398,7 +401,12 @@ namespace Umbraco.Cms.Core.Services.Implement public Task> GetByEditorAliasAsync(string[] propertyEditorAlias) { using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); - IQuery query = Query().Where(x => propertyEditorAlias.Contains(x.EditorAlias)); + + // Need to use a List here because the expression tree cannot convert the array when used in Contains. + // See ExpressionTests.Sql_In(). + List propertyEditorAliasesAsList = [.. propertyEditorAlias]; + IQuery query = Query().Where(x => propertyEditorAliasesAsList.Contains(x.EditorAlias)); + IEnumerable dataTypes = _dataTypeRepository.Get(query).ToArray(); return Task.FromResult(dataTypes); } diff --git a/src/Umbraco.Core/Services/INotificationService.cs b/src/Umbraco.Core/Services/INotificationService.cs index 8472333d19..06193b2708 100644 --- a/src/Umbraco.Core/Services/INotificationService.cs +++ b/src/Umbraco.Core/Services/INotificationService.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Membership; @@ -82,11 +83,12 @@ public interface INotificationService : IService IEnumerable? SetNotifications(IUser? user, IEntity entity, string[] actions); /// - /// Creates a new notification + /// Tries to create a new notification. /// - /// - /// + /// The user. + /// The entity. /// The action letter - note: this is a string for future compatibility - /// - Notification CreateNotification(IUser user, IEntity entity, string action); + /// The created notification. + /// + bool TryCreateNotification(IUser user, IEntity entity, string action, [NotNullWhen(true)] out Notification? notification); } diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index a782ef0db9..df60ceee6e 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -498,10 +498,15 @@ namespace Umbraco.Cms.Core.Services ordering = Ordering.By("sortOrder"); } + // Need to use a List here because the expression tree cannot convert the array when used in Contains. + // See ExpressionTests.Sql_In(). + List contentTypeIdsAsList = [.. contentTypeIds]; + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MediaTree); + return _mediaRepository.GetPage( - Query()?.Where(x => contentTypeIds.Contains(x.ContentTypeId)), pageIndex, pageSize, out totalRecords, filter, ordering); + Query()?.Where(x => contentTypeIdsAsList.Contains(x.ContentTypeId)), pageIndex, pageSize, out totalRecords, filter, ordering); } /// diff --git a/src/Umbraco.Core/Services/NotificationService.cs b/src/Umbraco.Core/Services/NotificationService.cs index 3d5673fd7a..d12ef6c6df 100644 --- a/src/Umbraco.Core/Services/NotificationService.cs +++ b/src/Umbraco.Core/Services/NotificationService.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Text; using Microsoft.Extensions.Logging; @@ -241,20 +242,14 @@ public class NotificationService : INotificationService } } - /// - /// Creates a new notification - /// - /// - /// - /// The action letter - note: this is a string for future compatibility - /// - public Notification CreateNotification(IUser user, IEntity entity, string action) + /// + public bool TryCreateNotification(IUser user, IEntity entity, string action, [NotNullWhen(true)] out Notification? notification) { using (ICoreScope scope = _uowProvider.CreateCoreScope()) { - Notification notification = _notificationsRepository.CreateNotification(user, entity, action); + var result = _notificationsRepository.TryCreateNotification(user, entity, action, out notification); scope.Complete(); - return notification; + return result; } } diff --git a/src/Umbraco.Core/Services/TemplateService.cs b/src/Umbraco.Core/Services/TemplateService.cs index cfb70aefe5..169c81df75 100644 --- a/src/Umbraco.Core/Services/TemplateService.cs +++ b/src/Umbraco.Core/Services/TemplateService.cs @@ -182,7 +182,8 @@ public class TemplateService : RepositoryService, ITemplateService IQuery query = Query(); if (keys.Any()) { - query = query.Where(x => keys.Contains(x.Key)); + var keysList = keys.ToList(); + query = query.Where(x => keysList.Contains(x.Key)); } IEnumerable templates = _templateRepository.Get(query).OrderBy(x => x.Name); diff --git a/src/Umbraco.Infrastructure/Migrations/AsyncMigrationBase.cs b/src/Umbraco.Infrastructure/Migrations/AsyncMigrationBase.cs index 461a79a0b4..859bee5f18 100644 --- a/src/Umbraco.Infrastructure/Migrations/AsyncMigrationBase.cs +++ b/src/Umbraco.Infrastructure/Migrations/AsyncMigrationBase.cs @@ -53,7 +53,7 @@ public abstract partial class AsyncMigrationBase : IDiscoverable /// /// Gets the database type. /// - protected DatabaseType DatabaseType => Context.Database.DatabaseType; + protected DatabaseType DatabaseType => (DatabaseType)Context.Database.DatabaseType; /// /// Builds a Create expression. diff --git a/src/Umbraco.Infrastructure/Migrations/Expressions/Alter/Table/AlterTableBuilder.cs b/src/Umbraco.Infrastructure/Migrations/Expressions/Alter/Table/AlterTableBuilder.cs index b4f7b12563..de6c64268c 100644 --- a/src/Umbraco.Infrastructure/Migrations/Expressions/Alter/Table/AlterTableBuilder.cs +++ b/src/Umbraco.Infrastructure/Migrations/Expressions/Alter/Table/AlterTableBuilder.cs @@ -28,6 +28,7 @@ public class AlterTableBuilder : ExpressionBuilderBase? sql = _scopeAccessor.AmbientScope?.Database.SqlContext.Sql() + NPoco.Sql sql = ambientScope.Database.SqlContext.Sql() .SelectCount() .From() .Where(x => x.Id == Constants.Security.SuperUserId && x.Password == "default"); - var result = _scopeAccessor.AmbientScope?.Database.ExecuteScalar(sql); + var result = _scopeAccessor.AmbientScope.Database.ExecuteScalar(sql); var has = result != 1; if (has == false) { // found only 1 user == the default user with default password // however this always exists on uCloud, also need to check if there are other users too - result = _scopeAccessor.AmbientScope?.Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoUser"); + result = _scopeAccessor.AmbientScope.Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoUser"); has = result != 1; } scope.Complete(); diff --git a/src/Umbraco.Infrastructure/Migrations/MigrationExpressionBase.cs b/src/Umbraco.Infrastructure/Migrations/MigrationExpressionBase.cs index 117eb8fe7b..187db2d7a2 100644 --- a/src/Umbraco.Infrastructure/Migrations/MigrationExpressionBase.cs +++ b/src/Umbraco.Infrastructure/Migrations/MigrationExpressionBase.cs @@ -20,7 +20,7 @@ public abstract class MigrationExpressionBase : IMigrationExpression protected MigrationExpressionBase(IMigrationContext context) => Context = context ?? throw new ArgumentNullException(nameof(context)); - public DatabaseType DatabaseType => Context.Database.DatabaseType; + public IDatabaseType DatabaseType => Context.Database.DatabaseType; protected IMigrationContext Context { get; } diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/DictionaryDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/DictionaryDto.cs index 8d93616ac8..ed47cd7864 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/DictionaryDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/DictionaryDto.cs @@ -32,5 +32,5 @@ public class DictionaryDto // public as required to be accessible from Deploy fo [ResultColumn] [Reference(ReferenceType.Many, ColumnName = "UniqueId", ReferenceMemberName = "UniqueId")] - public List? LanguageTextDtos { get; set; } + public List LanguageTextDtos { get; set; } = []; } diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/PropertyTypeGroupDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/PropertyTypeGroupDto.cs index 6e3bd0b70c..b9fcadd073 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/PropertyTypeGroupDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/PropertyTypeGroupDto.cs @@ -41,5 +41,5 @@ internal sealed class PropertyTypeGroupDto [ResultColumn] [Reference(ReferenceType.Many, ReferenceMemberName = "PropertyTypeGroupId")] - public List? PropertyTypeDtos { get; set; } + public List PropertyTypeDtos { get; set; } = []; } diff --git a/src/Umbraco.Infrastructure/Persistence/Factories/PropertyGroupFactory.cs b/src/Umbraco.Infrastructure/Persistence/Factories/PropertyGroupFactory.cs index f0ff524666..f85178cbac 100644 --- a/src/Umbraco.Infrastructure/Persistence/Factories/PropertyGroupFactory.cs +++ b/src/Umbraco.Infrastructure/Persistence/Factories/PropertyGroupFactory.cs @@ -25,7 +25,8 @@ internal static class PropertyGroupFactory } dto.PropertyTypeDtos = propertyGroup.PropertyTypes - ?.Select(propertyType => BuildPropertyTypeDto(propertyGroup.Id, propertyType, contentTypeId)).ToList(); + ?.Select(propertyType => BuildPropertyTypeDto(propertyGroup.Id, propertyType, contentTypeId)).ToList() + ?? []; return dto; } diff --git a/src/Umbraco.Infrastructure/Persistence/ISqlContext.cs b/src/Umbraco.Infrastructure/Persistence/ISqlContext.cs index 87bc1dd81f..31088e7b52 100644 --- a/src/Umbraco.Infrastructure/Persistence/ISqlContext.cs +++ b/src/Umbraco.Infrastructure/Persistence/ISqlContext.cs @@ -2,6 +2,7 @@ using NPoco; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Infrastructure.Persistence.Mappers; using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; +using IMapperCollection = Umbraco.Cms.Infrastructure.Persistence.Mappers.IMapperCollection; namespace Umbraco.Cms.Infrastructure.Persistence; diff --git a/src/Umbraco.Infrastructure/Persistence/NPocoDatabaseExtensions.cs b/src/Umbraco.Infrastructure/Persistence/NPocoDatabaseExtensions.cs index bebf59cbe7..b36bb2ca02 100644 --- a/src/Umbraco.Infrastructure/Persistence/NPocoDatabaseExtensions.cs +++ b/src/Umbraco.Infrastructure/Persistence/NPocoDatabaseExtensions.cs @@ -241,7 +241,7 @@ public static partial class NPocoDatabaseExtensions /// /// /// - public static TConnection GetTypedConnection(IDbConnection connection) + public static TConnection GetTypedConnection(IDbConnection? connection) where TConnection : class, IDbConnection { IDbConnection? c = connection; @@ -258,7 +258,7 @@ public static partial class NPocoDatabaseExtensions c = profiled.WrappedConnection; break; default: - throw new NotSupportedException(connection.GetType().FullName); + throw new NotSupportedException(connection?.GetType().FullName); } } } diff --git a/src/Umbraco.Infrastructure/Persistence/NPocoDatabaseTypeExtensions.cs b/src/Umbraco.Infrastructure/Persistence/NPocoDatabaseTypeExtensions.cs index 4dd63c9919..611fbfe52d 100644 --- a/src/Umbraco.Infrastructure/Persistence/NPocoDatabaseTypeExtensions.cs +++ b/src/Umbraco.Infrastructure/Persistence/NPocoDatabaseTypeExtensions.cs @@ -6,10 +6,10 @@ namespace Umbraco.Cms.Infrastructure.Persistence; internal static class NPocoDatabaseTypeExtensions { [Obsolete("Usage of this method indicates a code smell.")] - public static bool IsSqlServer(this DatabaseType databaseType) + public static bool IsSqlServer(this IDatabaseType databaseType) => databaseType is not null && databaseType.GetProviderName() == "Microsoft.Data.SqlClient"; [Obsolete("Usage of this method indicates a code smell.")] - public static bool IsSqlite(this DatabaseType databaseType) + public static bool IsSqlite(this IDatabaseType databaseType) => databaseType is SQLiteDatabaseType; } diff --git a/src/Umbraco.Infrastructure/Persistence/Querying/ExpressionVisitorBase.cs b/src/Umbraco.Infrastructure/Persistence/Querying/ExpressionVisitorBase.cs index a585151929..47a64c96e3 100644 --- a/src/Umbraco.Infrastructure/Persistence/Querying/ExpressionVisitorBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/Querying/ExpressionVisitorBase.cs @@ -21,7 +21,7 @@ internal abstract class ExpressionVisitorBase /// /// Gets the list of SQL parameters. /// - protected readonly List SqlParameters = new(); + protected List SqlParameters { get; } = []; protected ExpressionVisitorBase(ISqlSyntaxProvider sqlSyntax) => SqlSyntax = sqlSyntax; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs index 1fa6d9e523..b20e9f34d0 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs @@ -57,7 +57,7 @@ internal sealed class AuditEntryRepository : EntityRepositoryBase() .Where(x => x.Id == id); - AuditEntryDto dto = Database.FirstOrDefault(sql); + AuditEntryDto? dto = Database.FirstOrDefault(sql); return dto == null ? null : AuditEntryFactory.BuildEntity(dto); } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CacheInstructionRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CacheInstructionRepository.cs index 9356ee1a7d..c623a672d7 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CacheInstructionRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CacheInstructionRepository.cs @@ -22,7 +22,12 @@ internal sealed class CacheInstructionRepository : ICacheInstructionRepository /// public int CountAll() { - Sql? sql = AmbientScope?.SqlContext.Sql().Select("COUNT(*)") + if (AmbientScope is null) + { + return 0; + } + + Sql? sql = AmbientScope.SqlContext.Sql().Select("COUNT(*)") .From(); return AmbientScope?.Database.ExecuteScalar(sql) ?? 0; @@ -50,13 +55,17 @@ internal sealed class CacheInstructionRepository : ICacheInstructionRepository /// public IEnumerable GetPendingInstructions(int lastId, int maxNumberToRetrieve) { - Sql? sql = AmbientScope?.SqlContext.Sql().SelectAll() + if (AmbientScope is null) + { + return []; + } + + Sql sql = AmbientScope.SqlContext.Sql().SelectAll() .From() .Where(dto => dto.Id > lastId) .OrderBy(dto => dto.Id); - Sql? topSql = sql?.SelectTop(maxNumberToRetrieve); - return AmbientScope?.Database.Fetch(topSql).Select(CacheInstructionFactory.BuildEntity) ?? - Array.Empty(); + Sql topSql = sql.SelectTop(maxNumberToRetrieve); + return AmbientScope.Database.Fetch(topSql).Select(CacheInstructionFactory.BuildEntity); } /// diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentNavigationRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentNavigationRepository.cs index b2553987d4..d184be555b 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentNavigationRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentNavigationRepository.cs @@ -26,7 +26,12 @@ public class ContentNavigationRepository : INavigationRepository private IEnumerable FetchNavigationDtos(Guid objectTypeKey, bool trashed) { - Sql? sql = AmbientScope?.SqlContext.Sql() + if (AmbientScope is null) + { + return Enumerable.Empty(); + } + + Sql sql = AmbientScope.SqlContext.Sql() .Select( $"n.{NodeDto.IdColumnName} as {NodeDto.IdColumnName}", $"n.{NodeDto.KeyColumnName} as {NodeDto.KeyColumnName}", @@ -40,6 +45,6 @@ public class ContentNavigationRepository : INavigationRepository .Where(n => n.NodeObjectType == objectTypeKey && n.Trashed == trashed, "n") .OrderBy(n => n.Path, "n"); // make sure that we get the parent items first - return AmbientScope?.Database.Fetch(sql) ?? Enumerable.Empty(); + return AmbientScope.Database.Fetch(sql); } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs index f4b041b629..0327521208 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs @@ -29,8 +29,11 @@ internal sealed class ContentTypeCommonRepository : IContentTypeCommonRepository /// /// Initializes a new instance of the class. /// - public ContentTypeCommonRepository(IScopeAccessor scopeAccessor, ITemplateRepository templateRepository, - AppCaches appCaches, IShortStringHelper shortStringHelper) + public ContentTypeCommonRepository( + IScopeAccessor scopeAccessor, + ITemplateRepository templateRepository, + AppCaches appCaches, + IShortStringHelper shortStringHelper) { _scopeAccessor = scopeAccessor; _templateRepository = templateRepository; @@ -57,14 +60,17 @@ internal sealed class ContentTypeCommonRepository : IContentTypeCommonRepository /// public void ClearCache() => _appCaches.RuntimeCache.Clear(CacheKey); - private Sql? Sql() => SqlContext?.Sql(); - private IEnumerable GetAllTypesInternal() { var contentTypes = new Dictionary(); + if (SqlContext is null) + { + return contentTypes.Values; + } + // get content types - Sql? sql1 = Sql()? + Sql sql1 = SqlContext.Sql() .Select(r => r.Select(x => x.NodeDto)) .From() .InnerJoin().On((ct, n) => ct.NodeId == n.NodeId) @@ -73,7 +79,7 @@ internal sealed class ContentTypeCommonRepository : IContentTypeCommonRepository List? contentTypeDtos = Database?.Fetch(sql1); // get allowed content types - Sql? sql2 = Sql()? + Sql sql2 = SqlContext.Sql() .Select() .From() .OrderBy(x => x.Id); @@ -153,8 +159,13 @@ internal sealed class ContentTypeCommonRepository : IContentTypeCommonRepository private void MapHistoryCleanup(Dictionary contentTypes) { + if (SqlContext is null) + { + return; + } + // get templates - Sql? sql1 = Sql()? + Sql sql1 = SqlContext.Sql() .Select() .From() .OrderBy(x => x.ContentTypeId); @@ -189,8 +200,13 @@ internal sealed class ContentTypeCommonRepository : IContentTypeCommonRepository private void MapTemplates(Dictionary contentTypes) { + if (SqlContext is null) + { + return; + } + // get templates - Sql? sql1 = Sql()? + Sql sql1 = SqlContext.Sql() .Select() .From() .OrderBy(x => x.ContentTypeNodeId); @@ -205,7 +221,7 @@ internal sealed class ContentTypeCommonRepository : IContentTypeCommonRepository foreach (IContentTypeComposition c in contentTypes.Values) { - if (!(c is IContentType contentType)) + if (c is not IContentType contentType) { continue; } @@ -238,8 +254,13 @@ internal sealed class ContentTypeCommonRepository : IContentTypeCommonRepository private void MapComposition(IDictionary contentTypes) { + if (SqlContext is null) + { + return; + } + // get parent/child - Sql? sql1 = Sql()? + Sql sql1 = SqlContext.Sql() .Select() .From() .OrderBy(x => x.ChildId); @@ -268,7 +289,12 @@ internal sealed class ContentTypeCommonRepository : IContentTypeCommonRepository private void MapGroupsAndProperties(IDictionary contentTypes) { - Sql? sql1 = Sql()? + if (SqlContext is null) + { + return; + } + + Sql sql1 = SqlContext.Sql() .Select() .From() .InnerJoin() @@ -278,7 +304,7 @@ internal sealed class ContentTypeCommonRepository : IContentTypeCommonRepository List? groupDtos = Database?.Fetch(sql1); - Sql? sql2 = Sql()? + Sql sql2 = SqlContext.Sql() .Select(r => r.Select(x => x.DataTypeDto, r1 => r1.Select(x => x.NodeDto))) .AndSelect() .From() @@ -329,8 +355,7 @@ internal sealed class ContentTypeCommonRepository : IContentTypeCommonRepository propertyDtos[propertyIx].ContentTypeId == contentType.Id && propertyDtos[propertyIx].PropertyTypeGroupId == group.Id) { - group.PropertyTypes?.Add(MapPropertyType(contentType, propertyDtos[propertyIx], - builtinProperties)); + group.PropertyTypes?.Add(MapPropertyType(contentType, propertyDtos[propertyIx], builtinProperties)); propertyIx++; } } @@ -380,7 +405,9 @@ internal sealed class ContentTypeCommonRepository : IContentTypeCommonRepository SortOrder = dto.SortOrder, }; - private PropertyType MapPropertyType(IContentTypeComposition contentType, PropertyTypeCommonDto dto, + private PropertyType MapPropertyType( + IContentTypeComposition contentType, + PropertyTypeCommonDto dto, IDictionary builtinProperties) { var groupId = dto.PropertyTypeGroupId; @@ -397,9 +424,7 @@ internal sealed class ContentTypeCommonRepository : IContentTypeCommonRepository memberType.SetMemberCanViewProperty(dto.Alias, dto.ViewOnProfile); } - return new - PropertyType(_shortStringHelper, dto.DataTypeDto.EditorAlias, storageType, readonlyStorageType, - dto.Alias) + return new PropertyType(_shortStringHelper, dto.DataTypeDto.EditorAlias, storageType, readonlyStorageType, dto.Alias) { Description = dto.Description, DataTypeId = dto.DataTypeId, diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentVersionRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentVersionRepository.cs index 651cd2f743..043f99eeb8 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentVersionRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentVersionRepository.cs @@ -25,17 +25,22 @@ internal sealed class DocumentVersionRepository : IDocumentVersionRepository /// public IReadOnlyCollection? GetDocumentVersionsEligibleForCleanup() { - Sql? query = _scopeAccessor.AmbientScope?.SqlContext.Sql(); + if (_scopeAccessor.AmbientScope is null) + { + return []; + } - query?.Select(@"umbracoDocument.nodeId as contentId, - umbracoContent.contentTypeId as contentTypeId, - umbracoContentVersion.id as versionId, - umbracoContentVersion.userId as userId, - umbracoContentVersion.versionDate as versionDate, - umbracoDocumentVersion.published as currentPublishedVersion, - umbracoContentVersion.[current] as currentDraftVersion, - umbracoContentVersion.preventCleanup as preventCleanup, - umbracoUser.userName as username") + Sql query = _scopeAccessor.AmbientScope.SqlContext.Sql(); + + query.Select(@"umbracoDocument.nodeId as contentId, + umbracoContent.contentTypeId as contentTypeId, + umbracoContentVersion.id as versionId, + umbracoContentVersion.userId as userId, + umbracoContentVersion.versionDate as versionDate, + umbracoDocumentVersion.published as currentPublishedVersion, + umbracoContentVersion.[current] as currentDraftVersion, + umbracoContentVersion.preventCleanup as preventCleanup, + umbracoUser.userName as username") .From() .InnerJoin() .On(left => left.NodeId, right => right.NodeId) @@ -49,7 +54,7 @@ internal sealed class DocumentVersionRepository : IDocumentVersionRepository .Where(x => !x.PreventCleanup) // Never delete "pinned" versions .Where(x => !x.Published); // Never delete published version - List? results = _scopeAccessor.AmbientScope?.Database.Fetch(query); + List results = _scopeAccessor.AmbientScope.Database.Fetch(query); EnsureUtcDates(results); return results; } @@ -57,9 +62,14 @@ internal sealed class DocumentVersionRepository : IDocumentVersionRepository /// public IReadOnlyCollection? GetCleanupPolicies() { - Sql? query = _scopeAccessor.AmbientScope?.SqlContext.Sql(); + if (_scopeAccessor.AmbientScope is null) + { + return []; + } - query?.Select() + Sql query = _scopeAccessor.AmbientScope.SqlContext.Sql(); + + query.Select() .From(); return _scopeAccessor.AmbientScope?.Database.Fetch(query); @@ -68,17 +78,23 @@ internal sealed class DocumentVersionRepository : IDocumentVersionRepository /// public IEnumerable? GetPagedItemsByContentId(int contentId, long pageIndex, int pageSize, out long totalRecords, int? languageId = null) { - Sql? query = _scopeAccessor.AmbientScope?.SqlContext.Sql(); + if (_scopeAccessor.AmbientScope is null) + { + totalRecords = 0; + return []; + } - query?.Select(@"umbracoDocument.nodeId as contentId, - umbracoContent.contentTypeId as contentTypeId, - umbracoContentVersion.id as versionId, - umbracoContentVersion.userId as userId, - umbracoContentVersion.versionDate as versionDate, - umbracoDocumentVersion.published as currentPublishedVersion, - umbracoContentVersion.[current] as currentDraftVersion, - umbracoContentVersion.preventCleanup as preventCleanup, - umbracoUser.userName as username") + Sql query = _scopeAccessor.AmbientScope.SqlContext.Sql(); + + query.Select(@"umbracoDocument.nodeId as contentId, + umbracoContent.contentTypeId as contentTypeId, + umbracoContentVersion.id as versionId, + umbracoContentVersion.userId as userId, + umbracoContentVersion.versionDate as versionDate, + umbracoDocumentVersion.published as currentPublishedVersion, + umbracoContentVersion.[current] as currentDraftVersion, + umbracoContentVersion.preventCleanup as preventCleanup, + umbracoUser.userName as username") .From() .InnerJoin() .On(left => left.NodeId, right => right.NodeId) @@ -94,17 +110,17 @@ internal sealed class DocumentVersionRepository : IDocumentVersionRepository // TODO: If there's not a better way to write this then we need a better way to write this. query = languageId.HasValue - ? query?.Where(x => x.LanguageId == languageId.Value) - : query?.Where("umbracoContentVersionCultureVariation.languageId is null"); + ? query.Where(x => x.LanguageId == languageId.Value) + : query.Where("umbracoContentVersionCultureVariation.languageId is null"); - query = query?.OrderByDescending(x => x.Id); + query = query.OrderByDescending(x => x.Id); - Page? page = - _scopeAccessor.AmbientScope?.Database.Page(pageIndex + 1, pageSize, query); + Page page = + _scopeAccessor.AmbientScope.Database.Page(pageIndex + 1, pageSize, query); - totalRecords = page?.TotalItems ?? 0; + totalRecords = page.TotalItems; - List? results = page?.Items; + List results = page.Items; EnsureUtcDates(results); return results; } @@ -115,6 +131,11 @@ internal sealed class DocumentVersionRepository : IDocumentVersionRepository /// public void DeleteVersions(IEnumerable versionIds) { + if (_scopeAccessor.AmbientScope is null) + { + return; + } + foreach (IEnumerable group in versionIds.InGroupsOf(Constants.Sql.MaxParameterCount)) { var groupedVersionIds = group.ToList(); @@ -124,32 +145,37 @@ internal sealed class DocumentVersionRepository : IDocumentVersionRepository * Can use test PerformContentVersionCleanup_WithNoKeepPeriods_DeletesEverythingExceptActive to try things out. */ - Sql? query = _scopeAccessor.AmbientScope?.SqlContext.Sql() + Sql query = _scopeAccessor.AmbientScope.SqlContext.Sql() .Delete() .WhereIn(x => x.VersionId, groupedVersionIds); - _scopeAccessor.AmbientScope?.Database.Execute(query); + _scopeAccessor.AmbientScope.Database.Execute(query); - query = _scopeAccessor.AmbientScope?.SqlContext.Sql() + query = _scopeAccessor.AmbientScope.SqlContext.Sql() .Delete() .WhereIn(x => x.VersionId, groupedVersionIds); - _scopeAccessor.AmbientScope?.Database.Execute(query); + _scopeAccessor.AmbientScope.Database.Execute(query); - query = _scopeAccessor.AmbientScope?.SqlContext.Sql() + query = _scopeAccessor.AmbientScope.SqlContext.Sql() .Delete() .WhereIn(x => x.Id, groupedVersionIds); - _scopeAccessor.AmbientScope?.Database.Execute(query); + _scopeAccessor.AmbientScope.Database.Execute(query); - query = _scopeAccessor.AmbientScope?.SqlContext.Sql() + query = _scopeAccessor.AmbientScope.SqlContext.Sql() .Delete() .WhereIn(x => x.Id, groupedVersionIds); - _scopeAccessor.AmbientScope?.Database.Execute(query); + _scopeAccessor.AmbientScope.Database.Execute(query); } } /// public void SetPreventCleanup(int versionId, bool preventCleanup) { - Sql? query = _scopeAccessor.AmbientScope?.SqlContext.Sql() + if (_scopeAccessor.AmbientScope is null) + { + return; + } + + Sql? query = _scopeAccessor.AmbientScope.SqlContext.Sql() .Update(x => x.Set(y => y.PreventCleanup, preventCleanup)) .Where(x => x.Id == versionId); @@ -159,17 +185,22 @@ internal sealed class DocumentVersionRepository : IDocumentVersionRepository /// public ContentVersionMeta? Get(int versionId) { - Sql? query = _scopeAccessor.AmbientScope?.SqlContext.Sql(); + if (_scopeAccessor.AmbientScope is null) + { + return null; + } - query?.Select(@"umbracoDocument.nodeId as contentId, - umbracoContent.contentTypeId as contentTypeId, - umbracoContentVersion.id as versionId, - umbracoContentVersion.userId as userId, - umbracoContentVersion.versionDate as versionDate, - umbracoDocumentVersion.published as currentPublishedVersion, - umbracoContentVersion.[current] as currentDraftVersion, - umbracoContentVersion.preventCleanup as preventCleanup, - umbracoUser.userName as username") + Sql query = _scopeAccessor.AmbientScope.SqlContext.Sql(); + + query.Select(@"umbracoDocument.nodeId as contentId, + umbracoContent.contentTypeId as contentTypeId, + umbracoContentVersion.id as versionId, + umbracoContentVersion.userId as userId, + umbracoContentVersion.versionDate as versionDate, + umbracoDocumentVersion.published as currentPublishedVersion, + umbracoContentVersion.[current] as currentDraftVersion, + umbracoContentVersion.preventCleanup as preventCleanup, + umbracoUser.userName as username") .From() .InnerJoin() .On(left => left.NodeId, right => right.NodeId) @@ -181,14 +212,14 @@ internal sealed class DocumentVersionRepository : IDocumentVersionRepository .On(left => left.Id, right => right.UserId) .Where(x => x.Id == versionId); - ContentVersionMeta? result = _scopeAccessor.AmbientScope?.Database.Single(query); - result?.EnsureUtc(); + ContentVersionMeta result = _scopeAccessor.AmbientScope.Database.Single(query); + result.EnsureUtc(); return result; } - private static void EnsureUtcDates(IEnumerable? versions) + private static void EnsureUtcDates(IEnumerable versions) { - foreach (ContentVersionMeta version in versions ?? []) + foreach (ContentVersionMeta version in versions) { version.EnsureUtc(); } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs index 5cec8191fa..117e8451fd 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs @@ -134,7 +134,7 @@ internal class EntityContainerRepository : EntityRepositoryBase(Sql().SelectAll() + NodeDto? nodeDto = Database.FirstOrDefault(Sql().SelectAll() .From() .InnerJoin("parent") .On( @@ -147,7 +147,7 @@ internal class EntityContainerRepository : EntityRepositoryBase(Sql().SelectAll() + NodeDto? nodeDto = Database.FirstOrDefault(Sql().SelectAll() .From() .Where(dto => dto.Text == name && dto.NodeObjectType == NodeObjectTypeId && dto.ParentId == parentId)); @@ -163,7 +163,7 @@ internal class EntityContainerRepository : EntityRepositoryBase(Sql().SelectAll() + NodeDto? nodeDto = Database.FirstOrDefault(Sql().SelectAll() .From() .Where(dto => dto.NodeId == entity.Id && dto.NodeObjectType == entity.ContainerObjectType)); @@ -219,7 +219,7 @@ internal class EntityContainerRepository : EntityRepositoryBase(Sql().SelectAll() + NodeDto? nodeDto = Database.FirstOrDefault(Sql().SelectAll() .From() .Where(dto => dto.ParentId == entity.ParentId && dto.Text == entity.Name && @@ -302,7 +302,7 @@ internal class EntityContainerRepository : EntityRepositoryBase(Sql().SelectAll() + NodeDto? dupNodeDto = Database.FirstOrDefault(Sql().SelectAll() .From() .Where(dto => dto.ParentId == entity.ParentId && dto.Text == entity.Name && diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs index abde201331..382e0a43e9 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs @@ -1,3 +1,4 @@ +using System.Collections; using NPoco; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; @@ -238,10 +239,10 @@ internal sealed class EntityRepository : RepositoryBase, IEntityRepositoryExtend foreach (Tuple filterClause in filter.GetWhereClauses()) { // We need to offset by one for each non-array parameter in the filter clause. - // If a query is created using Contains or some other set based operation, we'll get both the array and the - // items in the array provided in the where clauses. It's only the latter that count for applying parameters + // If a query is created using Contains or some other set based operation, we'll get both the collection and the + // items in the collection provided in the where clauses. It's only the latter that count for applying parameters // to the SQL statement, and hence we should only offset by them. - beforeAfterParameterIndexOffset += filterClause.Item2.Count(x => !x.GetType().IsArray); + beforeAfterParameterIndexOffset += filterClause.Item2.Count(x => x.GetType().GetInterface(nameof(IEnumerable)) is null); } } @@ -409,7 +410,7 @@ internal sealed class EntityRepository : RepositoryBase, IEntityRepositoryExtend public int ReserveId(Guid key) { - NodeDto node; + NodeDto? node; Sql sql = SqlContext.Sql() .Select() @@ -417,7 +418,7 @@ internal sealed class EntityRepository : RepositoryBase, IEntityRepositoryExtend .Where(x => x.UniqueId == key && x.NodeObjectType == Constants.ObjectTypes.IdReservation); node = Database.SingleOrDefault(sql); - if (node != null) + if (node is not null) { throw new InvalidOperationException("An identifier has already been reserved for this Udi."); } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LongRunningOperationRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LongRunningOperationRepository.cs index 0f53cb1dd3..1f02296be1 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LongRunningOperationRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LongRunningOperationRepository.cs @@ -48,7 +48,7 @@ internal class LongRunningOperationRepository : RepositoryBase, ILongRunningOper .From() .Where(x => x.Id == id); - LongRunningOperationDto dto = await Database.FirstOrDefaultAsync(sql); + LongRunningOperationDto? dto = await Database.FirstOrDefaultAsync(sql); return dto == null ? null : MapDtoToEntity(dto); } @@ -60,7 +60,7 @@ internal class LongRunningOperationRepository : RepositoryBase, ILongRunningOper .From() .Where(x => x.Id == id); - LongRunningOperationDto dto = await Database.FirstOrDefaultAsync(sql); + LongRunningOperationDto? dto = await Database.FirstOrDefaultAsync(sql); return dto == null ? null : MapDtoToEntity(dto); } @@ -79,9 +79,12 @@ internal class LongRunningOperationRepository : RepositoryBase, ILongRunningOper if (statuses.Length > 0) { var includeStale = statuses.Contains(LongRunningOperationStatus.Stale); - string[] possibleStaleStatuses = - [nameof(LongRunningOperationStatus.Enqueued), nameof(LongRunningOperationStatus.Running)]; - IEnumerable statusList = statuses.Except([LongRunningOperationStatus.Stale]).Select(s => s.ToString()); + var possibleStaleStatuses = new List + { + nameof(LongRunningOperationStatus.Enqueued), + nameof(LongRunningOperationStatus.Running) + }; + var statusList = statuses.Except([LongRunningOperationStatus.Stale]).Select(s => s.ToString()).ToList(); DateTime now = _timeProvider.GetUtcNow().UtcDateTime; sql = sql.Where(x => diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/NodeCountRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/NodeCountRepository.cs index 47d43a9a4e..d589da5084 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/NodeCountRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/NodeCountRepository.cs @@ -16,17 +16,27 @@ public class NodeCountRepository : INodeCountRepository /// public int GetNodeCount(Guid nodeType) { - Sql? query = _scopeAccessor.AmbientScope?.Database.SqlContext.Sql() + if (_scopeAccessor.AmbientScope is null) + { + return 0; + } + + Sql query = _scopeAccessor.AmbientScope.Database.SqlContext.Sql() .SelectCount() .From() .Where(x => x.NodeObjectType == nodeType && x.Trashed == false); - return _scopeAccessor.AmbientScope?.Database.ExecuteScalar(query) ?? 0; + return _scopeAccessor.AmbientScope.Database.ExecuteScalar(query); } public int GetMediaCount() { - Sql? query = _scopeAccessor.AmbientScope?.Database.SqlContext.Sql() + if (_scopeAccessor.AmbientScope is null) + { + return 0; + } + + Sql query = _scopeAccessor.AmbientScope.Database.SqlContext.Sql() .SelectCount() .From() .InnerJoin() @@ -37,6 +47,6 @@ public class NodeCountRepository : INodeCountRepository .Where(x => !x.Trashed) .Where(x => x.Alias != Constants.Conventions.MediaTypes.Folder); - return _scopeAccessor.AmbientScope?.Database.ExecuteScalar(query) ?? 0; + return _scopeAccessor.AmbientScope.Database.ExecuteScalar(query); } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/NotificationsRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/NotificationsRepository.cs index 8e279735d5..f9427ccb7f 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/NotificationsRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/NotificationsRepository.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using NPoco; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; @@ -17,10 +18,15 @@ public class NotificationsRepository : INotificationsRepository private IScope? AmbientScope => _scopeAccessor.AmbientScope; - public IEnumerable? GetUsersNotifications(IEnumerable userIds, string? action, IEnumerable nodeIds, Guid objectType) + public IEnumerable GetUsersNotifications(IEnumerable userIds, string? action, IEnumerable nodeIds, Guid objectType) { + if (AmbientScope is null) + { + return []; + } + var nodeIdsA = nodeIds.ToArray(); - Sql? sql = AmbientScope?.SqlContext.Sql() + Sql sql = AmbientScope.SqlContext.Sql() .Select( "DISTINCT umbracoNode.id nodeId, umbracoUser.id userId, umbracoNode.nodeObjectType, umbracoUser2NodeNotify.action") .From() @@ -31,20 +37,25 @@ public class NotificationsRepository : INotificationsRepository .Where(x => x.Action == action); // on the specified action if (nodeIdsA.Length > 0) { - sql? + sql .WhereIn(x => x.NodeId, nodeIdsA); // for the specified nodes } - sql? + sql .OrderBy(x => x.Id) .OrderBy(dto => dto.NodeId); - return AmbientScope?.Database.Fetch(sql) + return AmbientScope.Database.Fetch(sql) .Select(x => new Notification(x.NodeId, x.UserId, x.Action, objectType)); } - public IEnumerable? GetUserNotifications(IUser user) + public IEnumerable GetUserNotifications(IUser user) { - Sql? sql = AmbientScope?.SqlContext.Sql() + if (AmbientScope is null) + { + return []; + } + + Sql sql = AmbientScope.SqlContext.Sql() .Select( "DISTINCT umbracoNode.id AS nodeId, umbracoUser2NodeNotify.userId, umbracoNode.nodeObjectType, umbracoUser2NodeNotify.action") .From() @@ -53,21 +64,33 @@ public class NotificationsRepository : INotificationsRepository .Where(dto => dto.UserId == user.Id) .OrderBy(dto => dto.NodeId); - List? dtos = AmbientScope?.Database.Fetch(sql); + List dtos = AmbientScope.Database.Fetch(sql); // need to map the results - return dtos?.Select(d => new Notification(d.NodeId, d.UserId, d.Action, d.NodeObjectType)).ToList(); + return dtos.Select(d => new Notification(d.NodeId, d.UserId, d.Action, d.NodeObjectType)).ToList(); } public IEnumerable SetNotifications(IUser user, IEntity entity, string[] actions) { DeleteNotifications(user, entity); - return actions.Select(action => CreateNotification(user, entity, action)).ToList(); + return actions + .Select(action => + { + TryCreateNotification(user, entity, action, out Notification? n); + return n; + }) + .WhereNotNull() + .ToList(); } - public IEnumerable? GetEntityNotifications(IEntity entity) + public IEnumerable GetEntityNotifications(IEntity entity) { - Sql? sql = AmbientScope?.SqlContext.Sql() + if (AmbientScope is null) + { + return []; + } + + Sql sql = AmbientScope.SqlContext.Sql() .Select( "DISTINCT umbracoNode.id as nodeId, umbracoUser2NodeNotify.userId, umbracoNode.nodeObjectType, umbracoUser2NodeNotify.action") .From() @@ -76,10 +99,10 @@ public class NotificationsRepository : INotificationsRepository .Where(dto => dto.NodeId == entity.Id) .OrderBy(dto => dto.NodeId); - List? dtos = AmbientScope?.Database.Fetch(sql); + List dtos = AmbientScope.Database.Fetch(sql); // need to map the results - return dtos?.Select(d => new Notification(d.NodeId, d.UserId, d.Action, d.NodeObjectType)).ToList(); + return dtos.Select(d => new Notification(d.NodeId, d.UserId, d.Action, d.NodeObjectType)).ToList(); } public int DeleteNotifications(IEntity entity) => @@ -95,16 +118,23 @@ public class NotificationsRepository : INotificationsRepository "WHERE userId = @userId AND nodeId = @nodeId", new { userId = user.Id, nodeId = entity.Id }) ?? 0; - public Notification CreateNotification(IUser user, IEntity entity, string action) + public bool TryCreateNotification(IUser user, IEntity entity, string action, [NotNullWhen(true)] out Notification? notification) { - Sql? sql = AmbientScope?.SqlContext.Sql() + if (AmbientScope is null) + { + notification = null; + return false; + } + + Sql? sql = AmbientScope.SqlContext.Sql() .Select("DISTINCT nodeObjectType") .From() .Where(nodeDto => nodeDto.NodeId == entity.Id); - Guid? nodeType = AmbientScope?.Database.ExecuteScalar(sql); + Guid nodeType = AmbientScope.Database.ExecuteScalar(sql); var dto = new User2NodeNotifyDto { Action = action, NodeId = entity.Id, UserId = user.Id }; - AmbientScope?.Database.Insert(dto); - return new Notification(dto.NodeId, dto.UserId, dto.Action, nodeType); + AmbientScope.Database.Insert(dto); + notification = new Notification(dto.NodeId, dto.UserId, dto.Action, nodeType); + return true; } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs index 5a3de2ab14..5fb0c5755c 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs @@ -69,17 +69,19 @@ internal sealed class PermissionRepository : EntityRepositoryBase entityGroup in entityIds.InGroupsOf(Constants.Sql.MaxParameterCount - - userGroupIds.Length)) + foreach (IEnumerable entityGroup in entityIds.InGroupsOf(Constants.Sql.MaxParameterCount - userGroupIds.Length)) { + // Need to use a List here because the expression tree cannot convert the array when used in Contains. + // See ExpressionTests.Sql_In(). + List userGroupsIdsAsList = [.. userGroupIds]; Sql sql = Sql() .Select("gp").AndSelect("ug.id as userGroupId, en.id as entityId") .From("ug") .InnerJoin("gp") - .On((left, right) => left.UserGroupKey == right.Key && userGroupIds.Contains(right.Id), "gp", "ug") + .On((left, right) => left.UserGroupKey == right.Key && userGroupsIdsAsList.Contains(right.Id), "gp", "ug") .InnerJoin("en") .On((left, right) => left.UniqueId == right.UniqueId, "gp", "en") - .Where(en => entityGroup.Contains(en.NodeId), "en"); + .Where(en => entityGroup.Contains(en.NodeId), "en"); List permissions = AmbientScope.Database.Fetch(sql); diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs index 9de75c7af6..9aa212d058 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs @@ -474,7 +474,7 @@ internal sealed class TemplateRepository : EntityRepositoryBase, } //Get TemplateDto from db to get the Primary key of the entity - TemplateDto templateDto = Database.SingleOrDefault("WHERE nodeId = @Id", new {entity.Id}); + TemplateDto templateDto = Database.Single("WHERE nodeId = @Id", new {entity.Id}); //Save updated entity to db template.UpdateDate = DateTime.UtcNow; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs index 34b74ae3f4..fb66fd8265 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs @@ -61,8 +61,14 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement bool filterMustBeIsDependency, out long totalRecords) { + if (_scopeAccessor.AmbientScope is null) + { + totalRecords = 0; + return []; + } + Sql innerUnionSql = GetInnerUnionSql(); - Sql? sql = _scopeAccessor.AmbientScope?.Database.SqlContext.Sql().SelectDistinct( + Sql sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().SelectDistinct( "[x].[otherId] as nodeId", "[n].[uniqueId] as nodeKey", "[n].[text] as nodeName", @@ -104,7 +110,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement if (filterMustBeIsDependency) { - sql = sql?.Where(rt => rt.IsDependency, "x"); + sql = sql.Where(rt => rt.IsDependency, "x"); } // find the count before ordering @@ -116,7 +122,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement if (totalRecords > 0) { // Ordering is required for paging - sql = sql?.OrderBy(x => x.Alias, "x"); + sql = sql.OrderBy(x => x.Alias, "x"); pagedResult = _scopeAccessor.AmbientScope?.Database.SkipTake(skip, take, sql).ToArray() ?? @@ -138,8 +144,14 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement bool filterMustBeIsDependency, out long totalRecords) { + if (_scopeAccessor.AmbientScope is null) + { + totalRecords = 0; + return []; + } + Sql innerUnionSql = GetInnerUnionSql(); - Sql? sql = _scopeAccessor.AmbientScope?.Database.SqlContext.Sql().SelectDistinct( + Sql sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().SelectDistinct( "[x].[id] as nodeId", "[n].[uniqueId] as nodeKey", "[n].[text] as nodeName", @@ -165,12 +177,12 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement aliasRight: "ctn"); if (keys.Any()) { - sql = sql?.Where(x => keys.Contains(x.UniqueId), "n"); + sql = sql.Where(x => keys.Contains(x.UniqueId), "n"); } if (filterMustBeIsDependency) { - sql = sql?.Where(rt => rt.IsDependency, "x"); + sql = sql.Where(rt => rt.IsDependency, "x"); } // find the count before ordering @@ -182,7 +194,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement if (totalRecords > 0) { // Ordering is required for paging - sql = sql?.OrderBy(x => x.Alias, "x"); + sql = sql.OrderBy(x => x.Alias, "x"); pagedResult = _scopeAccessor.AmbientScope?.Database.SkipTake(skip, take, sql).ToArray() ?? @@ -205,10 +217,10 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { if (_scopeAccessor.AmbientScope is null) { - throw new InvalidOperationException("Can not execute without a valid AmbientScope"); + throw new InvalidOperationException("Cannot execute without a valid AmbientScope"); } - Sql? sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql() + Sql sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql() .SelectDistinct(node => node.UniqueId) .From() .InnerJoin() @@ -247,22 +259,28 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement bool filterMustBeIsDependency, out long totalRecords) { - SqlSyntax.ISqlSyntaxProvider? syntax = _scopeAccessor.AmbientScope?.Database.SqlContext.SqlSyntax; + if (_scopeAccessor.AmbientScope is null) + { + totalRecords = 0; + return []; + } + + SqlSyntax.ISqlSyntaxProvider syntax = _scopeAccessor.AmbientScope.Database.SqlContext.SqlSyntax; // Gets the path of the parent with ",%" added - Sql? subsubQuery = _scopeAccessor.AmbientScope?.Database.SqlContext.Sql() + Sql subsubQuery = _scopeAccessor.AmbientScope.Database.SqlContext.Sql() .Select(syntax?.GetConcat("[node].[path]", "',%'")) .From("node") .Where(x => x.UniqueId == parentKey, "node"); // Gets the descendants of the parent node - Sql? subQuery = _scopeAccessor.AmbientScope?.Database.SqlContext.Sql() + Sql subQuery = _scopeAccessor.AmbientScope.Database.SqlContext.Sql() .Select(x => x.NodeId) .From() .WhereLike(x => x.Path, subsubQuery); Sql innerUnionSql = GetInnerUnionSql(); - Sql? sql = _scopeAccessor.AmbientScope?.Database.SqlContext.Sql().SelectDistinct( + Sql sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().SelectDistinct( "[x].[id] as nodeId", "[n].[uniqueId] as nodeKey", "[n].[text] as nodeName", @@ -298,14 +316,14 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement (left, right) => left.NodeId == right.NodeId, aliasLeft: "n", aliasRight: "d"); - sql = sql?.WhereIn( - (System.Linq.Expressions.Expression>)(x => x.NodeId), + sql = sql.WhereIn( + (Expression>)(x => x.NodeId), subQuery, "n"); if (filterMustBeIsDependency) { - sql = sql?.Where(rt => rt.IsDependency, "x"); + sql = sql.Where(rt => rt.IsDependency, "x"); } // find the count before ordering @@ -317,7 +335,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement if (totalRecords > 0) { // Ordering is required for paging - sql = sql?.OrderBy(x => x.Alias, "x"); + sql = sql.OrderBy(x => x.Alias, "x"); pagedResult = _scopeAccessor.AmbientScope?.Database.SkipTake(skip, take, sql).ToArray() ?? diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserDataRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserDataRepository.cs index be142838a6..8086a659ab 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserDataRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserDataRepository.cs @@ -17,13 +17,18 @@ internal sealed class UserDataRepository : IUserDataRepository public async Task GetAsync(Guid key) { - Sql? sql = _scopeAccessor.AmbientScope?.Database.SqlContext.Sql() + if (_scopeAccessor.AmbientScope is null) + { + return null; + } + + Sql sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql() .Select() .From() .Where(dataDto => dataDto.Key == key) .OrderBy(dto => dto.Identifier); // need to order to skiptake; - UserDataDto? dto = await _scopeAccessor.AmbientScope?.Database.FirstOrDefaultAsync(sql)!; + UserDataDto? dto = await _scopeAccessor.AmbientScope.Database.FirstOrDefaultAsync(sql)!; return dto is null ? null : Map(dto); } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs index 8f69253526..4c68bc5189 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs @@ -20,6 +20,7 @@ using Umbraco.Cms.Infrastructure.Persistence.Mappers; using Umbraco.Cms.Infrastructure.Persistence.Querying; using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; +using IMapperCollection = Umbraco.Cms.Infrastructure.Persistence.Mappers.IMapperCollection; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; /// diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/WebhookRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/WebhookRepository.cs index 77c5912c5c..978043eeaa 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/WebhookRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/WebhookRepository.cs @@ -16,11 +16,20 @@ public class WebhookRepository : IWebhookRepository public async Task> GetAllAsync(int skip, int take) { - Sql? sql = _scopeAccessor.AmbientScope?.Database.SqlContext.Sql() + if (_scopeAccessor.AmbientScope is null) + { + return new PagedModel + { + Items = Enumerable.Empty(), + Total = 0, + }; + } + + Sql? sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql() .Select() .From(); - List? webhookDtos = await _scopeAccessor.AmbientScope?.Database.FetchAsync(sql)!; + List? webhookDtos = await _scopeAccessor.AmbientScope.Database.FetchAsync(sql); return new PagedModel { @@ -48,19 +57,33 @@ public class WebhookRepository : IWebhookRepository public async Task GetAsync(Guid key) { - Sql? sql = _scopeAccessor.AmbientScope?.Database.SqlContext.Sql() + if (_scopeAccessor.AmbientScope is null) + { + return null; + } + + Sql? sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql() .Select() .From() .Where(x => x.Key == key); - WebhookDto? webhookDto = await _scopeAccessor.AmbientScope?.Database.FirstOrDefaultAsync(sql)!; + WebhookDto? webhookDto = await _scopeAccessor.AmbientScope.Database.FirstOrDefaultAsync(sql); return webhookDto is null ? null : await DtoToEntity(webhookDto); } public async Task> GetByIdsAsync(IEnumerable keys) { - Sql? sql = _scopeAccessor.AmbientScope?.Database.SqlContext.Sql() + if (_scopeAccessor.AmbientScope is null) + { + return new PagedModel + { + Items = Enumerable.Empty(), + Total = 0, + }; + } + + Sql? sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql() .Select() .From() .WhereIn(x => x.Key, keys); @@ -76,7 +99,16 @@ public class WebhookRepository : IWebhookRepository public async Task> GetByAliasAsync(string alias) { - Sql? sql = _scopeAccessor.AmbientScope?.Database.SqlContext.Sql() + if (_scopeAccessor.AmbientScope is null) + { + return new PagedModel + { + Items = Enumerable.Empty(), + Total = 0, + }; + } + + Sql sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql() .SelectAll() .From() .InnerJoin() diff --git a/src/Umbraco.Infrastructure/Persistence/SqlContext.cs b/src/Umbraco.Infrastructure/Persistence/SqlContext.cs index 57e82770c6..d3e2c9c6e9 100644 --- a/src/Umbraco.Infrastructure/Persistence/SqlContext.cs +++ b/src/Umbraco.Infrastructure/Persistence/SqlContext.cs @@ -3,6 +3,7 @@ using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Infrastructure.Persistence.Mappers; using Umbraco.Cms.Infrastructure.Persistence.Querying; using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; +using IMapperCollection = Umbraco.Cms.Infrastructure.Persistence.Mappers.IMapperCollection; namespace Umbraco.Cms.Infrastructure.Persistence; diff --git a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabase.cs b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabase.cs index 3abec76ec2..158c272539 100644 --- a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabase.cs +++ b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabase.cs @@ -79,7 +79,10 @@ public class UmbracoDatabase : Database, IUmbracoDatabase if (_mapperCollection != null) { - Mappers.AddRange(_mapperCollection); + foreach (IMapper mapper in _mapperCollection) + { + Mappers.Add(mapper); + } } InitCommandTimeout(); diff --git a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseFactory.cs b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseFactory.cs index d8753bfd37..5ef99e9b54 100644 --- a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseFactory.cs +++ b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseFactory.cs @@ -9,6 +9,7 @@ using Umbraco.Cms.Infrastructure.Migrations.Install; using Umbraco.Cms.Infrastructure.Persistence.Mappers; using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; using Umbraco.Extensions; +using IMapperCollection = Umbraco.Cms.Infrastructure.Persistence.Mappers.IMapperCollection; using MapperCollection = NPoco.MapperCollection; namespace Umbraco.Cms.Infrastructure.Persistence; diff --git a/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs b/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs index 93579d0d4e..32b6137391 100644 --- a/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs +++ b/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs @@ -278,7 +278,12 @@ public class BackOfficeUserStore : } using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); - IQuery query = _scopeProvider.CreateQuery().Where(x => ids.Contains(x.Id)); + + // Need to use a List here because the expression tree cannot convert the array when used in Contains. + // See ExpressionTests.Sql_In(). + List idsAsList = [.. ids]; + IQuery query = _scopeProvider.CreateQuery().Where(x => idsAsList.Contains(x.Id)); + IEnumerable users = _userRepository.Get(query); return Task.FromResult(users); diff --git a/tests/Umbraco.Tests.Common/TestHelpers/TestDatabase.cs b/tests/Umbraco.Tests.Common/TestHelpers/TestDatabase.cs index 2d387dcdff..f5f0bd6d07 100644 --- a/tests/Umbraco.Tests.Common/TestHelpers/TestDatabase.cs +++ b/tests/Umbraco.Tests.Common/TestHelpers/TestDatabase.cs @@ -1,13 +1,10 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using System.Collections; -using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Linq.Expressions; -using System.Threading.Tasks; using Microsoft.Extensions.Options; using Moq; using NPoco; @@ -89,6 +86,9 @@ public class TestDatabase : IUmbracoDatabase public bool EnableSqlCount { get; set; } public int SqlCount { get; } + IMapperCollection IDatabaseConfig.Mappers { get => Mappers; set => throw new NotImplementedException(); } + + IDatabaseType IDatabaseConfig.DatabaseType => DatabaseType; public int BulkInsertRecords(IEnumerable records) => throw new NotImplementedException(); public bool IsUmbracoInstalled() => true; @@ -154,15 +154,19 @@ public class TestDatabase : IUmbracoDatabase return default; } - public Task ExecuteScalarAsync(string sql, params object[] args) => throw new NotImplementedException(); + public Task ExecuteScalarAsync(string sql, CancellationToken token) => throw new NotImplementedException(); - public Task ExecuteScalarAsync(Sql sql) => throw new NotImplementedException(); + public Task ExecuteScalarAsync(string sql, object[] args, CancellationToken token) => throw new NotImplementedException(); - public Task ExecuteAsync(string sql, params object[] args) => throw new NotImplementedException(); + public Task ExecuteScalarAsync(Sql sql, CancellationToken token) => throw new NotImplementedException(); - public Task ExecuteAsync(Sql sql) => throw new NotImplementedException(); + public Task ExecuteAsync(string sql, CancellationToken token) => throw new NotImplementedException(); - public Task InsertAsync(string tableName, string primaryKeyName, object poco) => + public Task ExecuteAsync(string sql, object[] args, CancellationToken token) => throw new NotImplementedException(); + + public Task ExecuteAsync(Sql sql, CancellationToken token) => throw new NotImplementedException(); + + public Task InsertAsync(string tableName, string primaryKeyName, object poco, CancellationToken token) => throw new NotImplementedException(); public object Insert(string tableName, string primaryKeyName, bool autoIncrement, T poco) => @@ -172,34 +176,35 @@ public class TestDatabase : IUmbracoDatabase public object Insert(T poco) => throw new NotImplementedException(); - public void InsertBulk(IEnumerable pocos, InsertBulkOptions? options = null) => + public void InsertBulk(IEnumerable pocos, InsertBulkOptions? options) => throw new NotImplementedException(); - public Task InsertAsync(T poco) => throw new NotImplementedException(); + public Task InsertAsync(T poco, CancellationToken token) => throw new NotImplementedException(); - public Task InsertBulkAsync(IEnumerable pocos, InsertBulkOptions options = null) => + public Task InsertBulkAsync(IEnumerable pocos, InsertBulkOptions? options, CancellationToken token) => throw new NotImplementedException(); - public Task InsertBatchAsync(IEnumerable pocos, BatchOptions options = null) => + public Task InsertBatchAsync(IEnumerable pocos, BatchOptions? options, CancellationToken token) => throw new NotImplementedException(); - public Task UpdateAsync(object poco) => throw new NotImplementedException(); + public Task UpdateAsync(object poco, CancellationToken token) => throw new NotImplementedException(); - public Task UpdateAsync(object poco, IEnumerable columns) => throw new NotImplementedException(); + public Task UpdateAsync(object poco, IEnumerable columns, CancellationToken token) => throw new NotImplementedException(); - public Task UpdateAsync(T poco, Expression> fields) => throw new NotImplementedException(); + public Task UpdateAsync(T poco, Expression> fields, CancellationToken token) => throw new NotImplementedException(); - public Task UpdateBatchAsync(IEnumerable> pocos, BatchOptions options = null) => + public Task UpdateBatchAsync(IEnumerable> pocos, BatchOptions? options, CancellationToken token) => throw new NotImplementedException(); - public Task DeleteAsync(object poco) => throw new NotImplementedException(); + public Task DeleteAsync(object poco, CancellationToken token) => throw new NotImplementedException(); public IAsyncUpdateQueryProvider UpdateManyAsync() => throw new NotImplementedException(); public IAsyncDeleteQueryProvider DeleteManyAsync() => throw new NotImplementedException(); - public Task IsNewAsync(T poco) => throw new NotImplementedException(); - public Task SaveAsync(T poco) => throw new NotImplementedException(); + public Task IsNewAsync(T poco, CancellationToken token) => throw new NotImplementedException(); + + public Task SaveAsync(T poco, CancellationToken token) => throw new NotImplementedException(); int IDatabase.InsertBatch(IEnumerable pocos, BatchOptions options) => throw new NotImplementedException(); @@ -371,84 +376,118 @@ public class TestDatabase : IUmbracoDatabase (List, List, List) IDatabaseQuery.FetchMultiple(Sql sql) => throw new NotImplementedException(); - public Task SingleAsync(string sql, params object[] args) => throw new NotImplementedException(); + public Task SingleAsync(string sql, CancellationToken token) => throw new NotImplementedException(); - public Task SingleAsync(Sql sql) => throw new NotImplementedException(); + public Task SingleAsync(string sql, object[] args, CancellationToken token) => throw new NotImplementedException(); - public Task SingleOrDefaultAsync(string sql, params object[] args) => throw new NotImplementedException(); + public Task SingleAsync(Sql sql, CancellationToken token) => throw new NotImplementedException(); - public Task SingleOrDefaultAsync(Sql sql) => throw new NotImplementedException(); + public Task SingleOrDefaultAsync(string sql, CancellationToken token) => throw new NotImplementedException(); - public Task SingleByIdAsync(object primaryKey) => throw new NotImplementedException(); + public Task SingleOrDefaultAsync(string sql, object[] args, CancellationToken token) => throw new NotImplementedException(); - public Task SingleOrDefaultByIdAsync(object primaryKey) => throw new NotImplementedException(); + public Task SingleOrDefaultAsync(Sql sql, CancellationToken token) => throw new NotImplementedException(); - public Task FirstAsync(string sql, params object[] args) => throw new NotImplementedException(); + public Task SingleByIdAsync(object primaryKey, CancellationToken token) => throw new NotImplementedException(); - public Task FirstAsync(Sql sql) => throw new NotImplementedException(); + public Task SingleOrDefaultByIdAsync(object primaryKey, CancellationToken token) => throw new NotImplementedException(); - public Task FirstOrDefaultAsync(string sql, params object[] args) => throw new NotImplementedException(); + public Task FirstAsync(string sql, CancellationToken token) => throw new NotImplementedException(); - public Task FirstOrDefaultAsync(Sql sql) => throw new NotImplementedException(); + public Task FirstAsync(string sql, object[] args, CancellationToken token) => throw new NotImplementedException(); - IAsyncEnumerable IAsyncQueryDatabase.QueryAsync(string sql, params object[] args) => - throw new NotImplementedException(); + public Task FirstAsync(Sql sql, CancellationToken token) => throw new NotImplementedException(); - IAsyncEnumerable IAsyncQueryDatabase.QueryAsync(Sql sql) => throw new NotImplementedException(); + public Task FirstOrDefaultAsync(string sql, CancellationToken token) => throw new NotImplementedException(); + + public Task FirstOrDefaultAsync(string sql, object[] args, CancellationToken token) => throw new NotImplementedException(); + + public Task FirstOrDefaultAsync(Sql sql, CancellationToken token) => throw new NotImplementedException(); public IAsyncQueryProviderWithIncludes QueryAsync() => throw new NotImplementedException(); - public Task> FetchAsync(string sql, params object[] args) => throw new NotImplementedException(); + IAsyncEnumerable IAsyncQueryDatabase.QueryAsync(string sql, CancellationToken token) => throw new NotImplementedException(); - public Task> FetchAsync(Sql sql) => throw new NotImplementedException(); + IAsyncEnumerable IAsyncQueryDatabase.QueryAsync(string sql, object[] args, CancellationToken token) => throw new NotImplementedException(); - public Task> FetchAsync() => throw new NotImplementedException(); + IAsyncEnumerable IAsyncQueryDatabase.QueryAsync(Sql sql, CancellationToken token) => throw new NotImplementedException(); - public Task> PageAsync(long page, long itemsPerPage, string sql, params object[] args) => + public IAsyncQueryProviderWithIncludes QueryAsync(CancellationToken token) => throw new NotImplementedException(); + + public Task> FetchAsync(string sql, CancellationToken token) => throw new NotImplementedException(); + + public Task> FetchAsync(string sql, object[] args, CancellationToken token) => throw new NotImplementedException(); + + public Task> FetchAsync(Sql sql, CancellationToken token) => throw new NotImplementedException(); + + public Task> FetchAsync(CancellationToken token) => throw new NotImplementedException(); + + public Task> PageAsync(long page, long itemsPerPage, string sql, CancellationToken cancellationToken = default) => throw new NotImplementedException(); + + public Task> PageAsync(long page, long itemsPerPage, string sql, object[] args, CancellationToken token) => throw new NotImplementedException(); - public Task> PageAsync(long page, long itemsPerPage, Sql sql) => throw new NotImplementedException(); + public Task> PageAsync(long page, long itemsPerPage, Sql sql, CancellationToken token) => throw new NotImplementedException(); - public Task> FetchAsync(long page, long itemsPerPage, string sql, params object[] args) => + public Task> FetchAsync(long page, long itemsPerPage, string sql, CancellationToken token) => throw new NotImplementedException(); - public Task> FetchAsync(long page, long itemsPerPage, Sql sql) => throw new NotImplementedException(); - - public Task> SkipTakeAsync(long skip, long take, string sql, params object[] args) => + public Task> FetchAsync(long page, long itemsPerPage, string sql, object[] args, CancellationToken token) => throw new NotImplementedException(); - public Task> SkipTakeAsync(long skip, long take, Sql sql) => throw new NotImplementedException(); + public Task> FetchAsync(long page, long itemsPerPage, Sql sql, CancellationToken token) => throw new NotImplementedException(); - public Task FetchMultipleAsync(Func, List, TRet> cb, string sql, params object[] args) => throw new NotImplementedException(); - - public Task FetchMultipleAsync(Func, List, List, TRet> cb, string sql, params object[] args) => throw new NotImplementedException(); - - public Task FetchMultipleAsync(Func, List, List, List, TRet> cb, string sql, params object[] args) => throw new NotImplementedException(); - - public Task FetchMultipleAsync(Func, List, TRet> cb, Sql sql) => + public Task> SkipTakeAsync(long skip, long take, string sql, CancellationToken token) => throw new NotImplementedException(); - public Task FetchMultipleAsync(Func, List, List, TRet> cb, Sql sql) => + public Task> SkipTakeAsync(long skip, long take, string sql, object[] args, CancellationToken token) => throw new NotImplementedException(); - public Task FetchMultipleAsync(Func, List, List, List, TRet> cb, Sql sql) => throw new NotImplementedException(); + public Task> SkipTakeAsync(long skip, long take, Sql sql, CancellationToken token) => throw new NotImplementedException(); - public Task<(List, List)> FetchMultipleAsync(string sql, params object[] args) => + public Task FetchMultipleAsync(Func, List, TRet> cb, string sql, object[] args, CancellationToken token) => throw new NotImplementedException(); + + public Task FetchMultipleAsync(Func, List, List, TRet> cb, string sql, object[] args, CancellationToken token) => throw new NotImplementedException(); + + public Task FetchMultipleAsync(Func, List, List, List, TRet> cb, string sql, object[] args, CancellationToken token) => throw new NotImplementedException(); + + public Task FetchMultipleAsync(Func, List, TRet> cb, Sql sql, CancellationToken token) => throw new NotImplementedException(); - public Task<(List, List, List)> FetchMultipleAsync(string sql, params object[] args) => + public Task FetchMultipleAsync(Func, List, List, TRet> cb, Sql sql, CancellationToken token) => throw new NotImplementedException(); - public Task<(List, List, List, List)> FetchMultipleAsync(string sql, params object[] args) => throw new NotImplementedException(); + public Task FetchMultipleAsync(Func, List, List, List, TRet> cb, Sql sql, CancellationToken token) => throw new NotImplementedException(); - public Task<(List, List)> FetchMultipleAsync(Sql sql) => throw new NotImplementedException(); - - public Task<(List, List, List)> FetchMultipleAsync(Sql sql) => + public Task<(List, List)> FetchMultipleAsync(string sql, object[] args, CancellationToken token) => throw new NotImplementedException(); - public Task<(List, List, List, List)> FetchMultipleAsync(Sql sql) => + public Task<(List, List, List)> FetchMultipleAsync(string sql, object[] args, CancellationToken token) => throw new NotImplementedException(); + public Task<(List, List, List, List)> FetchMultipleAsync(string sql, object[] args, CancellationToken token) => throw new NotImplementedException(); + + public Task<(List, List)> FetchMultipleAsync(Sql sql, CancellationToken token) => throw new NotImplementedException(); + + public Task<(List, List, List)> FetchMultipleAsync(Sql sql, CancellationToken token) => + throw new NotImplementedException(); + + public Task<(List, List, List, List)> FetchMultipleAsync(Sql sql, CancellationToken token) => + throw new NotImplementedException(); + + public Task FetchMultipleAsync(Func, List, TRet> cb, string sql, CancellationToken cancellationToken = default) => throw new NotImplementedException(); + + public Task FetchMultipleAsync(Func, List, List, TRet> cb, string sql, CancellationToken cancellationToken = default) => throw new NotImplementedException(); + + public Task FetchMultipleAsync(Func, List, List, List, TRet> cb, string sql, CancellationToken cancellationToken = default) => throw new NotImplementedException(); + + public Task<(List, List)> FetchMultipleAsync(string sql, CancellationToken cancellationToken = default) => throw new NotImplementedException(); + + public Task<(List, List, List)> FetchMultipleAsync(string sql, CancellationToken cancellationToken = default) => throw new NotImplementedException(); + + public Task<(List, List, List, List)> FetchMultipleAsync(string sql, CancellationToken cancellationToken = default) => throw new NotImplementedException(); + + public void BuildPageQueries(long skip, long take, string sql, ref object[] args, out string sqlCount, out string sqlPage) => throw new NotImplementedException(); public void InsertBulk(IEnumerable pocos) => throw new NotImplementedException(); @@ -473,9 +512,27 @@ public class TestDatabase : IUmbracoDatabase public Tuple, List, List, List> FetchMultiple(Sql sql) => throw new NotImplementedException(); - public Task> QueryAsync(string sql, params object[] args) => throw new NotImplementedException(); + public IDatabase OpenSharedConnection(OpenConnectionOptions? options = null) => throw new NotImplementedException(); - public Task> QueryAsync(Sql sql) => throw new NotImplementedException(); + public Task OpenSharedConnectionAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException(); + + public Task OpenSharedConnectionAsync(OpenConnectionOptions options, CancellationToken cancellationToken = default) => throw new NotImplementedException(); + + public Task CloseSharedConnectionAsync() => throw new NotImplementedException(); + + public Task GetTransactionAsync() => throw new NotImplementedException(); + + public Task GetTransactionAsync(IsolationLevel isolationLevel) => throw new NotImplementedException(); + + public Task BeginTransactionAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException(); + + public Task BeginTransactionAsync(IsolationLevel isolationLevel, CancellationToken cancellationToken = default) => throw new NotImplementedException(); + + public Task AbortTransactionAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException(); + + public Task CompleteTransactionAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException(); + + public ValueTask DisposeAsync() => throw new NotImplementedException(); /// /// Represents a database operation. diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentServiceTests.cs index e7f01226fa..784638e594 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentServiceTests.cs @@ -2094,7 +2094,8 @@ internal sealed class ContentServiceTests : UmbracoIntegrationTestWithContent var user = await UserService.GetAsync(Constants.Security.SuperUserKey); var userGroup = await UserGroupService.GetAsync(user.Groups.First().Alias); - Assert.IsNotNull(NotificationService.CreateNotification(user, content1, "X")); + NotificationService.TryCreateNotification(user, content1, "X", out Notification? notification); + Assert.IsNotNull(notification); ContentService.SetPermission(content1, "A", new[] { userGroup.Id }); var updateDomainResult = await DomainService.UpdateDomainsAsync( diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaRepositoryTest.cs index 1532846fc4..05adc7f5c9 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaRepositoryTest.cs @@ -310,7 +310,7 @@ internal sealed class MediaRepositoryTest : UmbracoIntegrationTest repository.Save(folder); } - int[] types = { 1031 }; + var types = new List { 1031 }; var query = provider.CreateQuery().Where(x => types.Contains(x.ContentTypeId)); var result = repository.Get(query); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/NotificationsRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/NotificationsRepositoryTest.cs index 6088c68af6..6c73302a1f 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/NotificationsRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/NotificationsRepositoryTest.cs @@ -6,8 +6,10 @@ using System.Linq; using Moq; using NUnit.Framework; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; using Umbraco.Cms.Infrastructure.Scoping; @@ -45,7 +47,8 @@ internal sealed class NotificationsRepositoryTest : UmbracoIntegrationTest var entity = Mock.Of(e => e.Id == node.NodeId); var user = Mock.Of(e => e.Id == node.UserId); - var notification = repo.CreateNotification(user, entity, "A"); + repo.TryCreateNotification(user, entity, "A", out Notification? notification); + Assert.IsNotNull(notification); Assert.AreEqual("A", notification.Action); Assert.AreEqual(node.NodeId, notification.EntityId); @@ -94,7 +97,7 @@ internal sealed class NotificationsRepositoryTest : UmbracoIntegrationTest }; var result = ScopeAccessor.AmbientScope.Database.Insert(node); var entity = Mock.Of(e => e.Id == node.NodeId); - var notification = repo.CreateNotification(i % 2 == 0 ? userAdmin : userNew, entity, i.ToString(CultureInfo.InvariantCulture)); + repo.TryCreateNotification(i % 2 == 0 ? userAdmin : userNew, entity, i.ToString(CultureInfo.InvariantCulture), out Notification? notification); } var notifications = repo.GetUserNotifications(userAdmin); @@ -157,7 +160,7 @@ internal sealed class NotificationsRepositoryTest : UmbracoIntegrationTest }; ScopeAccessor.AmbientScope.Database.Insert(userDto); var userNew = Mock.Of(e => e.Id == userDto.Id); - var notification = repo.CreateNotification(userNew, i % 2 == 0 ? entity1 : entity2, i.ToString(CultureInfo.InvariantCulture)); + repo.TryCreateNotification(userNew, i % 2 == 0 ? entity1 : entity2, i.ToString(CultureInfo.InvariantCulture), out Notification? notification); } var notifications = repo.GetEntityNotifications(entity1); @@ -220,7 +223,7 @@ internal sealed class NotificationsRepositoryTest : UmbracoIntegrationTest }; ScopeAccessor.AmbientScope.Database.Insert(userDto); var userNew = Mock.Of(e => e.Id == userDto.Id); - var notification = repo.CreateNotification(userNew, i % 2 == 0 ? entity1 : entity2, i.ToString(CultureInfo.InvariantCulture)); + repo.TryCreateNotification(userNew, i % 2 == 0 ? entity1 : entity2, i.ToString(CultureInfo.InvariantCulture), out Notification? notification); } var delCount = repo.DeleteNotifications(entity1); @@ -269,7 +272,7 @@ internal sealed class NotificationsRepositoryTest : UmbracoIntegrationTest }; var result = ScopeAccessor.AmbientScope.Database.Insert(node); var entity = Mock.Of(e => e.Id == node.NodeId); - var notification = repo.CreateNotification(i % 2 == 0 ? userAdmin : userNew, entity, i.ToString(CultureInfo.InvariantCulture)); + repo.TryCreateNotification(i % 2 == 0 ? userAdmin : userNew, entity, i.ToString(CultureInfo.InvariantCulture), out Notification? notification); } var delCount = repo.DeleteNotifications(userAdmin); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityServiceTests.cs index 090cc3c1f0..0234cff14f 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityServiceTests.cs @@ -979,7 +979,7 @@ internal sealed class EntityServiceTests : UmbracoIntegrationTest // Apply a filter that excludes the child at index 1. We'd expect to not get this, but // get still get one previous sibling, i.e. the entity at index 0. - Guid[] keysToExclude = [children[1].Key]; + var keysToExclude = new List { children[1].Key }; IQuery filter = ScopeProvider.CreateQuery().Where(x => !keysToExclude.Contains(x.Key)); var target = children[2]; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaServiceTests.cs index 75b0a7ed75..bd70df5d4b 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaServiceTests.cs @@ -100,13 +100,15 @@ internal sealed class MediaServiceTests : UmbracoIntegrationTest var provider = ScopeProvider; using (provider.CreateScope()) { + var filter = provider.CreateQuery() + .Where(x => new List { mediaType1.Id, mediaType2.Id }.Contains(x.ContentTypeId)); + var result = MediaService.GetPagedChildren( -1, 0, 11, out var total, - provider.CreateQuery() - .Where(x => new[] { mediaType1.Id, mediaType2.Id }.Contains(x.ContentTypeId)), + filter, Ordering.By("SortOrder")); Assert.AreEqual(11, result.Count()); Assert.AreEqual(20, total); @@ -116,8 +118,7 @@ internal sealed class MediaServiceTests : UmbracoIntegrationTest 1, 11, out total, - provider.CreateQuery() - .Where(x => new[] { mediaType1.Id, mediaType2.Id }.Contains(x.ContentTypeId)), + filter, Ordering.By("SortOrder")); Assert.AreEqual(9, result.Count()); Assert.AreEqual(20, total); diff --git a/tests/Umbraco.Tests.UnitTests/TestHelpers/BaseUsingSqlSyntax.cs b/tests/Umbraco.Tests.UnitTests/TestHelpers/BaseUsingSqlSyntax.cs index 8e74e4b9bd..cd6e973682 100644 --- a/tests/Umbraco.Tests.UnitTests/TestHelpers/BaseUsingSqlSyntax.cs +++ b/tests/Umbraco.Tests.UnitTests/TestHelpers/BaseUsingSqlSyntax.cs @@ -13,6 +13,7 @@ using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Mappers; using Umbraco.Cms.Persistence.SqlServer.Services; using Umbraco.Extensions; +using IMapperCollection = Umbraco.Cms.Infrastructure.Persistence.Mappers.IMapperCollection; using MapperCollection = NPoco.MapperCollection; namespace Umbraco.Cms.Tests.UnitTests.TestHelpers; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Querying/ExpressionTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Querying/ExpressionTests.cs index dcf7d9c67f..6d5dea762c 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Querying/ExpressionTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Querying/ExpressionTests.cs @@ -56,7 +56,7 @@ public class ExpressionTests : BaseUsingSqlSyntax public void Can_Query_With_Content_Type_Aliases_IEnumerable() { // Arrange - Contains is IEnumerable.Contains extension method - var aliases = new[] { "Test1", "Test2" }; + var aliases = new List { "Test1", "Test2" }; Expression> predicate = content => aliases.Contains(content.ContentType.Alias); var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor(SqlContext.SqlSyntax, Mappers); var result = modelToSqlExpressionHelper.Visit(predicate); @@ -178,7 +178,14 @@ public class ExpressionTests : BaseUsingSqlSyntax [Test] public void Sql_In() { - var userNames = new[] { "hello@world.com", "blah@blah.com" }; + // This unit test reveals a breaking change for .NET 10 and NPoco. + // When a List is used with Contains in an expression, we call bool List.Contains(string), which NPoco translates to SQL IN. + // However, if we use string[] with Contains, we call the extension method bool ReadOnlySpan.Contains(string) which + // blows up in ExpressionVisitorBase as the method name of "op_Implicit" is unrecognized. + // As such we need to ensure we use a List { "hello@world.com", "blah@blah.com" }; Expression> predicate = user => userNames.Contains(user.Username); var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor(SqlContext.SqlSyntax, Mappers);