protected T CheckNullStruct <T, TCheckId>([CanBeNull] T entry, TCheckId id) where T : class where TCheckId : struct { return(entry ?? throw new ApiErrorException(ApiNotFoundError.Create(typeof(T).Name, id))); }
/// <inheritdoc /> public async Task InternalBatchDeleteAsync( TDbContext db, TSession session, TState state, TId[] ids, BenchmarkSection benchmarks) { if (db == null) { throw new ArgumentNullException(nameof(db)); } if (session == null) { throw new ArgumentNullException(nameof(session)); } if (state == null) { throw new ArgumentNullException(nameof(state)); } if (ids == null) { throw new ArgumentNullException(nameof(ids)); } if (ids.Length == 0) { return; } using (var benchmarkSection = benchmarks.CreateSection($"{nameof(InternalDeleteAsync)}<{typeof(TModel).Name}>")) { var currentDbModels = new TDbModel[ids.Length]; var currentModels = new TModel[ids.Length]; using (benchmarkSection.CreateBenchmark("Query Current Models")) { var dbModelQuery = FilterDbModelCollection(GetDbSet(db)) .Where(d => ids.Contains(d.Id)); var dbMetaDataQuery = SelectDbMetaData(db, session, dbModelQuery); var currentModelDataById = await dbModelQuery .Join(dbMetaDataQuery, dbModel => dbModel.Id, dbModelMetaData => dbModelMetaData.Id, (dbModel, dbModelMetaData) => new { DbModel = dbModel, DbModelMetaData = dbModelMetaData }) .ToDictionaryAsync(x => x.DbModel.Id); if (ids.Length != currentModelDataById.Count) { throw new ApiErrorException(ids .Where(id => !currentModelDataById.ContainsKey(id)) .Select(id => TranslateApiError(ApiNotFoundError.Create(typeof(TModel).Name, id))) .ToArray()); } var index = 0; foreach (var currentModelData in currentModelDataById.Values) { currentDbModels[index] = currentModelData.DbModel; var model = DbModelToModel(currentModelData.DbModelMetaData); model.AttachService(Service, session); currentModels[index] = model; ++index; } } await DeleteAsyncCore( db, session, currentDbModels, currentModels, benchmarkSection); using (benchmarkSection.CreateBenchmark("SaveChangesAsync")) { try { await db.SaveChangesAsync(state); } catch (DbUpdateConcurrencyException) { throw new ApiErrorException(new ApiOptimisticLockingError <TModel>( string.Format(Resources.ServiceCrudNode_OptimisticLocking, typeof(TModel).Name))); } catch (DbUpdateException e) { throw TranslateDbUpdateExceptionCore(e); } #if !NET_CORE catch (DbEntityValidationException e) { throw TranslateDbEntityValidationException(e); } #endif } } }
/// <inheritdoc /> public async Task <TModel[]> InternalBatchUpdateAsync( TDbContext db, TSession session, TState state, TModel[] models, Func <string, bool>[] updatedProperties, BenchmarkSection benchmarks) { if (models == null || models.Any(model => model == null)) { throw new ArgumentNullException(nameof(models)); } if (updatedProperties == null) { throw new ArgumentNullException(nameof(updatedProperties)); } if (models.GroupBy(model => model.Id).Any(group => group.Count() != 1)) { throw new InvalidOperationException("Cannot update the same element twice in one batch."); } if (models.Length == 0) { return(new TModel[0]); } using (var benchmarkSection = benchmarks.CreateSection($"{nameof(InternalBatchUpdateAsync)}<{typeof(TModel).Name}>")) { var mapping = models .Select((model, idx) => new { Idx = idx, model.Id }) .ToDictionary(x => x.Id, x => x.Idx); var ids = mapping.Keys.ToArray(); var currentDbModels = new TDbModel[ids.Length]; var currentModels = new TModel[ids.Length]; using (benchmarkSection.CreateBenchmark("Query Current Models")) { var dbModelQuery = FilterDbModelCollection(GetDbSet(db)) .Where(d => ids.Contains(d.Id)); var dbMetaDataQuery = SelectDbMetaData(db, session, dbModelQuery); var currentModelDataById = await dbModelQuery .Join(dbMetaDataQuery, dbModel => dbModel.Id, dbModelMetaData => dbModelMetaData.Id, (dbModel, dbModelMetaData) => new { DbModel = dbModel, DbModelMetaData = dbModelMetaData }) .ToDictionaryAsync(x => x.DbModel.Id); if (ids.Length != currentModelDataById.Count) { throw new ApiErrorException(ids .Where(id => !currentModelDataById.ContainsKey(id)) .Select(id => TranslateApiError(ApiNotFoundError.Create(typeof(TModel).Name, id))) .ToArray()); } foreach (var currentModelData in currentModelDataById.Values) { if (!mapping.TryGetValue(currentModelData.DbModel.Id, out var index)) { continue; } currentDbModels[index] = currentModelData.DbModel; var model = DbModelToModel(currentModelData.DbModelMetaData); model.AttachService(Service, session); currentModels[index] = model; } } using (benchmarkSection.CreateBenchmark("Permission check")) { if (currentModels.Any(currentModel => !PermissionResolverHelper.CanAccessRecord(session.PermissionResolver, AccessType.Update, currentModel))) { throw new ApiErrorException(TranslateApiError(new ApiForbiddenNoRightsError( string.Format(Resources.ServiceCrudNode_Update, typeof(TModel).Name)))); } } using (benchmarkSection.CreateBenchmark("UpdateDbModel")) { for (var i = 0; i < currentModels.Length; i++) { var model = models[i]; var updatedPropertiesSingle = updatedProperties[i]; var currentDbModel = currentDbModels[i]; var updatedDbModel = await UpdateDbModel(db, session, currentDbModel, model, updatedPropertiesSingle); // ReSharper disable once SuspiciousTypeConversion.Global if (updatedDbModel is IOptimisticLockable lockableDbModel) { db.Entry(updatedDbModel).OriginalValues[nameof(IOptimisticLockable.SysStartTime)] = lockableDbModel.SysStartTime; } } } using (benchmarkSection.CreateBenchmark("SaveChangesAsync")) { try { await db.SaveChangesAsync(state); } catch (DbUpdateConcurrencyException) { throw new ApiErrorException( new ApiOptimisticLockingError <TModel>(string.Format(Resources.ServiceCrudNode_OptimisticLocking, typeof(TModel).Name))); } catch (DbUpdateException e) { throw TranslateDbUpdateExceptionCore(e); } #if !NET_CORE catch (DbEntityValidationException e) { throw TranslateDbEntityValidationException(e); } #endif } using (benchmarkSection.CreateBenchmark("InternalGetByIdsAsync")) { models = await InternalGetByIdsAsync(db, session, ids, false, false, benchmarkSection); if (models.Any(m => m == null)) { throw new Exception($"Could not find element by id after batch update (Entity = {typeof(TModel).Name})."); } } return(models); } }
/// <inheritdoc /> public async Task InternalDeleteAsync( TDbContext db, TSession session, TState state, TId id, BenchmarkSection benchmarks) { using (var benchmarkSection = benchmarks.CreateSection($"{nameof(InternalDeleteAsync)}<{typeof(TModel).Name}>")) { TDbModel currentDbModel; TModel currentModel; using (benchmarkSection.CreateBenchmark("Query Current Models")) { var whereExpressionParam = Expression.Parameter(typeof(TDbModel), "x"); var whereExpressionBody = Expression.Equal(Expression.Property(whereExpressionParam, nameof(IId <TId> .Id)), Expression.Constant(id, typeof(TId))); var dbModelQuery = FilterDbModelCollection(GetDbSet(db)) .Where(Expression.Lambda <Func <TDbModel, bool> >(whereExpressionBody, whereExpressionParam)); var dbMetaDataQuery = SelectDbMetaData(db, session, dbModelQuery); var currentModelData = (await dbModelQuery .Join(dbMetaDataQuery, dbModel => dbModel.Id, dbModelMetaData => dbModelMetaData.Id, (dbModel, dbModelMetaData) => new { DbModel = dbModel, DbModelMetaData = dbModelMetaData }) .SingleOrDefaultAsync()) ?? throw new ApiErrorException(TranslateApiError(ApiNotFoundError.Create(typeof(TModel).Name, id))); currentDbModel = currentModelData.DbModel; currentModel = DbModelToModel(currentModelData.DbModelMetaData); currentModel.AttachService(Service, session); } await DeleteAsyncCore( db, session, new[] { currentDbModel }, new[] { currentModel }, benchmarkSection); using (benchmarkSection.CreateBenchmark("SaveChangesAsync")) { try { await db.SaveChangesAsync(state); } catch (DbUpdateConcurrencyException) { throw new ApiErrorException( new ApiOptimisticLockingError <TModel>(string.Format(Resources.ServiceCrudNode_OptimisticLocking, typeof(TModel).Name))); } catch (DbUpdateException e) { throw TranslateDbUpdateExceptionCore(e); } #if !NET_CORE catch (DbEntityValidationException e) { throw TranslateDbEntityValidationException(e); } #endif } } }
/// <inheritdoc /> public async Task <TModel> InternalUpdateAsync( TDbContext db, TSession session, TState state, TModel model, Func <string, bool> updatedProperties, BenchmarkSection benchmarks) { if (model == null) { throw new ArgumentNullException(nameof(model)); } using (var benchmarkSection = benchmarks.CreateSection($"{nameof(InternalUpdateAsync)}<{typeof(TModel).Name}>")) { var id = model.Id; TDbModel currentDbModel; TModel currentModel; using (benchmarkSection.CreateBenchmark("Query Current Models")) { var whereExpressionParam = Expression.Parameter(typeof(TDbModel), "x"); var whereExpressionBody = Expression.Equal(Expression.Property(whereExpressionParam, nameof(IId <TId> .Id)), Expression.Constant(id, typeof(TId))); var dbModelQuery = FilterForDirectReadDbModelCollection(GetDbSet(db)) .Where(Expression.Lambda <Func <TDbModel, bool> >(whereExpressionBody, whereExpressionParam)); var dbMetaDataQuery = SelectDbMetaData(db, session, dbModelQuery); var currentModelData = (await dbModelQuery .Join(dbMetaDataQuery, dbModel => dbModel.Id, dbModelMetaData => dbModelMetaData.Id, (dbModel, dbModelMetaData) => new { DbModel = dbModel, DbModelMetaData = dbModelMetaData }) .SingleOrDefaultAsync()) ?? throw new ApiErrorException(TranslateApiError(ApiNotFoundError.Create(typeof(TModel).Name, model.Id))); currentDbModel = currentModelData.DbModel; currentModel = DbModelToModel(currentModelData.DbModelMetaData); currentModel.AttachService(Service, session); } using (benchmarkSection.CreateBenchmark("Permission check")) { if (!PermissionResolverHelper.CanAccessRecord(session.PermissionResolver, AccessType.Update, currentModel)) { throw new ApiErrorException( TranslateApiError(new ApiForbiddenNoRightsError(string.Format(Resources.ServiceCrudNode_Update, typeof(TModel).Name)))); } } using (benchmarkSection.CreateBenchmark("UpdateDbModel")) { var updatedDbModel = await UpdateDbModel(db, session, currentDbModel, model, updatedProperties); // ReSharper disable once SuspiciousTypeConversion.Global if (updatedDbModel is IOptimisticLockable lockableDbModel) { db.Entry(updatedDbModel).OriginalValues[nameof(IOptimisticLockable.SysStartTime)] = lockableDbModel.SysStartTime; } } using (benchmarkSection.CreateBenchmark("SaveChangesAsync")) { try { await db.SaveChangesAsync(state); } catch (DbUpdateConcurrencyException) { throw new ApiErrorException(new ApiOptimisticLockingError <TModel>( string.Format(Resources.ServiceCrudNode_OptimisticLocking, typeof(TModel).Name))); } catch (DbUpdateException e) { throw TranslateDbUpdateExceptionCore(e); } #if !NET_CORE catch (DbEntityValidationException e) { throw TranslateDbEntityValidationException(e); } #endif } using (benchmarkSection.CreateBenchmark("InternalGetByIdAsync")) { model = (await InternalGetByIdAsync(db, session, id, false, false, benchmarkSection)) ?? throw new Exception($"Could not find element by id after update (Entity = {typeof(TModel).Name}, Id = {id})."); } return(model); } }