<commit_before>﻿using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Persistence.Caching;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.UnitOfWork;

namespace Umbraco.Core.Persistence.Repositories
{

    /// <summary>
    /// Represent an abstract Repository, which is the base of the Repository implementations
    /// </summary>
    /// <typeparam name="TEntity">Type of <see cref="IAggregateRoot"/> entity for which the repository is used</typeparam>
    /// <typeparam name="TId">Type of the Id used for this entity</typeparam>
    internal abstract class RepositoryBase<TId, TEntity> : DisposableObject, IRepositoryQueryable<TId, TEntity>, IUnitOfWorkRepository 
		where TEntity : class, IAggregateRoot
    {
		private readonly IUnitOfWork _work;
        private readonly IRepositoryCacheProvider _cache;

		protected RepositoryBase(IUnitOfWork work)
            : this(work, RuntimeCacheProvider.Current)
        {
        }

		internal RepositoryBase(IUnitOfWork work, IRepositoryCacheProvider cache)
        {
		    if (work == null) throw new ArgumentNullException("work");
		    if (cache == null) throw new ArgumentNullException("cache");
		    _work = work;
            _cache = cache;
        }

        /// <summary>
        /// Returns the Unit of Work added to the repository
        /// </summary>
		protected internal IUnitOfWork UnitOfWork
        {
            get { return _work; }
        }

        /// <summary>
        /// Internal for testing purposes
        /// </summary>
        internal Guid UnitKey
        {
            get { return (Guid)_work.Key; }
        }

        #region IRepository<TEntity> Members

        /// <summary>
        /// Adds or Updates an entity of type TEntity
        /// </summary>
        /// <remarks>This method is backed by an <see cref="IRepositoryCacheProvider"/> cache</remarks>
        /// <param name="entity"></param>
        public void AddOrUpdate(TEntity entity)
        {
            if (entity.HasIdentity == false)
            {
                _work.RegisterAdded(entity, this);
            }
            else
            {
                _work.RegisterChanged(entity, this);
            }
        }

        /// <summary>
        /// Deletes the passed in entity
        /// </summary>
        /// <param name="entity"></param>
        public virtual void Delete(TEntity entity)
        {
            if(_work != null)
            {
                _work.RegisterRemoved(entity, this);
            }
        }

        protected abstract TEntity PerformGet(TId id);
        /// <summary>
        /// Gets an entity by the passed in Id
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public TEntity Get(TId id)
        {
            var fromCache = TryGetFromCache(id);
            if (fromCache.Success)
            {
                return fromCache.Result;
            }

            var entity = PerformGet(id);
            if (entity != null)
            {
                _cache.Save(typeof(TEntity), entity);
            }

            if (entity != null)
            {
                //on initial construction we don't want to have dirty properties tracked
                // http://issues.umbraco.org/issue/U4-1946
                var asEntity = entity as TracksChangesEntityBase;
                if (asEntity != null)
                {
                    asEntity.ResetDirtyProperties(false);
                }
            }
            
            return entity;
        }

        protected Attempt<TEntity> TryGetFromCache(TId id)
        {
            Guid key = id is int ? ConvertIdToGuid(id) : ConvertStringIdToGuid(id.ToString());
            var rEntity = _cache.GetById(typeof(TEntity), key);
            if (rEntity != null)
            {
                return Attempt.Succeed((TEntity) rEntity);
            }
            return Attempt<TEntity>.Fail();
        } 

        protected abstract IEnumerable<TEntity> PerformGetAll(params TId[] ids);
        /// <summary>
        /// Gets all entities of type TEntity or a list according to the passed in Ids
        /// </summary>
        /// <param name="ids"></param>
        /// <returns></returns>
        public IEnumerable<TEntity> GetAll(params TId[] ids)
        {
            //ensure they are de-duplicated, easy win if people don't do this as this can cause many excess queries
            ids = ids.Distinct()
                //don't query by anything that is a default of T (like a zero)
                //TODO: I think we should enabled this in case accidental calls are made to get all with invalid ids
                //.Where(x => Equals(x, default(TId)) == false)
                .ToArray();

            if (ids.Length > 2000)
            {
                throw new InvalidOperationException("Cannot perform a query with more than 2000 parameters");
            }

            if (ids.Any())
            {
                var entities = _cache.GetByIds(
                    typeof (TEntity), ids.Select(id => id is int ? ConvertIdToGuid(id) : ConvertStringIdToGuid(id.ToString())).ToList())
                    .ToArray();

                if (ids.Count().Equals(entities.Count()) && entities.Any(x => x == null) == false)
                    return entities.Select(x => (TEntity)x);
            }
            else
            {
                var allEntities = _cache.GetAllByType(typeof (TEntity)).ToArray();
                
                if (allEntities.Any())
                {
                    //Get count of all entities of current type (TEntity) to ensure cached result is correct
                    var query = Query<TEntity>.Builder.Where(x => x.Id != 0);
                    int totalCount = PerformCount(query);

                    if(allEntities.Count() == totalCount)
                        return allEntities.Select(x => (TEntity)x);
                }
            }

            var entityCollection = PerformGetAll(ids)
                //ensure we don't include any null refs in the returned collection!
                .WhereNotNull()
                .ToArray();

            //We need to put a threshold here! IF there's an insane amount of items
            // coming back here we don't want to chuck it all into memory, this added cache here
            // is more for convenience when paging stuff temporarily

            if (entityCollection.Length > 100) return entityCollection;

            foreach (var entity in entityCollection)
            {
                if (entity != null)
                {
                    _cache.Save(typeof(TEntity), entity);
                }<commit_msg>#U4-5529 Fixed Due in version: 7.2.0<commit_after>﻿using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Persistence.Caching;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.UnitOfWork;

namespace Umbraco.Core.Persistence.Repositories
{

    /// <summary>
    /// Represent an abstract Repository, which is the base of the Repository implementations
    /// </summary>
    /// <typeparam name="TEntity">Type of <see cref="IAggregateRoot"/> entity for which the repository is used</typeparam>
    /// <typeparam name="TId">Type of the Id used for this entity</typeparam>
    internal abstract class RepositoryBase<TId, TEntity> : DisposableObject, IRepositoryQueryable<TId, TEntity>, IUnitOfWorkRepository 
		where TEntity : class, IAggregateRoot
    {
		private readonly IUnitOfWork _work;
        private readonly IRepositoryCacheProvider _cache;

		protected RepositoryBase(IUnitOfWork work)
            : this(work, RuntimeCacheProvider.Current)
        {
        }

		internal RepositoryBase(IUnitOfWork work, IRepositoryCacheProvider cache)
        {
		    if (work == null) throw new ArgumentNullException("work");
		    if (cache == null) throw new ArgumentNullException("cache");
		    _work = work;
            _cache = cache;
        }

        /// <summary>
        /// Returns the Unit of Work added to the repository
        /// </summary>
		protected internal IUnitOfWork UnitOfWork
        {
            get { return _work; }
        }

        /// <summary>
        /// Internal for testing purposes
        /// </summary>
        internal Guid UnitKey
        {
            get { return (Guid)_work.Key; }
        }

        #region IRepository<TEntity> Members

        /// <summary>
        /// Adds or Updates an entity of type TEntity
        /// </summary>
        /// <remarks>This method is backed by an <see cref="IRepositoryCacheProvider"/> cache</remarks>
        /// <param name="entity"></param>
        public void AddOrUpdate(TEntity entity)
        {
            if (entity.HasIdentity == false)
            {
                _work.RegisterAdded(entity, this);
            }
            else
            {
                _work.RegisterChanged(entity, this);
            }
        }

        /// <summary>
        /// Deletes the passed in entity
        /// </summary>
        /// <param name="entity"></param>
        public virtual void Delete(TEntity entity)
        {
            if(_work != null)
            {
                _work.RegisterRemoved(entity, this);
            }
        }

        protected abstract TEntity PerformGet(TId id);
        /// <summary>
        /// Gets an entity by the passed in Id
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public TEntity Get(TId id)
        {
            var fromCache = TryGetFromCache(id);
            if (fromCache.Success)
            {
                return fromCache.Result;
            }

            var entity = PerformGet(id);
            if (entity != null)
            {
                _cache.Save(typeof(TEntity), entity);
            }

            if (entity != null)
            {
                //on initial construction we don't want to have dirty properties tracked
                // http://issues.umbraco.org/issue/U4-1946
                var asEntity = entity as TracksChangesEntityBase;
                if (asEntity != null)
                {
                    asEntity.ResetDirtyProperties(false);
                }
            }
            
            return entity;
        }

        protected Attempt<TEntity> TryGetFromCache(TId id)
        {
            Guid key = id is int ? ConvertIdToGuid(id) : ConvertStringIdToGuid(id.ToString());
            var rEntity = _cache.GetById(typeof(TEntity), key);
            if (rEntity != null)
            {
                return Attempt.Succeed((TEntity) rEntity);
            }
            return Attempt<TEntity>.Fail();
        } 

        protected abstract IEnumerable<TEntity> PerformGetAll(params TId[] ids);
        /// <summary>
        /// Gets all entities of type TEntity or a list according to the passed in Ids
        /// </summary>
        /// <param name="ids"></param>
        /// <returns></returns>
        public IEnumerable<TEntity> GetAll(params TId[] ids)
        {
            if (ids.Length > 2000)
            {
                throw new InvalidOperationException("Cannot perform a query with more than 2000 parameters");
            }

            TEntity[] entityCollection = {};

            if (ids.Any())
            {
                var entities = _cache.GetByIds(
                    typeof (TEntity), ids.Select(id => id is int ? ConvertIdToGuid(id) : ConvertStringIdToGuid(id.ToString())).ToList())
                    .ToArray();

                if (ids.Count().Equals(entities.Count()) && entities.Any(x => x == null) == false)
                    return entities.Select(x => (TEntity)x);

                if (ids.Any())
                {
                    entityCollection = PerformGetAll(ids)
                        //ensure we don't include any null refs in the returned collection!
                        .WhereNotNull()
                        .ToArray();

                    //We need to put a threshold here! IF there's an insane amount of items
                    // coming back here we don't want to chuck it all into memory, this added cache here
                    // is more for convenience when paging stuff temporarily

                    if (entityCollection.Length > 100) return entityCollection;

                    foreach (var entity in entityCollection.Where(entity => entity != null))
                    {
                        _cache.Save(typeof(TEntity), entity);
                    }
                }                
            }
            else
            {
                var allEntities = _cache.GetAllByType(typeof (TEntity)).ToArray();
                
                if (allEntities.Any())
                {
                    //Get count of all entities of current type (TEntity) to ensure cached result is correct
                    var query = Query<TEntity>.Builder.Where(x => x.Id != 0);
                    int totalCount = PerformCount(query);

                    if(allEntities.Count() == totalCount)
                        return allEntities.Select(x => (TEntity)x);
                }
{'fraction_non_alphanumeric': 0.09993961352657005, 'fraction_numerical': 0.003170289855072464, 'mean_word_length': 1.5587099266125917, 'pattern_counts': {'":': 0, '<': 87, '<?xml version=': 0, '>': 103, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5010041', 'n_tokens_mistral': 3485, 'n_tokens_neox': 3291, 'n_words': 944}