using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Query; 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.Providers { /// /// a Base Repository Pattern Implementation Specially for EF DBs using /// EF Core Capabilities ... /// /// /// /// public abstract class XBaseEFRepository : IXBaseRepository where T : XBaseEntity where TDbContext : XDbContext { // #region Properties ... // public readonly DbSet dbSet; private readonly IXKeyGenerator keyGenerator; public readonly IXUnitOfWorks unitOfWorks; public readonly XDataServiceConfiguration configuration; private readonly IXBaseRepositoryEvents baseRepositoryEvents; #endregion // #region Constructor ... public XBaseEFRepository( IXUnitOfWorks unitOfWorks, XDataServiceConfiguration configuration, IXKeyGenerator keyGenerator = null, IXBaseRepositoryEvents baseRepositoryEvents = null ) { // this.unitOfWorks = unitOfWorks; this.keyGenerator = keyGenerator; this.configuration = configuration; this.dbSet = unitOfWorks.GetDbSet(); this.baseRepositoryEvents = baseRepositoryEvents; } #endregion // #region Actions ... // #region Add ... /// /// add a new Entity ... /// /// /// /// /// public async Task AddAsync( T item, bool saveChanges = true, CancellationToken cancellationToken = default ) { // // Handle Key ... item = await HandleKeyAsync( item: item, cancellationToken: cancellationToken ); // var entry = await dbSet.AddAsync( entity: item, cancellationToken: cancellationToken ); // // Check action is Succeeded or not ... var isSucceed = false; if (saveChanges) { // // Get Modified Count ... var qResult = await SaveChangesAsync(cancellationToken); // // 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; } /// /// add or update an Entity (add if not exists/update if exists) ... /// /// /// /// /// public async Task AddOrUpdateAsync( T item, bool saveChanges = true, CancellationToken cancellationToken = default ) { // var isExists = await IsExistsAsync( id: GetKey(item), cancellationToken: cancellationToken ); if (!isExists) { // return await AddAsync( item: item, saveChanges: saveChanges, cancellationToken: cancellationToken ); } else { // return await UpdateAsync( item: item, id: GetKey(item), saveChanges: saveChanges, cancellationToken: cancellationToken ); } } /// /// add a range of new Entities ... /// /// /// /// /// public async Task AddRangeAsync( IEnumerable items, bool saveChanges = true, CancellationToken cancellationToken = default ) { // // Preparing Ids and Keys ... await dbSet.AddRangeAsync( await Task .WhenAll( items .Select(async i => await HandleKeyAsync(i)) ), cancellationToken: cancellationToken ); // // Check action is Succeeded or not ... var isSucceed = false; if (saveChanges) { // // Get Modified Count ... var qResult = await SaveChangesAsync(cancellationToken); // // set isSucceed Value based on Changes ... isSucceed = qResult > 0; } // // Notify Event ... if ( isSucceed && !baseRepositoryEvents.IsNull() ) { // baseRepositoryEvents .AddManyEvent(new XBaseEventModel>(null)); } } #endregion // #region Update ... /// /// Update an Entity values ... /// /// /// /// /// /// public async Task UpdateAsync( TKey id, T item, bool saveChanges = true, CancellationToken cancellationToken = default ) { // // Get Exists Entity ... var existsEntity = await GetAsync( id: id, ignoreSoftDeleteds: false, cancellationToken: cancellationToken ); // // 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(cancellationToken); // // 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; } /// /// Update a range of Entities ... /// /// /// /// /// public async Task UpdateRangeAsync( IEnumerable items, bool saveChanges = true, CancellationToken cancellationToken = default ) { // 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(cancellationToken); // // 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 Remove ... /// /// remove an Entity by it's Id ... /// /// /// /// /// /// public async Task RemoveAsync( TKey id, bool softDelete = true, bool saveChanges = true, CancellationToken cancellationToken = default ) { // // Retrieve Item ... var item = await GetAsync( id, ignoreSoftDeleteds: false, cancellationToken: cancellationToken ); if (item.IsNullOrDefault()) { 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(cancellationToken); // // 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; } /// /// remove an Entity ... /// /// /// /// /// /// public async Task RemoveAsync( T 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 ) { // // 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(cancellationToken); // // set isSucceed Value based on Changes ... isSucceed = qResult > 0; } // // Notify Event ... if ( isSucceed && !baseRepositoryEvents.IsNull() ) { // baseRepositoryEvents .RemoveManyEvent(new XBaseEventModel>(null)); } } #endregion // #region Count ... /// /// count all exists Entities ... /// /// /// /// public async Task CountAsync( bool ignoreSoftDeleteds = true, CancellationToken cancellationToken = default ) { // var result = 0; // if (ignoreSoftDeleteds) { // result = await dbSet.CountAsync( predicate: x => !x.Deleted, cancellationToken: cancellationToken ); } else { result = await dbSet.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 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 = dbSet .AsQueryable(); // // Apply Predicate ... if (!predicate.IsNull()) { result = result .Where(predicate); } // // Apply Includes ... if (!includeBuilder.IsNull()) { result = includeBuilder(result); } // // Apply Ordering ... if (!orderBuilder.IsNull()) { result = orderBuilder(result); } // 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 ) .ToListAsync(cancellationToken)) .FirstOrDefault(x => GetKey(x).ToString() == id.ToString()); // 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, predicate: predicator, orderBuilder: orderBuilder, 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 ) { // // Prepare Ignore Soft Deleted Predicator ... 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 ) .ToListAsync(cancellationToken)) .FirstOrDefault(predicate.Compile()); // 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, predicate: predicator, orderBuilder: orderBuilder, includeBuilder: includeBuilder ) .ToListAsync(cancellationToken)) .Where(predicate.Compile()); // 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 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 ) .ToListAsync(cancellationToken)) .Any(predicate: x => GetKey(x).ToString() == id.ToString()); // 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 unitOfWorks.SaveChangesAsync(cancellationToken); } #endregion // #region Key ... /// /// Retrieve Key of Entity ... /// /// /// 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(); } /// /// Set Key of Entity ... /// /// /// 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); } /// /// Handle Checking Key ... /// /// /// /// public async Task HandleKeyAsync( T 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(T item) { // if (item.IsNull()) { return; } // Entry(item).State = EntityState.Detached; } /// /// Detach an Enumerable of Entities ... /// /// public void Detach(IEnumerable items) { // if (items.IsNull() || !items.HasChild()) { return; } // items .ToList() .ForEach(item => { Detach(item); }); } /// /// Detach a Query Result of Entity ... /// /// public void Detach(XQueryResult query) { // if (query.IsNull() || query.Items.HasChild()) { return; } // Detach(query.Items); } #endregion #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); } #endregion // #region Private ... #endregion } }