diff --git a/InMemRepositories/XBaseInMemoryRepositoy.cs b/InMemRepositories/XBaseInMemoryRepositoy.cs new file mode 100644 index 0000000..0e07f7e --- /dev/null +++ b/InMemRepositories/XBaseInMemoryRepositoy.cs @@ -0,0 +1,486 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; +using xCommons.Extensions; +using xDataService.Configuration; +using xDataService.Extensions; +using xDataService.Interfaces; +using xModels.Base; +using xModels.Dtos; + +namespace xDataService.InMemRepositories +{ + public abstract class XBaseInMemoryRepositoy : 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 XBaseInMemoryRepositoy( + XDataServiceConfiguration configuration, + IXKeyGenerator keyGenerator = null, + IXBaseRepositoryEvents baseRepositoryEvents = null + ) + { + this.configuration = configuration; + this.keyGenerator = keyGenerator; + this.baseRepositoryEvents = baseRepositoryEvents; + } + #endregion + + // + #region Add ... + public async Task AddAsync(TEntity item, bool saveChanges = true) + { + // + // Handle Key ... + item = await HandleKeyAsync(item); + + // + // Add Item to Dictionary ... + store[item.Id] = item; + + // + // Return Item ... + return item; + } + + public async Task AddOrUpdateAsync(TEntity item, bool saveChanges = true) + { + // + var isExists = await IsExistsAsync(item.Id); + if (isExists) + { + item = await UpdateAsync(item.Id, item); + } + else + { + item = await AddAsync(item); + } + + // + return item; + } + + public async Task AddRangeAsync(IEnumerable items, bool saveChanges = true) + { + // + foreach (var item in items) + { + var handled = await HandleKeyAsync(item); + store[handled.Id] = handled; + } + } + #endregion + + // + #region Remove ... + public async Task RemoveAsync(TKey id, bool softDelete = true, bool saveChanges = true) + { + // + var isExists = await IsExistsAsync(id); + if (!isExists) + { + return null; + } + + // + var result = store[id]; + store.TryRemove(id, out _); + + // + return result; + } + + public async Task RemoveAsync(TEntity item, bool softDelete = true, bool saveChanges = true) + { + return await RemoveAsync(item.Id); + } + + public async Task RemoveRangeAsync(IEnumerable items, bool softDelete = true, bool saveChanges = true) + { + // + var isValid = !items.IsNull() && items.HasChild(); + if (!isValid) + { + return; + } + + // + foreach (var item in items) + { + await RemoveAsync(item); + } + } + #endregion + + // + #region Retrieve ... + public IQueryable AsQueryable() + { + return store.Values.AsQueryable(); + } + + public async Task GetAsync(TKey id, bool ignoreSoftDeleteds = true, bool containsDetail = false) + { + // + var isExists = await IsExistsAsync(id); + if (!isExists) + { + return null; + } + + // + return store[id]; + } + + public async Task> GetAllAsync(bool ignoreSoftDeleteds = true, bool containsDetail = false) + { + // + var result = store.Values.AsEnumerable(); + return await Task.FromResult(result); + } + + public async Task FindOneAsync(Expression> whereClause, bool ignoreSoftDeleteds = true, bool containsDetail = false) + { + // + var func = whereClause.Compile(); + var result = store.Values.FirstOrDefault(x => func(x)); + return await Task.FromResult(result); + } + + public async Task> FindManyAsync(Expression> whereClause, bool ignoreSoftDeleteds = true, bool containsDetail = false) + { + // + var func = whereClause.Compile(); + var result = store.Values.Where(x => func(x)); + return await Task.FromResult(result); + } + + public async Task> QueryAsync(XQuery query, bool ignoreSoftDeleteds = true) + { + // + // 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; + } + + public async Task> ConditionalQueryAsync(Expression> whereClause, XQuery query, bool ignoreSoftDeleteds = true) + { + // + // Normalize Query ... + query = query.NormalizeQuery(configuration); + + // + var func = whereClause.Compile(); + + // + var items = (await GetAllAsync()).Where(x => func(x)); + 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 Update ... + public async Task UpdateAsync(TKey id, TEntity item, bool saveChanges = true) + { + // + var isExists = await IsExistsAsync(id); + if (!isExists) + { + return null; + } + + // + var exists = store[id]; + exists = exists.UpdateData(exists, propertyBlackList: new List { nameof(exists.Id) }); + store[id] = exists; + + // + return exists; + } + + public async Task UpdateRangeAsync(IEnumerable items, bool saveChanges = true) + { + // + var result = false; + var isValid = !items.IsNull() && items.HasChild(); + if (!isValid) + { + return result; + } + + // + foreach (var item in items) + { + // + var updated = await UpdateAsync(item.Id, item); + if (!updated.IsNullOrDefault() && !result) + { + result = true; + } + } + + // + return result; + } + #endregion + + // + #region Count ... + public async Task CountAsync(bool ignoreSoftDeleteds = true) + { + // + var result = store.Count(); + return await Task.FromResult(result); + } + + public Task PagesCountAsync(int pageSize, int? totalItems = null) + { + return Task.FromResult(0); + } + #endregion + + // + #region Exists ... + public async Task IsExistsAsync(TKey id, bool ignoreSoftDeleteds = true) + { + // + var result = store.Keys.Contains(id); + return await Task.FromResult(result); + } + #endregion + + // + #region Unit Of Works ... + public async Task SaveChangesAsync() + { + return await Task.FromResult(0); + } + #endregion + + // + #region Keys ... + 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); + } + + 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(); + } + + public async Task HandleKeyAsync(TEntity item) + { + // + 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 ... + public void Detach(TEntity item) + { } + + public void Detach(IEnumerable items) + { } + + public void Detach(XQueryResult query) + { } + + public void Detach(XPageResponse page) + { } + #endregion + + // + #region Others ... + public void Dispose() + { } + #endregion + } +} \ No newline at end of file