using System.Linq.Expressions; using System.Transactions; using Nop.Core; using Nop.Core.Caching; using Nop.Core.Configuration; using Nop.Core.Domain.Common; using Nop.Core.Events; namespace Nop.Data; /// /// Represents the entity repository implementation /// /// Entity type public partial class EntityRepository : IRepository where TEntity : BaseEntity { #region Fields protected readonly IEventPublisher _eventPublisher; protected readonly INopDataProvider _dataProvider; protected readonly IShortTermCacheManager _shortTermCacheManager; protected readonly IStaticCacheManager _staticCacheManager; protected readonly bool _usingDistributedCache; #endregion #region Ctor public EntityRepository(IEventPublisher eventPublisher, INopDataProvider dataProvider, IShortTermCacheManager shortTermCacheManager, IStaticCacheManager staticCacheManager, AppSettings appSettings) { _eventPublisher = eventPublisher; _dataProvider = dataProvider; _shortTermCacheManager = shortTermCacheManager; _staticCacheManager = staticCacheManager; _usingDistributedCache = appSettings.Get().DistributedCacheType switch { DistributedCacheType.Redis => true, DistributedCacheType.SqlServer => true, _ => false }; } #endregion #region Utilities /// /// Get all entity entries /// /// Function to select entries /// Function to get a cache key; pass null to don't cache; return null from this function to use the default key /// /// A task that represents the asynchronous operation /// The task result contains the entity entries /// protected virtual async Task> GetEntitiesAsync(Func>> getAllAsync, Func getCacheKey) { if (getCacheKey == null) return await getAllAsync(); //caching var cacheKey = getCacheKey(_staticCacheManager) ?? _staticCacheManager.PrepareKeyForDefaultCache(NopEntityCacheDefaults.AllCacheKey); return await _staticCacheManager.GetAsync(cacheKey, getAllAsync); } /// /// Get all entity entries /// /// Function to select entries /// Function to get a cache key; pass null to don't cache; return null from this function to use the default key /// Entity entries protected virtual IList GetEntities(Func> getAll, Func getCacheKey) { if (getCacheKey == null) return getAll(); //caching var cacheKey = getCacheKey(_staticCacheManager) ?? _staticCacheManager.PrepareKeyForDefaultCache(NopEntityCacheDefaults.AllCacheKey); return _staticCacheManager.Get(cacheKey, getAll); } /// /// Get all entity entries /// /// Function to select entries /// Function to get a cache key; pass null to don't cache; return null from this function to use the default key /// /// A task that represents the asynchronous operation /// The task result contains the entity entries /// protected virtual async Task> GetEntitiesAsync(Func>> getAllAsync, Func> getCacheKey) { if (getCacheKey == null) return await getAllAsync(); //caching var cacheKey = await getCacheKey(_staticCacheManager) ?? _staticCacheManager.PrepareKeyForDefaultCache(NopEntityCacheDefaults.AllCacheKey); return await _staticCacheManager.GetAsync(cacheKey, getAllAsync); } /// /// Adds "deleted" filter to query which contains entries, if its need /// /// Entity entries /// Whether to include deleted items /// Entity entries protected virtual IQueryable AddDeletedFilter(IQueryable query, in bool includeDeleted) { if (includeDeleted) return query; if (typeof(TEntity).GetInterface(nameof(ISoftDeletedEntity)) == null) return query; return query.OfType().Where(entry => !entry.Deleted).OfType(); } /// /// Transactionally deletes a list of entities /// /// Entities to delete protected virtual async Task DeleteAsync(IList entities) { using var transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled); await _dataProvider.BulkDeleteEntitiesAsync(entities); transaction.Complete(); } /// /// Soft-deletes entities /// /// Entities to delete protected virtual async Task DeleteAsync(IList entities) where T : ISoftDeletedEntity, TEntity { foreach (var entity in entities) entity.Deleted = true; await _dataProvider.UpdateEntitiesAsync(entities); } #endregion #region Methods /// /// Get the entity entry /// /// Entity entry identifier /// Function to get a cache key; pass null to don't cache; return null from this function to use the default key /// Whether to include deleted items (applies only to entities) /// Whether to use short term cache instead of static cache /// /// A task that represents the asynchronous operation /// The task result contains the entity entry /// public virtual async Task GetByIdAsync(int? id, Func getCacheKey = null, bool includeDeleted = true, bool useShortTermCache = false) { if (!id.HasValue || id == 0) return null; async Task getEntityAsync() { return await AddDeletedFilter(Table, includeDeleted).FirstOrDefaultAsync(entity => entity.Id == Convert.ToInt32(id)); } if (getCacheKey == null) return await getEntityAsync(); ICacheKeyService cacheKeyService = useShortTermCache ? _shortTermCacheManager : _staticCacheManager; //caching var cacheKey = getCacheKey(cacheKeyService) ?? cacheKeyService.PrepareKeyForDefaultCache(NopEntityCacheDefaults.ByIdCacheKey, id); if (useShortTermCache) return await _shortTermCacheManager.GetAsync(getEntityAsync, cacheKey); return await _staticCacheManager.GetAsync(cacheKey, getEntityAsync); } /// /// Get the entity entry /// /// Entity entry identifier /// Function to get a cache key; pass null to don't cache; return null from this function to use the default key /// Whether to include deleted items (applies only to entities) /// /// The entity entry /// public virtual TEntity GetById(int? id, Func getCacheKey = null, bool includeDeleted = true) { if (!id.HasValue || id == 0) return null; TEntity getEntity() { return AddDeletedFilter(Table, includeDeleted).FirstOrDefault(entity => entity.Id == Convert.ToInt32(id)); } if (getCacheKey == null) return getEntity(); //caching var cacheKey = getCacheKey(_staticCacheManager) ?? _staticCacheManager.PrepareKeyForDefaultCache(NopEntityCacheDefaults.ByIdCacheKey, id); return _staticCacheManager.Get(cacheKey, getEntity); } /// /// Get entity entries by identifiers /// /// Entity entry identifiers /// Function to get a cache key; pass null to don't cache; return null from this function to use the default key /// Whether to include deleted items (applies only to entities) /// /// A task that represents the asynchronous operation /// The task result contains the entity entries /// public virtual async Task> GetByIdsAsync(IList ids, Func getCacheKey = null, bool includeDeleted = true) { if (ids?.Any() != true) return new List(); static IList sortByIdList(IList listOfId, IDictionary entitiesById) { var sortedEntities = new List(listOfId.Count); foreach (var id in listOfId) if (entitiesById.TryGetValue(id, out var entry)) sortedEntities.Add(entry); return sortedEntities; } async Task> getByIdsAsync(IList listOfId, bool sort = true) { var query = AddDeletedFilter(Table, includeDeleted) .Where(entry => listOfId.Contains(entry.Id)); return sort ? sortByIdList(listOfId, await query.ToDictionaryAsync(entry => entry.Id)) : await query.ToListAsync(); } if (getCacheKey == null) return await getByIdsAsync(ids); //caching var cacheKey = getCacheKey(_staticCacheManager); if (cacheKey == null && _usingDistributedCache) cacheKey = _staticCacheManager.PrepareKeyForDefaultCache(NopEntityCacheDefaults.ByIdsCacheKey, ids); if (cacheKey != null) return await _staticCacheManager.GetAsync(cacheKey, async () => await getByIdsAsync(ids)); //if we are using an in-memory cache, we can optimize by caching each entity individually for maximum reusability. //with a distributed cache, the overhead would be too high. var cachedById = await ids .Distinct() .SelectAwait(async id => await _staticCacheManager.GetAsync( _staticCacheManager.PrepareKeyForDefaultCache(NopEntityCacheDefaults.ByIdCacheKey, id), default(TEntity))) .Where(entity => entity != default) .ToDictionaryAsync(entity => entity.Id, entity => entity); var missingIds = ids.Except(cachedById.Keys).ToList(); var missingEntities = missingIds.Count > 0 ? await getByIdsAsync(missingIds, false) : new List(); foreach (var entity in missingEntities) { await _staticCacheManager.SetAsync(_staticCacheManager.PrepareKeyForDefaultCache(NopEntityCacheDefaults.ByIdCacheKey, entity.Id), entity); cachedById[entity.Id] = entity; } return sortByIdList(ids, cachedById); } /// /// Get all entity entries /// /// Function to select entries /// Function to get a cache key; pass null to don't cache; return null from this function to use the default key /// Whether to include deleted items (applies only to entities) /// /// A task that represents the asynchronous operation /// The task result contains the entity entries /// public virtual async Task> GetAllAsync(Func, IQueryable> func = null, Func getCacheKey = null, bool includeDeleted = true) { async Task> getAllAsync() { var query = AddDeletedFilter(Table, includeDeleted); query = func != null ? func(query) : query; return await query.ToListAsync(); } return await GetEntitiesAsync(getAllAsync, getCacheKey); } /// /// Get all entity entries /// /// Function to select entries /// Function to get a cache key; pass null to don't cache; return null from this function to use the default key /// Whether to include deleted items (applies only to entities) /// Entity entries public virtual IList GetAll(Func, IQueryable> func = null, Func getCacheKey = null, bool includeDeleted = true) { IList getAll() { var query = AddDeletedFilter(Table, includeDeleted); query = func != null ? func(query) : query; return query.ToList(); } return GetEntities(getAll, getCacheKey); } /// /// Get all entity entries /// /// Function to select entries /// Function to get a cache key; pass null to don't cache; return null from this function to use the default key /// Whether to include deleted items (applies only to entities) /// /// A task that represents the asynchronous operation /// The task result contains the entity entries /// public virtual async Task> GetAllAsync( Func, Task>> func = null, Func getCacheKey = null, bool includeDeleted = true) { async Task> getAllAsync() { var query = AddDeletedFilter(Table, includeDeleted); query = func != null ? await func(query) : query; return await query.ToListAsync(); } return await GetEntitiesAsync(getAllAsync, getCacheKey); } /// /// Get all entity entries /// /// Function to select entries /// Function to get a cache key; pass null to don't cache; return null from this function to use the default key /// Whether to include deleted items (applies only to entities) /// /// A task that represents the asynchronous operation /// The task result contains the entity entries /// public virtual async Task> GetAllAsync( Func, Task>> func = null, Func> getCacheKey = null, bool includeDeleted = true) { async Task> getAllAsync() { var query = AddDeletedFilter(Table, includeDeleted); query = func != null ? await func(query) : query; return await query.ToListAsync(); } return await GetEntitiesAsync(getAllAsync, getCacheKey); } /// /// Get paged list of all entity entries /// /// Function to select entries /// Page index /// Page size /// Whether to get only the total number of entries without actually loading data /// Whether to include deleted items (applies only to entities) /// /// A task that represents the asynchronous operation /// The task result contains the paged list of entity entries /// public virtual async Task> GetAllPagedAsync(Func, IQueryable> func = null, int pageIndex = 0, int pageSize = int.MaxValue, bool getOnlyTotalCount = false, bool includeDeleted = true) { var query = AddDeletedFilter(Table, includeDeleted); query = func != null ? func(query) : query; return await query.ToPagedListAsync(pageIndex, pageSize, getOnlyTotalCount); } /// /// Get paged list of all entity entries /// /// Function to select entries /// Page index /// Page size /// Whether to get only the total number of entries without actually loading data /// Whether to include deleted items (applies only to entities) /// /// A task that represents the asynchronous operation /// The task result contains the paged list of entity entries /// public virtual async Task> GetAllPagedAsync(Func, Task>> func = null, int pageIndex = 0, int pageSize = int.MaxValue, bool getOnlyTotalCount = false, bool includeDeleted = true) { var query = AddDeletedFilter(Table, includeDeleted); query = func != null ? await func(query) : query; return await query.ToPagedListAsync(pageIndex, pageSize, getOnlyTotalCount); } /// /// Insert the entity entry /// /// Entity entry /// Whether to publish event notification /// A task that represents the asynchronous operation public virtual async Task InsertAsync(TEntity entity, bool publishEvent = true) { ArgumentNullException.ThrowIfNull(entity); await _dataProvider.InsertEntityAsync(entity); //event notification if (publishEvent) await _eventPublisher.EntityInsertedAsync(entity); } /// /// Insert the entity entry /// /// Entity entry /// Whether to publish event notification public virtual void Insert(TEntity entity, bool publishEvent = true) { ArgumentNullException.ThrowIfNull(entity); _dataProvider.InsertEntity(entity); //event notification if (publishEvent) _eventPublisher.EntityInserted(entity); } /// /// Insert entity entries /// /// Entity entries /// Whether to publish event notification /// A task that represents the asynchronous operation public virtual async Task InsertAsync(IList entities, bool publishEvent = true) { ArgumentNullException.ThrowIfNull(entities); using var transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled); await _dataProvider.BulkInsertEntitiesAsync(entities); transaction.Complete(); if (!publishEvent) return; //event notification foreach (var entity in entities) await _eventPublisher.EntityInsertedAsync(entity); } /// /// Insert entity entries /// /// Entity entries /// Whether to publish event notification public virtual void Insert(IList entities, bool publishEvent = true) { ArgumentNullException.ThrowIfNull(entities); using var transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled); _dataProvider.BulkInsertEntities(entities); transaction.Complete(); if (!publishEvent) return; //event notification foreach (var entity in entities) _eventPublisher.EntityInserted(entity); } /// /// Loads the original copy of the entity /// /// Entity /// /// A task that represents the asynchronous operation /// The task result contains the copy of the passed entity /// public virtual async Task LoadOriginalCopyAsync(TEntity entity) { return await _dataProvider.GetTable() .FirstOrDefaultAsync(e => e.Id == Convert.ToInt32(entity.Id)); } /// /// Update the entity entry /// /// Entity entry /// Whether to publish event notification /// A task that represents the asynchronous operation public virtual async Task UpdateAsync(TEntity entity, bool publishEvent = true) { ArgumentNullException.ThrowIfNull(entity); await _dataProvider.UpdateEntityAsync(entity); //event notification if (publishEvent) await _eventPublisher.EntityUpdatedAsync(entity); } /// /// Update the entity entry /// /// Entity entry /// Whether to publish event notification public virtual void Update(TEntity entity, bool publishEvent = true) { ArgumentNullException.ThrowIfNull(entity); _dataProvider.UpdateEntity(entity); //event notification if (publishEvent) _eventPublisher.EntityUpdated(entity); } /// /// Update entity entries /// /// Entity entries /// Whether to publish event notification /// A task that represents the asynchronous operation public virtual async Task UpdateAsync(IList entities, bool publishEvent = true) { ArgumentNullException.ThrowIfNull(entities); if (!entities.Any()) return; await _dataProvider.UpdateEntitiesAsync(entities); //event notification if (!publishEvent) return; foreach (var entity in entities) await _eventPublisher.EntityUpdatedAsync(entity); } /// /// Update entity entries /// /// Entity entries /// Whether to publish event notification public virtual void Update(IList entities, bool publishEvent = true) { ArgumentNullException.ThrowIfNull(entities); if (!entities.Any()) return; _dataProvider.UpdateEntities(entities); //event notification if (!publishEvent) return; foreach (var entity in entities) _eventPublisher.EntityUpdated(entity); } /// /// Delete the entity entry /// /// Entity entry /// Whether to publish event notification /// A task that represents the asynchronous operation public virtual async Task DeleteAsync(TEntity entity, bool publishEvent = true) { ArgumentNullException.ThrowIfNull(entity); switch (entity) { case ISoftDeletedEntity softDeletedEntity: softDeletedEntity.Deleted = true; await _dataProvider.UpdateEntityAsync(entity); break; default: await _dataProvider.DeleteEntityAsync(entity); break; } //event notification if (publishEvent) await _eventPublisher.EntityDeletedAsync(entity); } /// /// Delete the entity entry /// /// Entity entry /// Whether to publish event notification public virtual void Delete(TEntity entity, bool publishEvent = true) { ArgumentNullException.ThrowIfNull(entity); switch (entity) { case ISoftDeletedEntity softDeletedEntity: softDeletedEntity.Deleted = true; _dataProvider.UpdateEntity(entity); break; default: _dataProvider.DeleteEntity(entity); break; } //event notification if (publishEvent) _eventPublisher.EntityDeleted(entity); } /// /// Delete entity entries /// /// Entity entries /// Whether to publish event notification /// A task that represents the asynchronous operation public virtual async Task DeleteAsync(IList entities, bool publishEvent = true) { ArgumentNullException.ThrowIfNull(entities); if (!entities.Any()) return; await DeleteAsync(entities); //event notification if (!publishEvent) return; foreach (var entity in entities) await _eventPublisher.EntityDeletedAsync(entity); } /// /// Delete entity entries by the passed predicate /// /// A function to test each element for a condition /// /// A task that represents the asynchronous operation /// The task result contains the number of deleted records /// public virtual async Task DeleteAsync(Expression> predicate) { ArgumentNullException.ThrowIfNull(predicate); using var transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled); var countDeletedRecords = await _dataProvider.BulkDeleteEntitiesAsync(predicate); transaction.Complete(); return countDeletedRecords; } /// /// Delete entity entries by the passed predicate /// /// A function to test each element for a condition /// /// The number of deleted records /// public virtual int Delete(Expression> predicate) { ArgumentNullException.ThrowIfNull(predicate); using var transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled); var countDeletedRecords = _dataProvider.BulkDeleteEntities(predicate); transaction.Complete(); return countDeletedRecords; } /// /// Truncates database table /// /// Performs reset identity column /// A task that represents the asynchronous operation public virtual async Task TruncateAsync(bool resetIdentity = false) { await _dataProvider.TruncateAsync(resetIdentity); } #endregion #region Properties /// /// Gets a table /// public virtual IQueryable Table => _dataProvider.GetTable(); #endregion }