2017-12-07 16:45:25 +01:00
using NPoco ;
2021-02-09 10:22:42 +01:00
using Umbraco.Cms.Core ;
using Umbraco.Cms.Core.Cache ;
using Umbraco.Cms.Core.Models ;
using Umbraco.Cms.Core.Models.Entities ;
using Umbraco.Cms.Core.Persistence.Querying ;
using Umbraco.Cms.Core.Services ;
2021-02-12 13:36:50 +01:00
using Umbraco.Cms.Infrastructure.Persistence.Dtos ;
using Umbraco.Cms.Infrastructure.Persistence.Querying ;
using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax ;
2022-01-13 17:44:11 +00:00
using Umbraco.Cms.Infrastructure.Scoping ;
2021-02-09 11:26:22 +01:00
using Umbraco.Extensions ;
2021-02-09 10:22:42 +01:00
using static Umbraco . Cms . Core . Persistence . SqlExtensionsStatics ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement ;
/// <summary>
/// Represents the EntityRepository used to query entity objects.
/// </summary>
/// <remarks>
/// <para>Limited to objects that have a corresponding node (in umbracoNode table).</para>
/// <para>Returns <see cref="IEntitySlim" /> objects, i.e. lightweight representation of entities.</para>
/// </remarks>
internal class EntityRepository : RepositoryBase , IEntityRepositoryExtended
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
public EntityRepository ( IScopeAccessor scopeAccessor , AppCaches appCaches )
: base ( scopeAccessor , appCaches )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
#region Repository
2020-12-09 22:43:49 +11:00
2024-01-02 13:53:24 +01:00
public int CountByQuery ( IQuery < IUmbracoEntity > query , Guid objectType , IQuery < IUmbracoEntity > ? filter )
{
Sql < ISqlContext > sql = Sql ( ) ;
sql . SelectCount ( ) ;
sql
. From < NodeDto > ( ) ;
sql . WhereIn < NodeDto > ( x = > x . NodeObjectType , new [ ] { objectType } ) ;
foreach ( Tuple < string , object [ ] > queryClause in query . GetWhereClauses ( ) )
{
sql . Where ( queryClause . Item1 , queryClause . Item2 ) ;
}
if ( filter is not null )
{
foreach ( Tuple < string , object [ ] > filterClause in filter . GetWhereClauses ( ) )
{
sql . Where ( filterClause . Item1 , filterClause . Item2 ) ;
}
}
return Database . ExecuteScalar < int > ( sql ) ;
}
2022-06-02 08:18:31 +02:00
public IEnumerable < IEntitySlim > GetPagedResultsByQuery ( IQuery < IUmbracoEntity > query , Guid objectType ,
long pageIndex , int pageSize , out long totalRecords ,
IQuery < IUmbracoEntity > ? filter , Ordering ? ordering ) = >
GetPagedResultsByQuery ( query , new [ ] { objectType } , pageIndex , pageSize , out totalRecords , filter , ordering ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// get a page of entities
public IEnumerable < IEntitySlim > GetPagedResultsByQuery ( IQuery < IUmbracoEntity > query , Guid [ ] objectTypes ,
long pageIndex , int pageSize , out long totalRecords ,
IQuery < IUmbracoEntity > ? filter , Ordering ? ordering , Action < Sql < ISqlContext > > ? sqlCustomization = null )
{
var isContent = objectTypes . Any ( objectType = >
objectType = = Constants . ObjectTypes . Document | | objectType = = Constants . ObjectTypes . DocumentBlueprint ) ;
var isMedia = objectTypes . Any ( objectType = > objectType = = Constants . ObjectTypes . Media ) ;
var isMember = objectTypes . Any ( objectType = > objectType = = Constants . ObjectTypes . Member ) ;
Sql < ISqlContext > sql = GetBaseWhere ( isContent , isMedia , isMember , false , s = >
2019-11-05 15:05:51 +11:00
{
2022-06-02 08:18:31 +02:00
sqlCustomization ? . Invoke ( s ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
if ( filter ! = null )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
foreach ( Tuple < string , object [ ] > filterClause in filter . GetWhereClauses ( ) )
2019-11-06 12:43:10 +11:00
{
2022-06-02 08:18:31 +02:00
s . Where ( filterClause . Item1 , filterClause . Item2 ) ;
2019-11-06 12:43:10 +11:00
}
2018-12-21 13:15:46 +11:00
}
2022-06-02 08:18:31 +02:00
} , objectTypes ) ;
2018-12-21 13:15:46 +11:00
2022-06-02 08:18:31 +02:00
ordering = ordering ? ? Ordering . ByDefault ( ) ;
2019-02-09 15:41:30 +01:00
2022-06-02 08:18:31 +02:00
var translator = new SqlTranslator < IUmbracoEntity > ( sql , query ) ;
sql = translator . Translate ( ) ;
sql = AddGroupBy ( isContent , isMedia , isMember , sql , ordering . IsEmpty ) ;
2019-02-08 08:50:57 +01:00
2022-06-02 08:18:31 +02:00
if ( ! ordering . IsEmpty )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
// apply ordering
ApplyOrdering ( ref sql , ordering ) ;
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
// TODO: we should be able to do sql = sql.OrderBy(x => Alias(x.NodeId, "NodeId")); but we can't because the OrderBy extension don't support Alias currently
// no matter what we always must have node id ordered at the end
sql = ordering . Direction = = Direction . Ascending ? sql . OrderBy ( "NodeId" ) : sql . OrderByDescending ( "NodeId" ) ;
2018-12-21 13:15:46 +11:00
2022-06-02 08:18:31 +02:00
// for content we must query for ContentEntityDto entities to produce the correct culture variant entity names
var pageIndexToFetch = pageIndex + 1 ;
IEnumerable < BaseDto > dtos ;
Page < GenericContentEntityDto > ? page = Database . Page < GenericContentEntityDto > ( pageIndexToFetch , pageSize , sql ) ;
dtos = page . Items ;
totalRecords = page . TotalItems ;
2018-09-25 10:55:33 +02:00
2022-06-02 08:18:31 +02:00
EntitySlim [ ] entities = dtos . Select ( BuildEntity ) . ToArray ( ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
BuildVariants ( entities . OfType < DocumentEntitySlim > ( ) ) ;
2019-06-13 23:39:36 +10:00
2022-06-02 08:18:31 +02:00
return entities ;
}
2018-04-20 16:07:34 +10:00
2022-06-02 08:18:31 +02:00
public IEntitySlim ? Get ( Guid key )
{
Sql < ISqlContext > sql = GetBaseWhere ( false , false , false , false , key ) ;
BaseDto ? dto = Database . FirstOrDefault < BaseDto > ( sql ) ;
return dto = = null ? null : BuildEntity ( dto ) ;
}
2017-12-07 16:45:25 +01:00
2018-04-24 17:59:26 +02:00
2022-06-02 08:18:31 +02:00
private IEntitySlim ? GetEntity ( Sql < ISqlContext > sql , bool isContent , bool isMedia , bool isMember )
{
// isContent is going to return a 1:M result now with the variants so we need to do different things
if ( isContent )
2018-04-24 17:59:26 +02:00
{
2022-06-02 08:18:31 +02:00
List < DocumentEntityDto > ? cdtos = Database . Fetch < DocumentEntityDto > ( sql ) ;
2018-04-24 17:59:26 +02:00
2022-06-02 08:18:31 +02:00
return cdtos . Count = = 0 ? null : BuildVariants ( BuildDocumentEntity ( cdtos [ 0 ] ) ) ;
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
BaseDto ? dto = isMedia
? Database . FirstOrDefault < MediaEntityDto > ( sql )
: Database . FirstOrDefault < BaseDto > ( sql ) ;
if ( dto = = null )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
return null ;
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
EntitySlim entity = BuildEntity ( dto ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
return entity ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
public IEntitySlim ? Get ( Guid key , Guid objectTypeId )
{
var isContent = objectTypeId = = Constants . ObjectTypes . Document | |
objectTypeId = = Constants . ObjectTypes . DocumentBlueprint ;
var isMedia = objectTypeId = = Constants . ObjectTypes . Media ;
var isMember = objectTypeId = = Constants . ObjectTypes . Member ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
Sql < ISqlContext > sql = GetFullSqlForEntityType ( isContent , isMedia , isMember , objectTypeId , key ) ;
return GetEntity ( sql , isContent , isMedia , isMember ) ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
public IEntitySlim ? Get ( int id )
{
Sql < ISqlContext > sql = GetBaseWhere ( false , false , false , false , id ) ;
BaseDto ? dto = Database . FirstOrDefault < BaseDto > ( sql ) ;
return dto = = null ? null : BuildEntity ( dto ) ;
}
2018-09-21 15:49:37 +10:00
2022-06-02 08:18:31 +02:00
public IEntitySlim ? Get ( int id , Guid objectTypeId )
{
var isContent = objectTypeId = = Constants . ObjectTypes . Document | |
objectTypeId = = Constants . ObjectTypes . DocumentBlueprint ;
var isMedia = objectTypeId = = Constants . ObjectTypes . Media ;
var isMember = objectTypeId = = Constants . ObjectTypes . Member ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
Sql < ISqlContext > sql = GetFullSqlForEntityType ( isContent , isMedia , isMember , objectTypeId , id ) ;
return GetEntity ( sql , isContent , isMedia , isMember ) ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
public IEnumerable < IEntitySlim > GetAll ( Guid objectType , params int [ ] ids ) = >
ids . Length > 0
? PerformGetAll ( objectType , sql = > sql . WhereIn < NodeDto > ( x = > x . NodeId , ids . Distinct ( ) ) )
: PerformGetAll ( objectType ) ;
2018-04-20 16:07:34 +10:00
2022-06-02 08:18:31 +02:00
public IEnumerable < IEntitySlim > GetAll ( Guid objectType , params Guid [ ] keys ) = >
keys . Length > 0
? PerformGetAll ( objectType , sql = > sql . WhereIn < NodeDto > ( x = > x . UniqueId , keys . Distinct ( ) ) )
: PerformGetAll ( objectType ) ;
2018-04-24 17:59:26 +02:00
2022-06-02 08:18:31 +02:00
private IEnumerable < IEntitySlim > GetEntities ( Sql < ISqlContext > sql , bool isContent , bool isMedia , bool isMember )
{
// isContent is going to return a 1:M result now with the variants so we need to do different things
if ( isContent )
2018-04-24 17:59:26 +02:00
{
2022-06-02 08:18:31 +02:00
List < DocumentEntityDto > ? cdtos = Database . Fetch < DocumentEntityDto > ( sql ) ;
2018-04-24 17:59:26 +02:00
2022-06-02 08:18:31 +02:00
return cdtos . Count = = 0
? Enumerable . Empty < IEntitySlim > ( )
: BuildVariants ( cdtos . Select ( BuildDocumentEntity ) ) . ToList ( ) ;
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
IEnumerable < BaseDto > ? dtos = isMedia
? ( IEnumerable < BaseDto > ) Database . Fetch < MediaEntityDto > ( sql )
: Database . Fetch < BaseDto > ( sql ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
EntitySlim [ ] entities = dtos . Select ( BuildEntity ) . ToArray ( ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
return entities ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
private IEnumerable < IEntitySlim > PerformGetAll ( Guid objectType , Action < Sql < ISqlContext > > ? filter = null )
{
var isContent = objectType = = Constants . ObjectTypes . Document | |
objectType = = Constants . ObjectTypes . DocumentBlueprint ;
var isMedia = objectType = = Constants . ObjectTypes . Media ;
var isMember = objectType = = Constants . ObjectTypes . Member ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
Sql < ISqlContext > sql = GetFullSqlForEntityType ( isContent , isMedia , isMember , objectType , filter ) ;
return GetEntities ( sql , isContent , isMedia , isMember ) ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
public IEnumerable < TreeEntityPath > GetAllPaths ( Guid objectType , params int [ ] ? ids ) = >
ids ? . Any ( ) ? ? false
? PerformGetAllPaths ( objectType , sql = > sql . WhereIn < NodeDto > ( x = > x . NodeId , ids . Distinct ( ) ) )
: PerformGetAllPaths ( objectType ) ;
2018-04-20 13:12:55 +10:00
2022-06-02 08:18:31 +02:00
public IEnumerable < TreeEntityPath > GetAllPaths ( Guid objectType , params Guid [ ] keys ) = >
keys . Any ( )
? PerformGetAllPaths ( objectType , sql = > sql . WhereIn < NodeDto > ( x = > x . UniqueId , keys . Distinct ( ) ) )
: PerformGetAllPaths ( objectType ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
private IEnumerable < TreeEntityPath > PerformGetAllPaths ( Guid objectType , Action < Sql < ISqlContext > > ? filter = null )
{
// NodeId is named Id on TreeEntityPath = use an alias
Sql < ISqlContext > sql = Sql ( ) . Select < NodeDto > ( x = > Alias ( x . NodeId , nameof ( TreeEntityPath . Id ) ) , x = > x . Path )
. From < NodeDto > ( ) . Where < NodeDto > ( x = > x . NodeObjectType = = objectType ) ;
filter ? . Invoke ( sql ) ;
return Database . Fetch < TreeEntityPath > ( sql ) ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
public IEnumerable < IEntitySlim > GetByQuery ( IQuery < IUmbracoEntity > query )
{
Sql < ISqlContext > sqlClause = GetBase ( false , false , false , null ) ;
var translator = new SqlTranslator < IUmbracoEntity > ( sqlClause , query ) ;
Sql < ISqlContext > sql = translator . Translate ( ) ;
sql = AddGroupBy ( false , false , false , sql , true ) ;
List < BaseDto > ? dtos = Database . Fetch < BaseDto > ( sql ) ;
return dtos . Select ( BuildEntity ) . ToList ( ) ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
public IEnumerable < IEntitySlim > GetByQuery ( IQuery < IUmbracoEntity > query , Guid objectType )
{
var isContent = objectType = = Constants . ObjectTypes . Document | |
objectType = = Constants . ObjectTypes . DocumentBlueprint ;
var isMedia = objectType = = Constants . ObjectTypes . Media ;
var isMember = objectType = = Constants . ObjectTypes . Member ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
Sql < ISqlContext > sql = GetBaseWhere ( isContent , isMedia , isMember , false , null , new [ ] { objectType } ) ;
2022-01-13 17:44:39 +00:00
2022-06-02 08:18:31 +02:00
var translator = new SqlTranslator < IUmbracoEntity > ( sql , query ) ;
sql = translator . Translate ( ) ;
sql = AddGroupBy ( isContent , isMedia , isMember , sql , true ) ;
2022-01-13 17:44:39 +00:00
2022-06-02 08:18:31 +02:00
return GetEntities ( sql , isContent , isMedia , isMember ) ;
}
2022-01-13 17:44:39 +00:00
2022-06-02 08:18:31 +02:00
public UmbracoObjectTypes GetObjectType ( int id )
{
Sql < ISqlContext > sql = Sql ( ) . Select < NodeDto > ( x = > x . NodeObjectType ) . From < NodeDto > ( )
. Where < NodeDto > ( x = > x . NodeId = = id ) ;
return ObjectTypes . GetUmbracoObjectType ( Database . ExecuteScalar < Guid > ( sql ) ) ;
}
2022-01-13 17:44:39 +00:00
2022-06-02 08:18:31 +02:00
public UmbracoObjectTypes GetObjectType ( Guid key )
{
Sql < ISqlContext > sql = Sql ( ) . Select < NodeDto > ( x = > x . NodeObjectType ) . From < NodeDto > ( )
. Where < NodeDto > ( x = > x . UniqueId = = key ) ;
return ObjectTypes . GetUmbracoObjectType ( Database . ExecuteScalar < Guid > ( sql ) ) ;
}
public int ReserveId ( Guid key )
{
NodeDto node ;
Sql < ISqlContext > sql = SqlContext . Sql ( )
. Select < NodeDto > ( )
. From < NodeDto > ( )
. Where < NodeDto > ( x = > x . UniqueId = = key & & x . NodeObjectType = = Constants . ObjectTypes . IdReservation ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
node = Database . SingleOrDefault < NodeDto > ( sql ) ;
if ( node ! = null )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
throw new InvalidOperationException ( "An identifier has already been reserved for this Udi." ) ;
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
node = new NodeDto
2018-09-21 15:49:37 +10:00
{
2022-06-02 08:18:31 +02:00
UniqueId = key ,
Text = "RESERVED.ID" ,
NodeObjectType = Constants . ObjectTypes . IdReservation ,
CreateDate = DateTime . Now ,
UserId = null ,
ParentId = - 1 ,
Level = 1 ,
Path = "-1" ,
SortOrder = 0 ,
Trashed = false
} ;
Database . Insert ( node ) ;
2018-09-21 15:49:37 +10:00
2022-06-02 08:18:31 +02:00
return node . NodeId ;
}
2018-09-21 15:49:37 +10:00
2022-06-02 08:18:31 +02:00
public bool Exists ( Guid key )
{
Sql < ISqlContext > sql = Sql ( ) . SelectCount ( ) . From < NodeDto > ( ) . Where < NodeDto > ( x = > x . UniqueId = = key ) ;
return Database . ExecuteScalar < int > ( sql ) > 0 ;
}
2018-09-25 10:55:33 +02:00
2024-02-27 20:57:02 +00:00
public bool Exists ( IEnumerable < Guid > keys )
{
var distictKeys = keys . Distinct ( ) ;
Sql < ISqlContext > sql = Sql ( ) . SelectCount ( ) . From < NodeDto > ( ) . Where < NodeDto > ( x = > distictKeys . Contains ( x . UniqueId ) ) ;
return Database . ExecuteScalar < int > ( sql ) = = distictKeys . Count ( ) ;
}
2023-02-16 09:39:17 +01:00
/// <inheritdoc />
public bool Exists ( Guid key , Guid objectType )
{
Sql < ISqlContext > sql = Sql ( )
. SelectCount ( )
. From < NodeDto > ( )
. Where < NodeDto > ( x = > x . UniqueId = = key & & x . NodeObjectType = = objectType ) ;
return Database . ExecuteScalar < int > ( sql ) > 0 ;
}
public bool Exists ( int id , Guid objectType )
{
Sql < ISqlContext > sql = Sql ( )
. SelectCount ( )
. From < NodeDto > ( )
. Where < NodeDto > ( x = > x . NodeId = = id & & x . NodeObjectType = = objectType ) ;
return Database . ExecuteScalar < int > ( sql ) > 0 ;
}
2022-06-02 08:18:31 +02:00
public bool Exists ( int id )
{
Sql < ISqlContext > sql = Sql ( ) . SelectCount ( ) . From < NodeDto > ( ) . Where < NodeDto > ( x = > x . NodeId = = id ) ;
return Database . ExecuteScalar < int > ( sql ) > 0 ;
}
2018-09-25 10:55:33 +02:00
2022-06-02 08:18:31 +02:00
private DocumentEntitySlim BuildVariants ( DocumentEntitySlim entity )
= > BuildVariants ( new [ ] { entity } ) . First ( ) ;
2018-09-25 10:55:33 +02:00
2022-06-02 08:18:31 +02:00
private IEnumerable < DocumentEntitySlim > BuildVariants ( IEnumerable < DocumentEntitySlim > entities )
{
List < DocumentEntitySlim > ? v = null ;
var entitiesList = entities . ToList ( ) ;
foreach ( DocumentEntitySlim e in entitiesList )
{
if ( e . Variations . VariesByCulture ( ) )
{
( v ? ? ( v = new List < DocumentEntitySlim > ( ) ) ) . Add ( e ) ;
2018-09-21 15:49:37 +10:00
}
2022-06-02 08:18:31 +02:00
}
2018-09-21 15:49:37 +10:00
2022-06-02 08:18:31 +02:00
if ( v = = null )
{
2018-09-25 10:55:33 +02:00
return entitiesList ;
2018-09-21 15:49:37 +10:00
}
2022-06-02 08:18:31 +02:00
// fetch all variant info dtos
IEnumerable < VariantInfoDto > dtos = Database . FetchByGroups < VariantInfoDto , int > ( v . Select ( x = > x . Id ) ,
Constants . Sql . MaxParameterCount , GetVariantInfos ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// group by node id (each group contains all languages)
var xdtos = dtos . GroupBy ( x = > x . NodeId ) . ToDictionary ( x = > x . Key , x = > x ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
foreach ( DocumentEntitySlim e in v )
2018-09-21 15:49:37 +10:00
{
2022-06-02 08:18:31 +02:00
// since we're only iterating on entities that vary, we must have something
IGrouping < int , VariantInfoDto > edtos = xdtos [ e . Id ] ;
2018-09-25 10:55:33 +02:00
2022-06-02 08:18:31 +02:00
e . CultureNames = edtos . Where ( x = > x . CultureAvailable ) . ToDictionary ( x = > x . IsoCode , x = > x . Name ) ;
e . PublishedCultures = edtos . Where ( x = > x . CulturePublished ) . Select ( x = > x . IsoCode ) ;
e . EditedCultures = edtos . Where ( x = > x . CultureAvailable & & x . CultureEdited ) . Select ( x = > x . IsoCode ) ;
}
2018-09-25 10:55:33 +02:00
2022-06-02 08:18:31 +02:00
return entitiesList ;
}
2018-09-25 10:55:33 +02:00
2022-06-02 08:18:31 +02:00
#endregion
#region Sql
protected Sql < ISqlContext > GetVariantInfos ( IEnumerable < int > ids ) = >
Sql ( )
. Select < NodeDto > ( x = > x . NodeId )
. AndSelect < LanguageDto > ( x = > x . IsoCode )
. AndSelect < DocumentDto > ( "doc" , x = > Alias ( x . Published , "DocumentPublished" ) ,
x = > Alias ( x . Edited , "DocumentEdited" ) )
. AndSelect < DocumentCultureVariationDto > ( "dcv" ,
x = > Alias ( x . Available , "CultureAvailable" ) , x = > Alias ( x . Published , "CulturePublished" ) ,
x = > Alias ( x . Edited , "CultureEdited" ) ,
x = > Alias ( x . Name , "Name" ) )
// from node x language
. From < NodeDto > ( )
. CrossJoin < LanguageDto > ( )
// join to document - always exists - indicates global document published/edited status
. InnerJoin < DocumentDto > ( "doc" )
. On < NodeDto , DocumentDto > ( ( node , doc ) = > node . NodeId = = doc . NodeId , aliasRight : "doc" )
// left-join do document variation - matches cultures that are *available* + indicates when *edited*
. LeftJoin < DocumentCultureVariationDto > ( "dcv" )
. On < NodeDto , DocumentCultureVariationDto , LanguageDto > (
( node , dcv , lang ) = > node . NodeId = = dcv . NodeId & & lang . Id = = dcv . LanguageId , aliasRight : "dcv" )
// for selected nodes
. WhereIn < NodeDto > ( x = > x . NodeId , ids )
. OrderBy < LanguageDto > ( x = > x . Id ) ;
// gets the full sql for a given object type and a given unique id
protected Sql < ISqlContext > GetFullSqlForEntityType ( bool isContent , bool isMedia , bool isMember , Guid objectType ,
Guid uniqueId )
{
Sql < ISqlContext > sql = GetBaseWhere ( isContent , isMedia , isMember , false , objectType , uniqueId ) ;
return AddGroupBy ( isContent , isMedia , isMember , sql , true ) ;
}
2018-09-25 10:55:33 +02:00
2022-06-02 08:18:31 +02:00
// gets the full sql for a given object type and a given node id
protected Sql < ISqlContext > GetFullSqlForEntityType ( bool isContent , bool isMedia , bool isMember , Guid objectType ,
int nodeId )
{
Sql < ISqlContext > sql = GetBaseWhere ( isContent , isMedia , isMember , false , objectType , nodeId ) ;
return AddGroupBy ( isContent , isMedia , isMember , sql , true ) ;
}
2018-09-25 10:55:33 +02:00
2022-06-02 08:18:31 +02:00
// gets the full sql for a given object type, with a given filter
protected Sql < ISqlContext > GetFullSqlForEntityType ( bool isContent , bool isMedia , bool isMember , Guid objectType ,
Action < Sql < ISqlContext > > ? filter )
{
Sql < ISqlContext > sql = GetBaseWhere ( isContent , isMedia , isMember , false , filter , new [ ] { objectType } ) ;
return AddGroupBy ( isContent , isMedia , isMember , sql , true ) ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// gets the base SELECT + FROM [+ filter] sql
// always from the 'current' content version
protected Sql < ISqlContext > GetBase ( bool isContent , bool isMedia , bool isMember , Action < Sql < ISqlContext > > ? filter ,
bool isCount = false )
{
Sql < ISqlContext > sql = Sql ( ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
if ( isCount )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
sql . SelectCount ( ) ;
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
else
2019-11-05 15:05:51 +11:00
{
2017-12-07 16:45:25 +01:00
sql
2022-06-02 08:18:31 +02:00
. Select < NodeDto > ( x = > x . NodeId , x = > x . Trashed , x = > x . ParentId , x = > x . UserId , x = > x . Level ,
x = > x . Path )
. AndSelect < NodeDto > ( x = > x . SortOrder , x = > x . UniqueId , x = > x . Text , x = > x . NodeObjectType ,
x = > x . CreateDate )
. Append ( ", COUNT(child.id) AS children" ) ;
2017-12-07 16:45:25 +01:00
2019-10-14 14:34:33 +02:00
if ( isContent | | isMedia | | isMember )
2017-12-07 16:45:25 +01:00
{
sql
2022-06-02 08:18:31 +02:00
. AndSelect < ContentVersionDto > ( x = > Alias ( x . Id , "versionId" ) , x = > x . VersionDate )
2024-02-22 09:11:31 +01:00
. AndSelect < ContentTypeDto > (
x = > x . Alias ,
x = > x . Icon ,
x = > x . Thumbnail ,
x = > x . ListView ,
2022-06-02 08:18:31 +02:00
x = > x . Variations ) ;
2017-12-07 16:45:25 +01:00
}
if ( isContent )
{
sql
2022-06-02 08:18:31 +02:00
. AndSelect < DocumentDto > ( x = > x . Published , x = > x . Edited ) ;
2017-12-07 16:45:25 +01:00
}
2019-06-13 23:39:36 +10:00
if ( isMedia )
{
sql
2022-06-02 08:18:31 +02:00
. AndSelect < MediaVersionDto > ( x = > Alias ( x . Path , "MediaPath" ) ) ;
2018-04-20 13:12:55 +10:00
}
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
sql
. From < NodeDto > ( ) ;
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
if ( isContent | | isMedia | | isMember )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
sql
. LeftJoin < ContentVersionDto > ( )
. On < NodeDto , ContentVersionDto > ( ( left , right ) = > left . NodeId = = right . NodeId & & right . Current )
. LeftJoin < ContentDto > ( ) . On < NodeDto , ContentDto > ( ( left , right ) = > left . NodeId = = right . NodeId )
. LeftJoin < ContentTypeDto > ( )
. On < ContentDto , ContentTypeDto > ( ( left , right ) = > left . ContentTypeId = = right . NodeId ) ;
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
if ( isContent )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
sql
. LeftJoin < DocumentDto > ( ) . On < NodeDto , DocumentDto > ( ( left , right ) = > left . NodeId = = right . NodeId ) ;
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
if ( isMedia )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
sql
. LeftJoin < MediaVersionDto > ( )
. On < ContentVersionDto , MediaVersionDto > ( ( left , right ) = > left . Id = = right . Id ) ;
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
//Any LeftJoin statements need to come last
if ( isCount = = false )
2017-12-07 16:45:25 +01:00
{
2022-06-02 08:18:31 +02:00
sql
. LeftJoin < NodeDto > ( "child" )
. On < NodeDto , NodeDto > ( ( left , right ) = > left . NodeId = = right . ParentId , aliasRight : "child" ) ;
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
filter ? . Invoke ( sql ) ;
2018-04-20 13:12:55 +10:00
2022-06-02 08:18:31 +02:00
return sql ;
}
2019-06-13 23:39:36 +10:00
2022-06-02 08:18:31 +02:00
// gets the base SELECT + FROM [+ filter] + WHERE sql
// for a given object type, with a given filter
protected Sql < ISqlContext > GetBaseWhere ( bool isContent , bool isMedia , bool isMember , bool isCount ,
Action < Sql < ISqlContext > > ? filter , Guid [ ] objectTypes )
{
Sql < ISqlContext > sql = GetBase ( isContent , isMedia , isMember , filter , isCount ) ;
if ( objectTypes . Length > 0 )
{
sql . WhereIn < NodeDto > ( x = > x . NodeObjectType , objectTypes ) ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
return sql ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// gets the base SELECT + FROM + WHERE sql
// for a given node id
protected Sql < ISqlContext > GetBaseWhere ( bool isContent , bool isMedia , bool isMember , bool isCount , int id )
{
Sql < ISqlContext > sql = GetBase ( isContent , isMedia , isMember , null , isCount )
. Where < NodeDto > ( x = > x . NodeId = = id ) ;
return AddGroupBy ( isContent , isMedia , isMember , sql , true ) ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
// gets the base SELECT + FROM + WHERE sql
// for a given unique id
protected Sql < ISqlContext > GetBaseWhere ( bool isContent , bool isMedia , bool isMember , bool isCount , Guid uniqueId )
{
Sql < ISqlContext > sql = GetBase ( isContent , isMedia , isMember , null , isCount )
. Where < NodeDto > ( x = > x . UniqueId = = uniqueId ) ;
return AddGroupBy ( isContent , isMedia , isMember , sql , true ) ;
}
// gets the base SELECT + FROM + WHERE sql
// for a given object type and node id
protected Sql < ISqlContext > GetBaseWhere ( bool isContent , bool isMedia , bool isMember , bool isCount , Guid objectType ,
int nodeId ) = >
GetBase ( isContent , isMedia , isMember , null , isCount )
. Where < NodeDto > ( x = > x . NodeId = = nodeId & & x . NodeObjectType = = objectType ) ;
// gets the base SELECT + FROM + WHERE sql
// for a given object type and unique id
protected Sql < ISqlContext > GetBaseWhere ( bool isContent , bool isMedia , bool isMember , bool isCount , Guid objectType ,
Guid uniqueId ) = >
GetBase ( isContent , isMedia , isMember , null , isCount )
. Where < NodeDto > ( x = > x . UniqueId = = uniqueId & & x . NodeObjectType = = objectType ) ;
// gets the GROUP BY / ORDER BY sql
// required in order to count children
protected Sql < ISqlContext > AddGroupBy ( bool isContent , bool isMedia , bool isMember , Sql < ISqlContext > sql ,
bool defaultSort )
{
sql
. GroupBy < NodeDto > ( x = > x . NodeId , x = > x . Trashed , x = > x . ParentId , x = > x . UserId , x = > x . Level , x = > x . Path )
. AndBy < NodeDto > ( x = > x . SortOrder , x = > x . UniqueId , x = > x . Text , x = > x . NodeObjectType , x = > x . CreateDate ) ;
if ( isContent )
{
sql
. AndBy < DocumentDto > ( x = > x . Published , x = > x . Edited ) ;
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
if ( isMedia )
2018-12-21 13:15:46 +11:00
{
2022-06-02 08:18:31 +02:00
sql
. AndBy < MediaVersionDto > ( x = > Alias ( x . Path , "MediaPath" ) ) ;
}
2018-12-21 13:15:46 +11:00
2019-11-11 12:05:26 +00:00
2022-06-02 08:18:31 +02:00
if ( isContent | | isMedia | | isMember )
{
sql
. AndBy < ContentVersionDto > ( x = > x . Id , x = > x . VersionDate )
2024-02-22 09:11:31 +01:00
. AndBy < ContentTypeDto > (
x = > x . Alias ,
x = > x . Icon ,
x = > x . Thumbnail ,
x = > x . ListView ,
2022-06-02 08:18:31 +02:00
x = > x . Variations ) ;
}
2018-12-21 13:15:46 +11:00
2022-06-02 08:18:31 +02:00
if ( defaultSort )
{
sql . OrderBy < NodeDto > ( x = > x . SortOrder ) ;
2018-12-21 13:15:46 +11:00
}
2022-06-02 08:18:31 +02:00
return sql ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
private void ApplyOrdering ( ref Sql < ISqlContext > sql , Ordering ordering )
{
if ( sql = = null )
{
throw new ArgumentNullException ( nameof ( sql ) ) ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
if ( ordering = = null )
2019-11-05 15:05:51 +11:00
{
2022-06-02 08:18:31 +02:00
throw new ArgumentNullException ( nameof ( ordering ) ) ;
2019-11-05 15:05:51 +11:00
}
2022-06-02 08:18:31 +02:00
// TODO: although the default ordering string works for name, it wont work for others without a table or an alias of some sort
// As more things are attempted to be sorted we'll prob have to add more expressions here
string orderBy ;
switch ( ordering . OrderBy ? . ToUpperInvariant ( ) )
2018-09-21 15:49:37 +10:00
{
2022-06-02 08:18:31 +02:00
case "PATH" :
orderBy = SqlSyntax . GetQuotedColumn ( NodeDto . TableName , "path" ) ;
break ;
2018-09-21 15:49:37 +10:00
2022-06-02 08:18:31 +02:00
default :
orderBy = ordering . OrderBy ? ? string . Empty ;
break ;
2018-09-21 15:49:37 +10:00
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
if ( ordering . Direction = = Direction . Ascending )
2019-06-13 23:39:36 +10:00
{
2022-06-02 08:18:31 +02:00
sql . OrderBy ( orderBy ) ;
2019-06-13 23:39:36 +10:00
}
2022-06-02 08:18:31 +02:00
else
2019-10-14 14:34:33 +02:00
{
2022-06-02 08:18:31 +02:00
sql . OrderByDescending ( orderBy ) ;
2019-10-14 14:34:33 +02:00
}
2022-06-02 08:18:31 +02:00
}
2019-10-14 14:34:33 +02:00
2022-06-02 08:18:31 +02:00
#endregion
2018-09-25 10:55:33 +02:00
2022-06-02 08:18:31 +02:00
#region Classes
2018-09-21 15:49:37 +10:00
2022-06-02 08:18:31 +02:00
/// <summary>
/// The DTO used to fetch results for a generic content item which could be either a document, media or a member
/// </summary>
private class GenericContentEntityDto : DocumentEntityDto
{
public string? MediaPath { get ; set ; }
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
/// <summary>
/// The DTO used to fetch results for a document item with its variation info
/// </summary>
private class DocumentEntityDto : BaseDto
{
public ContentVariation Variations { get ; set ; }
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
public bool Published { get ; set ; }
public bool Edited { get ; set ; }
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
/// <summary>
/// The DTO used to fetch results for a media item with its media path info
/// </summary>
private class MediaEntityDto : BaseDto
{
public string? MediaPath { get ; set ; }
}
/// <summary>
/// The DTO used to fetch results for a member item
/// </summary>
private class MemberEntityDto : BaseDto
{
}
public class VariantInfoDto
{
public int NodeId { get ; set ; }
public string IsoCode { get ; set ; } = null ! ;
public string Name { get ; set ; } = null ! ;
public bool DocumentPublished { get ; set ; }
public bool DocumentEdited { get ; set ; }
public bool CultureAvailable { get ; set ; }
public bool CulturePublished { get ; set ; }
public bool CultureEdited { get ; set ; }
}
// ReSharper disable once ClassNeverInstantiated.Local
/// <summary>
/// the DTO corresponding to fields selected by GetBase
/// </summary>
private class BaseDto
{
// ReSharper disable UnusedAutoPropertyAccessor.Local
// ReSharper disable UnusedMember.Local
public int NodeId { get ; set ; }
public bool Trashed { get ; set ; }
public int ParentId { get ; set ; }
public int? UserId { get ; set ; }
public int Level { get ; set ; }
public string Path { get ; } = null ! ;
public int SortOrder { get ; set ; }
public Guid UniqueId { get ; set ; }
public string? Text { get ; set ; }
public Guid NodeObjectType { get ; set ; }
public DateTime CreateDate { get ; set ; }
public DateTime VersionDate { get ; set ; }
public int Children { get ; set ; }
public int VersionId { get ; set ; }
public string Alias { get ; } = null ! ;
public string? Icon { get ; set ; }
public string? Thumbnail { get ; set ; }
public bool IsContainer { get ; set ; }
// ReSharper restore UnusedAutoPropertyAccessor.Local
// ReSharper restore UnusedMember.Local
}
#endregion
#region Factory
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
private EntitySlim BuildEntity ( BaseDto dto )
{
2022-10-05 12:14:43 +02:00
if ( dto . NodeObjectType = = Constants . ObjectTypes . Document
| | dto . NodeObjectType = = Constants . ObjectTypes . DocumentBlueprint )
2022-06-02 08:18:31 +02:00
{
return BuildDocumentEntity ( dto ) ;
2018-01-12 10:48:36 +01:00
}
2022-06-02 08:18:31 +02:00
if ( dto . NodeObjectType = = Constants . ObjectTypes . Media )
2018-01-12 10:48:36 +01:00
{
2022-06-02 08:18:31 +02:00
return BuildMediaEntity ( dto ) ;
2018-01-12 10:48:36 +01:00
}
2022-06-02 08:18:31 +02:00
if ( dto . NodeObjectType = = Constants . ObjectTypes . Member )
2018-01-12 10:48:36 +01:00
{
2022-06-02 08:18:31 +02:00
return BuildMemberEntity ( dto ) ;
}
2019-06-13 23:39:36 +10:00
2022-06-02 08:18:31 +02:00
// EntitySlim does not track changes
var entity = new EntitySlim ( ) ;
BuildEntity ( entity , dto ) ;
return entity ;
}
2019-06-13 23:39:36 +10:00
2022-06-02 08:18:31 +02:00
private static void BuildEntity ( EntitySlim entity , BaseDto dto )
{
entity . Trashed = dto . Trashed ;
entity . CreateDate = dto . CreateDate ;
entity . UpdateDate = dto . VersionDate ;
entity . CreatorId = dto . UserId ? ? Constants . Security . UnknownUserId ;
entity . Id = dto . NodeId ;
entity . Key = dto . UniqueId ;
entity . Level = dto . Level ;
entity . Name = dto . Text ;
entity . NodeObjectType = dto . NodeObjectType ;
entity . ParentId = dto . ParentId ;
entity . Path = dto . Path ;
entity . SortOrder = dto . SortOrder ;
entity . HasChildren = dto . Children > 0 ;
entity . IsContainer = dto . IsContainer ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
private static void BuildContentEntity ( ContentEntitySlim entity , BaseDto dto )
{
BuildEntity ( entity , dto ) ;
entity . ContentTypeAlias = dto . Alias ;
entity . ContentTypeIcon = dto . Icon ;
entity . ContentTypeThumbnail = dto . Thumbnail ;
}
2017-12-07 16:45:25 +01:00
2022-06-02 08:18:31 +02:00
private MediaEntitySlim BuildMediaEntity ( BaseDto dto )
{
// EntitySlim does not track changes
var entity = new MediaEntitySlim ( ) ;
BuildContentEntity ( entity , dto ) ;
2018-09-25 10:55:33 +02:00
2022-06-02 08:18:31 +02:00
// fill in the media info
if ( dto is MediaEntityDto mediaEntityDto )
{
entity . MediaPath = mediaEntityDto . MediaPath ;
2018-04-20 13:12:55 +10:00
}
2022-06-02 08:18:31 +02:00
else if ( dto is GenericContentEntityDto genericContentEntityDto )
2019-10-14 14:34:33 +02:00
{
2022-06-02 08:18:31 +02:00
entity . MediaPath = genericContentEntityDto . MediaPath ;
}
return entity ;
}
2019-10-14 14:34:33 +02:00
2022-06-02 08:18:31 +02:00
private DocumentEntitySlim BuildDocumentEntity ( BaseDto dto )
{
// EntitySlim does not track changes
var entity = new DocumentEntitySlim ( ) ;
BuildContentEntity ( entity , dto ) ;
2019-10-14 14:34:33 +02:00
2022-06-02 08:18:31 +02:00
if ( dto is DocumentEntityDto contentDto )
{
// fill in the invariant info
entity . Edited = contentDto . Edited ;
entity . Published = contentDto . Published ;
entity . Variations = contentDto . Variations ;
2019-10-14 14:34:33 +02:00
}
2022-06-02 08:18:31 +02:00
return entity ;
2017-12-07 16:45:25 +01:00
}
2022-06-02 08:18:31 +02:00
private MemberEntitySlim BuildMemberEntity ( BaseDto dto )
{
// EntitySlim does not track changes
var entity = new MemberEntitySlim ( ) ;
BuildEntity ( entity , dto ) ;
entity . ContentTypeAlias = dto . Alias ;
entity . ContentTypeIcon = dto . Icon ;
entity . ContentTypeThumbnail = dto . Thumbnail ;
return entity ;
}
#endregion
2017-12-07 16:45:25 +01:00
}