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 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 ) { // if (query.PageSize < configuration .PagingConfiguration .MinAvailablePageSize) { query.PageSize = configuration .PagingConfiguration .DefaultPageSize; } // if (query.PageSize > configuration .PagingConfiguration .MaxAvailablePageSize) { query.PageSize = configuration .PagingConfiguration .MaxAvailablePageSize; } // // 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 resultItems = new List (); await foreach (var entity in enumerator) { // var isApproved = (ignoreSoftDeletedsWhereFunc.IsNull () ? true : ignoreSoftDeletedsWhereFunc (entity)); if (isApproved) { resultItems.Add (entity); } } // var result = resultItems.AsEnumerable (); // // Count Total Items ... var totalItemsCount = result.Count (); // // Apply Filter ... if (!query.Filter.IsNullOrEmpty ()) { result = await result .ApplyFilterAsync (query.Filter); } // // Count Total Items ... int totalFilteredItemsCount = result.Count (); // // Apply Paging ... result = result .ApplyPaging ( query.Page, query.PageSize); // // Apply Sorting ... if (!query.SortBy.IsNullOrEmpty ()) { result = result .ApplySorting ( query.SortBy, query.IsAscending); } // // Generate Result Object ... // Query = query, var queryResult = new XQueryResult { Items = result.AsEnumerable (), Page = query.Page, PageSize = query.PageSize, TotalItems = totalItemsCount, TotalPages = await PagesCountAsync ( query.PageSize, totalFilteredItemsCount ), TotalFilteredItems = totalFilteredItemsCount }; // return queryResult; } public async Task> ConditionalQueryAsync ( Expression> condition, XQuery query, bool ignoreSoftDeleteds = true ) { // if (query.PageSize < configuration .PagingConfiguration .MinAvailablePageSize) { query.PageSize = configuration .PagingConfiguration .DefaultPageSize; } // if (query.PageSize > configuration .PagingConfiguration .MaxAvailablePageSize) { query.PageSize = configuration .PagingConfiguration .MaxAvailablePageSize; } // // Generate Where Function ... var whereFunc = condition.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: query.ContainsDetail ) .AsAsyncEnumerable (); // var resultItems = new List (); await foreach (var entity in enumerator) { // var isApproved = whereFunc (entity) && (ignoreSoftDeletedsWhereFunc.IsNull () ? true : ignoreSoftDeletedsWhereFunc (entity)); if (isApproved) { resultItems.Add (entity); } } // var result = resultItems.AsQueryable (); // // Count Total Items ... var totalItemsCount = result.Count (); // // Apply Filter ... if (totalItemsCount > 0 && !query.Filter.IsNullOrEmpty ()) { result = result .ApplyFilter (query.Filter); } // // Count Total Items ... var totalFilteredItemsCount = result.Count (); // // Apply Paging ... if (totalItemsCount > 0 && totalFilteredItemsCount > 0) { result = result .ApplyPaging ( query.Page, query.PageSize); // // Apply Sorting ... if (!query.SortBy.IsNullOrEmpty ()) { result = result .ApplySorting ( query.SortBy, query.IsAscending); } } // // Generate Result Object ... var queryResult = new XQueryResult { Items = result.AsEnumerable (), Page = query.Page, PageSize = query.PageSize, TotalItems = totalItemsCount, TotalPages = await PagesCountAsync ( query.PageSize, totalFilteredItemsCount ), TotalFilteredItems = totalFilteredItemsCount }; // return queryResult; } // // 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 } }