using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using xCommons.Extensions; using xDataService.Configuration; using xDataService.Db; using xDataService.Extensions; using xDataService.Interfaces; using xDataService.Models; using xExceptions.Constants; using xModels.Base; using xModels.Dtos; namespace xDataService.EFRepositories { /// /// Base EFCore Base Entity Repository Pattern implementation ... /// Only Used on EFCore ... /// /// is the Entity type /// is the Entity Key Type /// is the DbContext Type public abstract class XBaseEFRepository : IXBaseRepository where T : XBaseEntity where TDbContext : XDbContext { // public readonly DbSet dbSet; private readonly IXKeyGenerator keyGenerator; public readonly IXUnitOfWorks unitOfWorks; public readonly XDataServiceConfiguration configuration; private readonly IXBaseRepositoryEvents baseRepositoryEvents; // public abstract IQueryable GetFullDbSet(); // #region Constructor ... public XBaseEFRepository( IXUnitOfWorks unitOfWorks, XDataServiceConfiguration configuration, IXKeyGenerator keyGenerator = null, IXBaseRepositoryEvents baseRepositoryEvents = null ) { this.keyGenerator = keyGenerator; // this.unitOfWorks = unitOfWorks; this.configuration = configuration; this.dbSet = unitOfWorks.GetDbSet(); this.baseRepositoryEvents = baseRepositoryEvents; } #endregion // #region Add ... public async Task AddAsync( T item, bool saveChanges = true ) { // // Handle Key ... item = await HandleKeyAsync(item); // var entry = await dbSet.AddAsync(item); // // Check action is Succeeded or not ... var isSucceed = false; if (saveChanges) { // // Get Modified Count ... var qResult = await SaveChangesAsync(); // // set isSucceed Value based on Changes ... isSucceed = qResult > 0; } // // Notify Event ... if ( isSucceed && !baseRepositoryEvents.IsNull() ) { baseRepositoryEvents .AddEvent(new XBaseEventModel(entry.Entity)); } // // Return result base on action Succeed ... return isSucceed ? entry.Entity : null; } public async Task AddOrUpdateAsync( T item, bool saveChanges = true ) { // var isExists = await IsExistsAsync(GetKey(item)); if (!isExists) { return await AddAsync( item, saveChanges ); } else { return await UpdateAsync( GetKey(item), item, saveChanges ); } } public async Task AddRangeAsync( IEnumerable items, bool saveChanges = true ) { // await dbSet.AddRangeAsync( await Task .WhenAll( items .Select(async i => await HandleKeyAsync(i)) ) ); // // Check action is Succeeded or not ... var isSucceed = false; if (saveChanges) { // // Get Modified Count ... var qResult = await SaveChangesAsync(); // // set isSucceed Value based on Changes ... isSucceed = qResult > 0; } // // Notify Event ... if ( isSucceed && !baseRepositoryEvents.IsNull() ) { baseRepositoryEvents .AddManyEvent(new XBaseEventModel>(null)); } } #endregion // #region Remove ... public async Task RemoveAsync( TKey id, bool softDelete = true, bool saveChanges = true ) { // // Retrieve Item ... var item = await GetAsync( id, ignoreSoftDeleteds: false ); if (item.IsNull()) { return null; } // // Handle Remove ... EntityEntry entry = null; if (softDelete && configuration.EnableSoftDelete) { // item.Deleted = true; entry = dbSet.Update(item); } else { entry = dbSet.Remove(item); } // // Check action is Succeeded or not ... var isSucceed = false; if (saveChanges) { // // Get Modified Count ... var qResult = await SaveChangesAsync(); // // set isSucceed Value based on Changes ... isSucceed = qResult > 0; } // // Notify Event ... if ( isSucceed && !baseRepositoryEvents.IsNull() ) { baseRepositoryEvents .RemoveEvent(new XBaseEventModel(entry.Entity)); } // // Return result base on action Succeed ... return isSucceed ? entry.Entity : null; } public async Task RemoveAsync( T item, bool softDelete = true, bool saveChanges = true ) { // // Check item Exists ... var isExists = await IsExistsAsync( GetKey(item), ignoreSoftDeleteds: false ); if (!isExists) { return null; } // // Handle Remove ... EntityEntry entry = null; if (softDelete && configuration.EnableSoftDelete) { // item.Deleted = true; entry = dbSet.Update(item); } else { entry = dbSet.Remove(item); } // // Check action is Succeeded or not ... var isSucceed = false; if (saveChanges) { // // Get Modified Count ... var qResult = await SaveChangesAsync(); // // set isSucceed Value based on Changes ... isSucceed = qResult > 0; } // // Notify Event ... if ( isSucceed && !baseRepositoryEvents.IsNull() ) { baseRepositoryEvents .RemoveEvent(new XBaseEventModel(entry.Entity)); } // // Return result base on action Succeed ... return isSucceed ? entry.Entity : null; } public async Task RemoveRangeAsync( IEnumerable items, bool softDelete = true, bool saveChanges = true ) { // // Handle Remove ... if (softDelete && configuration.EnableSoftDelete) { // foreach (var item in items) { item.Deleted = true; } // dbSet.UpdateRange(items); } else { dbSet.RemoveRange(items); } // // Check action is Succeeded or not ... var isSucceed = false; if (saveChanges) { // // Get Modified Count ... var qResult = await SaveChangesAsync(); // // set isSucceed Value based on Changes ... isSucceed = qResult > 0; } // // Notify Event ... if ( isSucceed && !baseRepositoryEvents.IsNull() ) { baseRepositoryEvents .RemoveManyEvent(new XBaseEventModel>(null)); } } #endregion // #region Retrieve ... public IQueryable AsQueryable() { return dbSet.AsQueryable(); } public async Task GetAsync( TKey id, bool ignoreSoftDeleteds = true, bool containsDetail = false ) { // var result = await FindOneAsync(i => GetKey(i) .ToString() == id .ToString(), ignoreSoftDeleteds: ignoreSoftDeleteds, containsDetail: containsDetail ); // return result; } public async Task> GetAllAsync( bool ignoreSoftDeleteds = true, bool containsDetail = false ) { return await Task.Run(() => { // var result = GetDbSet( containsDetail: containsDetail ); // // Handle Soft Deleted Items ... if (ignoreSoftDeleteds && configuration.EnableSoftDelete) { result = result.Where(x => x.Deleted == false); } // return result.AsEnumerable(); }); } public async Task FindOneAsync( Expression> whereClause, bool ignoreSoftDeleteds = true, bool containsDetail = false ) { // // Generate Where Function ... var whereFunc = whereClause.Compile(); // // Handle Soft Deleted Items ... Func ignoreSoftDeletedsWhereFunc = null; if (ignoreSoftDeleteds && configuration.EnableSoftDelete) { // Expression> ignoreSoftDeletedsWhereClause = x => x.Deleted == false; ignoreSoftDeletedsWhereFunc = ignoreSoftDeletedsWhereClause.Compile(); } // // Get Enumerable ... var enumerator = GetDbSet( containsDetail: containsDetail ) .AsAsyncEnumerable(); // T result = null; await foreach (var entity in enumerator) { // var isApproved = whereFunc(entity) && (ignoreSoftDeletedsWhereFunc.IsNull() ? true : ignoreSoftDeletedsWhereFunc(entity)); if (isApproved) { // result = entity; break; } } // return result; } public async Task> FindManyAsync( Expression> whereClause, bool ignoreSoftDeleteds = true, bool containsDetail = false ) { // // Generate Where Function ... var whereFunc = whereClause.Compile(); // // Handle Soft Deleted Items ... Func ignoreSoftDeletedsWhereFunc = null; if (ignoreSoftDeleteds && configuration.EnableSoftDelete) { // Expression> ignoreSoftDeletedsWhereClause = x => x.Deleted == false; ignoreSoftDeletedsWhereFunc = ignoreSoftDeletedsWhereClause.Compile(); } // // Get Enumerable ... var enumerator = GetDbSet( containsDetail: containsDetail ) .AsAsyncEnumerable(); // var result = new List(); await foreach (var entity in enumerator) { // var isApproved = whereFunc(entity) && (ignoreSoftDeletedsWhereFunc.IsNull() ? true : ignoreSoftDeletedsWhereFunc(entity)); if (isApproved) { result.Add(entity); } } // return result.AsEnumerable(); } public async Task> QueryAsync( XQuery query, bool ignoreSoftDeleteds = true ) { // // Validate ... if (query.IsNull()) { XException.InvalidArgs.Throw(); } // // Normalize ... query = query.NormalizeQuery(configuration); // // Handle Soft Deleted Items ... Func ignoreSoftDeletedsWhereFunc = null; if (ignoreSoftDeleteds && configuration.EnableSoftDelete) { // Expression> ignoreSoftDeletedsWhereClause = x => x.Deleted == false; ignoreSoftDeletedsWhereFunc = ignoreSoftDeletedsWhereClause.Compile(); } // // Get Enumerable ... var enumerator = GetDbSet( containsDetail: query.ContainsDetail ) .AsAsyncEnumerable(); // var totalEntities = new List(); await foreach (var entity in enumerator) { // var isApproved = (ignoreSoftDeletedsWhereFunc.IsNull() ? true : ignoreSoftDeletedsWhereFunc(entity)); if (isApproved) { totalEntities.Add(entity); } } // var items = totalEntities.AsEnumerable(); 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> condition, XQuery query, bool ignoreSoftDeleteds = true ) { // // Validate ... if (query.IsNull() || condition.IsNull()) { XException.InvalidArgs.Throw(); } // // Normalize ... query = query.NormalizeQuery(configuration); // // Handle Soft Deleted Items ... Func ignoreSoftDeletedsWhereFunc = null; if (ignoreSoftDeleteds && configuration.EnableSoftDelete) { // Expression> ignoreSoftDeletedsWhereClause = x => x.Deleted == false; ignoreSoftDeletedsWhereFunc = ignoreSoftDeletedsWhereClause.Compile(); } // // Compile Condition ... Func conditionFunc = condition.Compile(); // // Get Enumerable ... var enumerator = GetDbSet( containsDetail: query.ContainsDetail ) .AsAsyncEnumerable(); // var totalEntities = new List(); await foreach (var entity in enumerator) { // var isApproved = (ignoreSoftDeletedsWhereFunc.IsNull() ? true : ignoreSoftDeletedsWhereFunc(entity)) && conditionFunc(entity); if (isApproved) { totalEntities.Add(entity); } } // var items = totalEntities.AsEnumerable(); 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; } // // TODO: Fix this ... // public Task> RequestPageAsync ( // XPageRequest request, // bool ignoreSoftDeleteds = true, // bool containsDetail = true // ) { // // // var result = GetDbSet ( // containsDetail: containsDetail // ); // // // // Handle Soft Deleted Items ... // if (ignoreSoftDeleteds) { // result = result.Where (x => x.Deleted == false); // } // // // if (request.First.HasValue) { // // // if (!request.After.IsNullOrEmpty ()) { // // // TKey lastId = XCursorHelper.FromCursor (request.After); // result = result.Where (x => GetKey (x).ToString() > lastId.ToString()); // } // // // result = result.Take (request.First.Value); // } // // // // Apply Sorting ... // List nodes = null; // if (!request.SortBy.IsNullOrEmpty ()) { // nodes = result // .ApplySorting ( // request.SortBy, !request.DescendingSort // ).ToList (); // } // // // // Claculate Required Info ... // int totalCount = result.CountAsync ().Result; // int maxId = nodes.Max (x => Convert.ToInt32 (GetKey (x))); // int minId = nodes.Min (x => Convert.ToInt32 (GetKey (x))); // bool hasNextPage = nodes.Any (x => Convert.ToInt32 (GetKey (x)) > maxId); // bool hasPreviousPage = nodes.Any (x => Convert.ToInt32 (GetKey (x)) < minId); // // // return Task.FromResult (new XPageResponse { // Nodes = nodes, // TotalCount = totalCount, // HasNextPage = hasNextPage, // HasPreviousPage = hasPreviousPage // }); // } // // TODO: Fix this ... // public Task> RequestConditionalPageAsync ( // Expression> condition, // XPageRequest request, // bool ignoreSoftDeleteds = true, // bool containsDetail = true // ) { // // // var result = GetDbSet ( // containsDetail: containsDetail // ) // .Where (condition); // // // // Handle Soft Deleted Items ... // if (ignoreSoftDeleteds) { // result = result.Where (x => x.Deleted == false); // } // // // if (request.First.HasValue) { // // // if (!request.After.IsNullOrEmpty ()) { // // // int lastId = XCursorHelper.FromCursor (request.After); // result = result.Where (x => Convert.ToInt32 (GetKey (x)) > lastId); // } // // // result = result.Take (request.First.Value); // } // // // // Apply Sorting ... // List nodes = null; // if (!request.SortBy.IsNullOrEmpty ()) { // nodes = result // .ApplySorting ( // request.SortBy, !request.DescendingSort) // .ToList (); // } // // // // Claculate Required Info ... // int totalCount = result.CountAsync ().Result; // int maxId = nodes.Max (x => Convert.ToInt32 (GetKey (x))); // int minId = nodes.Min (x => Convert.ToInt32 (GetKey (x))); // bool hasNextPage = result.Any (x => Convert.ToInt32 (GetKey (x)) > maxId); // bool hasPreviousPage = result.Any (x => Convert.ToInt32 (GetKey (x)) < minId); // // // return Task.FromResult (new XPageResponse { // Nodes = nodes, // TotalCount = totalCount, // HasNextPage = hasNextPage, // HasPreviousPage = hasPreviousPage // }); // } #endregion // #region Update ... public async Task UpdateAsync( TKey id, T item, bool saveChanges = true ) { // // Get Exists Entity ... var existsEntity = await GetAsync( id, containsDetail: true, ignoreSoftDeleteds: false ); // // Update Data ... existsEntity.UpdateData(item); var entry = dbSet.Attach(existsEntity); entry.State = EntityState.Modified; // // Check action is Succeeded or not ... var isSucceed = false; if (saveChanges) { // // Get Modified Count ... var qResult = await SaveChangesAsync(); // // set isSucceed Value based on Changes ... isSucceed = qResult > 0; } // // Notify Event ... if ( isSucceed && !baseRepositoryEvents.IsNull() ) { baseRepositoryEvents .UpdateEvent(new XBaseEventModel(entry.Entity)); } // // Return result base on action Succeed ... return isSucceed ? entry.Entity : null; } public async Task UpdateRangeAsync( IEnumerable items, bool saveChanges = true ) { // items .ToList() .ForEach(i => { var entry = dbSet.Attach(i); entry.State = EntityState.Modified; }); // // Check action is Succeeded or not ... var isSucceed = false; if (saveChanges) { // // Get Modified Count ... var qResult = await SaveChangesAsync(); // // set isSucceed Value based on Changes ... isSucceed = qResult > 0; } // // Notify Event ... if ( isSucceed && !baseRepositoryEvents.IsNull() ) { baseRepositoryEvents .UpdateManyEvent(new XBaseEventModel>(null)); } // // Return result base on action Succeed ... return isSucceed; } #endregion // #region Count ... public async Task CountAsync(bool ignoreSoftDeleteds = true) { // var dbSet = GetDbSet(); // if (ignoreSoftDeleteds && configuration.EnableSoftDelete) { return await dbSet.CountAsync(i => i.Deleted == false); } else { return await dbSet.CountAsync(); } } public async Task PagesCountAsync( int pageSize, int? totalItems = null ) { // int count = totalItems.HasValue ? totalItems.Value : await CountAsync(); int pagesCount = count / pageSize; // if (count % pageSize > 0) { pagesCount++; } // return pagesCount; } #endregion // #region Exists ... public async Task IsExistsAsync( TKey id, bool ignoreSoftDeleteds = true ) { // var result = await FindOneAsync(i => GetKey(i) .ToString() == id .ToString(), ignoreSoftDeleteds: ignoreSoftDeleteds, containsDetail: false ); // Detach(result); // return !result.IsNull(); } #endregion // #region Unit Of Work ... public async Task SaveChangesAsync() { return await unitOfWorks.SaveChangesAsync(); } #endregion // #region Keys ... public void SetKey( ref T 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(T 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(T 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(T item) { // if (item.IsNull()) { return; } // Entry(item).State = EntityState.Detached; } public void Detach(IEnumerable items) { // if (items.IsNull() || !items.HasChild()) { return; } // items .ToList() .ForEach(item => { Detach(item); }); } public void Detach(XQueryResult query) { // if (query.IsNull() || query.Items.HasChild()) { return; } // Detach(query.Items); } public void Detach(XPageResponse page) { // if (page.IsNull() || !page.Nodes.HasChild()) { return; } // Detach(page.Nodes); } #endregion // #region Others ... public void Dispose() { unitOfWorks.Dispose(); } public string GetPropValues(T item) { // var props = item.GetType().GetProperties(); var vals = props.Select(p => p.GetValue(p.Name)); // return vals.ToJSON(); } public EntityEntry Entry(T item) { return unitOfWorks.DbContext.Entry(item); } public IQueryable GetDbSet( bool containsDetail = false ) { // IQueryable result = null; if (!containsDetail) { result = dbSet; } else { result = GetFullDbSet(); } // return result; } #endregion } }