Beispiel #1
0
 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);
            }
        }