using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Query; using xCommons.Extensions; using xDataService.Configuration; using xDataService.Extensions; using xDataService.Interfaces; using xDataService.Models; using xModels.Base; using xModels.Dtos; namespace xDataService.Providers { /// /// a Base Repository Pattern Implementation Specially for InMemory Stores using /// EF Core Capabilities ... /// /// /// public abstract class XBaseInMemoryRepository : IXBaseRepository where TEntity : XBaseEntity { // #region Props ... private readonly XDataServiceConfiguration configuration; private readonly IXKeyGenerator keyGenerator; private readonly IXBaseRepositoryEvents baseRepositoryEvents; private static readonly ConcurrentDictionary store = new ConcurrentDictionary(); #endregion // #region Constructor ... public XBaseInMemoryRepository( XDataServiceConfiguration configuration, IXKeyGenerator keyGenerator = null, IXBaseRepositoryEvents baseRepositoryEvents = null ) { this.keyGenerator = keyGenerator; this.configuration = configuration; this.baseRepositoryEvents = baseRepositoryEvents; } #endregion // #region Actions ... // #region Add ... /// /// add a new Entity ... /// /// /// /// /// public async Task AddAsync( TEntity item, bool saveChanges = true, CancellationToken cancellationToken = default ) { // // Handle Key ... item = await HandleKeyAsync( item: item, cancellationToken: cancellationToken ); // var isSucceed = false; // // Add Item to Dictionary ... try { // store[item.Id] = item; isSucceed = true; } catch { isSucceed = false; } // // Notify Event ... if ( isSucceed && !baseRepositoryEvents.IsNull() ) { // baseRepositoryEvents .AddEvent(new XBaseEventModel(item)); } // // Return Item ... return item; } /// /// add or update an Entity (add if not exists/update if exists) ... /// /// /// /// /// public async Task AddOrUpdateAsync( TEntity item, bool saveChanges = true, CancellationToken cancellationToken = default ) { // var isExists = await IsExistsAsync( id: item.Id, ignoreSoftDeleteds: true, cancellationToken: cancellationToken ); // var isSucceed = false; if (isExists) { // try { // item = await UpdateAsync( item: item, id: item.Id, saveChanges: saveChanges, cancellationToken: cancellationToken ); isSucceed = true; } catch { isSucceed = false; } } else { // try { // item = await AddAsync( item: item, saveChanges: saveChanges, cancellationToken: cancellationToken ); // isSucceed = true; } catch { isSucceed = false; } } // // Notify Event ... if ( isSucceed && !baseRepositoryEvents.IsNull() ) { // if (isExists) { // baseRepositoryEvents .UpdateEvent(new XBaseEventModel(item)); } else { // baseRepositoryEvents .AddEvent(new XBaseEventModel(item)); } } // return item; } /// /// add a range of new Entities ... /// /// /// /// /// public async Task AddRangeAsync( IEnumerable items, bool saveChanges = true, CancellationToken cancellationToken = default ) { // var isValid = !items.IsNull() && items.HasChild(); if (!isValid) { return; } // foreach (var item in items) { // await AddAsync( item: item, saveChanges: saveChanges, cancellationToken: cancellationToken ); } // // Notify Event ... if ( isValid && !baseRepositoryEvents.IsNull() ) { // baseRepositoryEvents .AddManyEvent(new XBaseEventModel>(null)); } } #endregion // #region Update ... /// /// Update an Entity values ... /// /// /// /// /// /// public async Task UpdateAsync( TKey id, TEntity item, bool saveChanges = true, CancellationToken cancellationToken = default ) { // var isExists = await IsExistsAsync( id: id, ignoreSoftDeleteds: true, cancellationToken: cancellationToken ); if (!isExists) { return null; } // var isSucceed = false; var exists = store[id]; // try { // exists = exists.UpdateData( exists, propertyBlackList: new List { nameof(exists.Id) } ); store[id] = exists; // isSucceed = true; } catch { isSucceed = false; } // // Notify Event ... if ( isSucceed && !baseRepositoryEvents.IsNull() ) { // baseRepositoryEvents .UpdateEvent(new XBaseEventModel(item)); } // return exists; } /// /// Update a range of Entities ... /// /// /// /// /// public async Task UpdateRangeAsync( IEnumerable items, bool saveChanges = true, CancellationToken cancellationToken = default ) { // var result = false; var isValid = !items.IsNull() && items.HasChild(); if (!isValid) { return result; } // foreach (var item in items) { // var updated = await UpdateAsync( item: item, id: item.Id, saveChanges: saveChanges, cancellationToken: cancellationToken ); if (!updated.IsNullOrDefault() && !result) { result = true; } } // return result; } #endregion // #region Remove ... /// /// remove an Entity by it's Id ... /// /// /// /// /// /// public async Task RemoveAsync( TKey id, bool softDelete = true, bool saveChanges = true, CancellationToken cancellationToken = default ) { // var isExists = await IsExistsAsync( id: id, cancellationToken: cancellationToken ); if (!isExists) { return null; } // TEntity result = null; if (softDelete && configuration.EnableSoftDelete) { // store[id].Deleted = true; result = store[id]; } else { // result = store[id]; store.TryRemove(id, out _); } // return result; } /// /// remove an Entity ... /// /// /// /// /// /// public async Task RemoveAsync( TEntity item, bool softDelete = true, bool saveChanges = true, CancellationToken cancellationToken = default ) { // var result = await RemoveAsync( id: item.Id, softDelete: softDelete, saveChanges: saveChanges, cancellationToken: cancellationToken ); // return result; } /// /// remove a range of exists Entities ... /// /// /// /// /// /// public async Task RemoveRangeAsync( IEnumerable items, bool softDelete = true, bool saveChanges = true, CancellationToken cancellationToken = default ) { // var isValid = !items.IsNull() && items.HasChild(); if (!isValid) { return; } // foreach (var item in items) { // await RemoveAsync( item: item, softDelete: softDelete, saveChanges: saveChanges, cancellationToken: cancellationToken ); } } #endregion // #region Count ... /// /// count all exists Entities ... /// /// /// /// public async Task CountAsync( bool ignoreSoftDeleteds = true, CancellationToken cancellationToken = default ) { // // Prepare Ignore Soft Deleted Predicator ... Expression> predicator = null; if (ignoreSoftDeleteds) { predicator = x => !x.Deleted; } var result = await AsQueryable( asNoTracking: true, orderBuilder: null, includeBuilder: null, predicate: predicator ) .CountAsync(cancellationToken); // return result; } /// /// count all exists Entities Pages by providing page size ... /// /// /// /// /// /// public async Task PagesCountAsync( int pageSize, int? totalItems = null, bool ignoreSoftDeleteds = true, CancellationToken cancellationToken = default ) { // int count = totalItems.HasValue ? totalItems.Value : await CountAsync( cancellationToken: cancellationToken, ignoreSoftDeleteds: ignoreSoftDeleteds ); int pagesCount = count / pageSize; // if (count % pageSize > 0) { pagesCount++; } // return pagesCount; } #endregion // #region Exists ... /// /// Check an Entity exists or not ... /// /// /// /// /// public async Task IsExistsAsync( TKey id, bool ignoreSoftDeleteds = true, CancellationToken cancellationToken = default ) { // // Prepare Ignore Soft Deleted Predicator ... Expression> predicator = null; if (ignoreSoftDeleteds) { predicator = x => !x.Deleted; } var result = await AsQueryable( asNoTracking: true, orderBuilder: null, includeBuilder: null, predicate: predicator ) .AnyAsync( cancellationToken: cancellationToken, predicate: x => GetKey(x).ToString() == id.ToString()); // return result; } #endregion // #region Retrieve ... /// /// retrieve whole items as queryable ... /// /// a flag for Tracking behaviour /// an Expression for Filter Items ... /// an Order Builder expression for Ordering Query ... /// an Include Builder expression for Including Navigation Properties ... /// public IQueryable AsQueryable( bool asNoTracking = true, Expression> predicate = null, Func, IOrderedQueryable> orderBuilder = null, Func, IIncludableQueryable> includeBuilder = null ) { // var result = store.Values.AsQueryable(); // // Predicate ... if (!predicate.IsNull()) { result = result.Where(predicate); } // // Apply Orders ... if (!orderBuilder.IsNull()) { result = orderBuilder(result); } // // Apply Includes ... if (!includeBuilder.IsNull()) { result = includeBuilder(result); } // if (asNoTracking) { result = result.AsNoTracking(); } // return result; } /// /// retrieve an Entity by it's Id ... /// /// /// /// /// /// public async Task GetAsync( TKey id, bool ignoreSoftDeleteds = true, Func, IIncludableQueryable> includeBuilder = null, CancellationToken cancellationToken = default ) { // // Prepare Ignore Soft Deleted Predicator ... Expression> predicator = null; if (ignoreSoftDeleteds) { predicator = x => !x.Deleted; } var result = await AsQueryable( asNoTracking: true, orderBuilder: null, predicate: predicator, includeBuilder: includeBuilder ) .Where(x => GetKey(x).ToString() == id.ToString()) .FirstOrDefaultAsync(cancellationToken); // return result; } /// /// retrieve all exists Entities ... /// /// /// /// /// /// public async Task> GetAllAsync( bool ignoreSoftDeleteds = true, Func, IOrderedQueryable> orderBuilder = null, Func, IIncludableQueryable> includeBuilder = null, CancellationToken cancellationToken = default ) { // // Prepare Ignore Soft Deleted Predicator ... Expression> predicator = null; if (ignoreSoftDeleteds) { predicator = x => !x.Deleted; } var result = await AsQueryable( asNoTracking: true, orderBuilder: null, predicate: predicator, includeBuilder: includeBuilder ) .ToListAsync(cancellationToken); // return result; } /// /// retrieve all exists Entities /// as Async Enumerable ... /// /// /// /// /// public IAsyncEnumerable GetAllAsAsyncEnumerable( bool ignoreSoftDeleteds = true, Func, IOrderedQueryable> orderBuilder = null, Func, IIncludableQueryable> includeBuilder = null ) { // Expression> predicator = null; if (ignoreSoftDeleteds) { predicator = x => !x.Deleted; } var result = AsQueryable( asNoTracking: true, predicate: predicator, orderBuilder: orderBuilder, includeBuilder: includeBuilder ) .AsAsyncEnumerable(); // return result; } /// /// find an Entity by providing a Conditional Expression ... /// /// /// /// /// /// public async Task FindOneAsync( Expression> predicate, bool ignoreSoftDeleteds = true, Func, IIncludableQueryable> includeBuilder = null, CancellationToken cancellationToken = default ) { // // Prepare Ignore Soft Deleted Predicator ... Expression> predicator = null; if (ignoreSoftDeleteds) { predicator = x => !x.Deleted; } var result = await AsQueryable( asNoTracking: true, orderBuilder: null, predicate: predicator, includeBuilder: includeBuilder ) .Where(predicate) .FirstOrDefaultAsync(cancellationToken); // return result; } /// /// find a collection of Entities by proving a Conditional Expression ... /// /// /// /// /// /// /// public async Task> FindManyAsync( Expression> predicate, bool ignoreSoftDeleteds = true, Func, IOrderedQueryable> orderBuilder = null, Func, IIncludableQueryable> includeBuilder = null, CancellationToken cancellationToken = default ) { // // Prepare Ignore Soft Deleted Predicator ... Expression> predicator = null; if (ignoreSoftDeleteds) { predicator = x => !x.Deleted; } var result = await AsQueryable( asNoTracking: true, orderBuilder: null, predicate: predicator, includeBuilder: includeBuilder ) .Where(predicate) .ToListAsync(cancellationToken); // return result; } /// /// retrieve Entities based on XQuery Pagination structure ... /// /// /// /// /// /// /// /// public async Task> QueryAsync( XQuery query, bool ignoreSoftDeleteds = true, Expression> predicate = null, Func, IOrderedQueryable> orderBuilder = null, Func, IIncludableQueryable> includeBuilder = null, CancellationToken cancellationToken = default ) { // // Normalize Query ... query = query.NormalizeQuery(configuration); // var items = await GetAllAsync(); var totalItemsCount = items.Count(); // // Apply Filter ... if (!query.Filter.IsNullOrEmpty()) { // items = items .ApplyFilter(query.Filter); } int filteredItemsCount = items.Count(); // // Count Pages ... var totalPagesCount = query.CountPages(totalItemsCount); var filteredPagesCount = query.CountPages(filteredItemsCount); // // Apply Paging and Sorting ... if (totalItemsCount > 0 && filteredItemsCount > 0) { // // Apply Sorting ... items = items .ToList() .ApplySorting( query.SortBy, query.IsAscending ); // // Apply Paging ... items = items .ToList() .ApplyPaging( query.Page, query.PageSize ); } // // Prepare Result ... var result = new XQueryResult { Page = query.Page, Items = items.ToList(), PageSize = query.PageSize, TotalPages = totalPagesCount, TotalItems = totalItemsCount, TotalFilteredPages = filteredPagesCount, TotalFilteredItems = filteredItemsCount }; // return result; } #endregion // #region Unit Of Work ... /// /// Save all unsaved Transactions on DbContext ... /// used fo Unit Of Works Design Pattern ... /// /// /// public async Task SaveChangesAsync( CancellationToken cancellationToken = default ) { // return await Task.Run( () => 0, cancellationToken ); } #endregion // #region Key ... /// /// Retrieve Key of Entity ... /// /// /// public TKey GetKey(TEntity item) { // var props = item.GetType().GetProperties(); var keyProp = props.FirstOrDefault(p => p.Name == "Id"); // var keyString = string.Empty; if (keyProp.IsNull()) { keyString = string.Empty; } else { keyString = keyProp.GetValue(item).ToString(); } // if (keyString.IsNullOrEmpty()) { return default(TKey); } // // Prevent Deserializing issues throug JsonReader ... if (keyString.IsGuid() && typeof(TKey) == typeof(Guid)) { return item.Id; } // return keyString.FromJSON(); } /// /// Set Key of Entity ... /// /// /// public void SetKey( ref TEntity item, TKey id ) { // var props = item.GetType().GetProperties(); var keyProp = props.FirstOrDefault(p => p.Name == "Id"); if (keyProp.IsNull()) { return; } // Type t = Nullable.GetUnderlyingType(keyProp.PropertyType) ?? keyProp.PropertyType; object safeValue = (id == null) ? null : Convert.ChangeType(id, t); keyProp.SetValue(item, safeValue, null); } /// /// Handle Checking Key ... /// /// /// /// public async Task HandleKeyAsync( TEntity item, CancellationToken cancellationToken = default ) { // var keyType = typeof(TKey); // // Handle Guid Key Type ... // Since EFCore has AutoIncrement on int Ids, there is no need to handle int Key types ... if ( ( keyType == typeof(Guid) || keyType == typeof(string) ) && keyGenerator.IsEmpty(item.Id) ) { // var newKey = await keyGenerator.GenerateKey(this); // SetKey(ref item, newKey); } // return item; } #endregion // #region Detach ... /// /// Detach an Entity ... /// /// public void Detach(TEntity item) { } /// /// Detach an Enumerable of Entities ... /// /// public void Detach(IEnumerable items) { } /// /// Detach a Query Result of Entity ... /// /// public void Detach(XQueryResult query) { } #endregion #endregion // #region Others ... public void Dispose() { } #endregion // #region Private ... #endregion } }