Token导航 LogoToken导航

Agent Skill · 开发

bulk-operations-patterns(批量操作模式)

bulk-operations-patterns 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要围绕仓库状态、代码变更或协作事项进行整理时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

bulk-operations-patterns 是开发类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为只读;许可证信息还不明确,第三方扫描有未通过项,按安装量在开发类的 21,746 个 Skill 里排第 18,498。

总安装 333周安装 14GitHub Stars 21下载 116核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
unknown

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:bulk-operations-patterns(批量操作模式)
来源仓库:https://github.com/thapaliyabikendra/ai-artifacts
仓库路径:skills/bulk-operations-patterns
安装命令:
npx skills add https://github.com/thapaliyabikendra/ai-artifacts --skill bulk-operations-patterns
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/thapaliyabikendra/ai-artifacts --skill bulk-operations-patterns

这个 Skill 是做什么的

开发类 Skill 面向写代码、改代码和排查问题,一般会读写仓库文件,部分还会执行构建或测试命令。

bulk-operations-patterns 提供 ABP Framework 下的批量数据处理最佳实践。涵盖 Excel 导入导出、大批量数据库插入/更新及文件上传等常见操作。使用 InsertManyAsync 和 UpdateManyAsync 提升性能,支持进度跟踪。

适用于需要高效处理海量数据的 Web 应用场景,减少循环操作开销。适用宿主包括 Codex、Claude、Cursor、Gemini CLI,接入前应确认版本、权限和运行环境要求。

来源仓库内路径:skills/bulk-operations-patterns

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/thapaliyabikendra/ai-artifacts --skill bulk-op…;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它标注为只读,先用只读任务试一次,确认行为符合预期再扩大使用范围。

常见问题

6 条

bulk-operations-patterns 怎么安装?

在终端执行 npx skills add https://github.com/thapaliyabikendra/ai-artifacts --skill bulk-operations-p…。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

bulk-operations-patterns 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

bulk-operations-patterns 需要什么权限?

权限级别是只读。该 Skill 主要提供规则、说明或参考内容,本身偏只读;真正读写文件、联网或执行命令仍取决于宿主 Agent 的任务。

bulk-operations-patterns 是免费的吗?

公开资料没有标注明确许可证,收费和商用限制都无法确认。正式使用前请到来源仓库核对授权说明。

bulk-operations-patterns 用的人多吗?

公开数据是累计安装 333 次,近一周安装 14 次,下载 116 次,GitHub 21 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

bulk-operations-patterns 安全吗?

第三方扫描有未通过项,风险较高。除非你确认过源码,否则不建议直接安装到常用环境。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 标注为只读权限,试用阶段不容易误改本地文件。

先想清楚的地方

  • 许可证没有明确标注,能不能商用、能不能再分发都不确定。
  • 第三方扫描存在提醒项或未通过项,需要人工逐条确认。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理开发类任务、又不想每次都手写提示词的人。
  • 只想先试试效果、不希望它改动本地文件的人。

不太适合

  • 需要明确授权才能用到公司项目里的场景。
  • 需要厂商级技术支持和 SLA 的生产系统。

开发类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
skill-vetter

技能审查员 · OpenClaw

8,779,018986操作浏览器MIT-0通过
ontology

本体论 · OpenClaw

6,636,960576只读MIT-0通过
entra-app-registration

内部应用程序注册 · Codex、Claude

6,631,872764接触敏感数据未明确通过
entra-app-registration

内部应用程序注册 · Codex、Claude

3,757,680201接触敏感数据未明确通过
nano-banana-pro

纳米香蕉专业版 · OpenClaw

3,526,898364接触敏感数据MIT-0有提醒
bulk-operations-patterns

批量操作模式 · 本页

33321只读未明确未通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

Bulk Operations Patterns

Master bulk data operations including Excel import/export, batch processing, and efficient database operations in ABP Framework applications.

When to Use This Skill

  • Implementing bulk import from Excel/CSV files
  • Processing large datasets with validation
  • Bulk database inserts/updates with InsertManyAsync/UpdateManyAsync
  • File upload handling with blob storage
  • Progress tracking for long-running operations
  • Export data to Excel/CSV formats

Excel Import Pattern

1. DTO for Excel Import

// Application.Contracts/{EntityPlural}/BulkImport{Entity}Dto.cs
public class BulkImportProductDto
{
    [ExcelColumnName("Product Code")]
    public string ProductCode { get; set; }

    [ExcelColumnName("Product Name")]
    public string Name { get; set; }

    [ExcelColumnName("Price")]
    public string PriceText { get; set; }  // String for validation

    [ExcelColumnName("Stock")]
    public string StockText { get; set; }

    [ExcelColumnName("Category")]
    public string CategoryName { get; set; }

    // Parsed values (not from Excel)
    [ExcelIgnore]
    public decimal Price { get; set; }

    [ExcelIgnore]
    public int Stock { get; set; }

    [ExcelIgnore]
    public Guid? CategoryId { get; set; }
}

2. File Upload DTO

// Application.Contracts/{EntityPlural}/BulkImportFileDto.cs
public class BulkImportFileDto
{
    [Required]
    public IFormFile File { get; set; }

    public bool ValidateOnly { get; set; } = false;
}

// Allowed extensions
public static class BulkImportFileExtensions
{
    public static readonly string[] Allowed = { ".xlsx", ".xls", ".csv" };
    public const long MaxFileSize = 10 * 1024 * 1024; // 10MB
}

3. AppService Implementation

public class ProductAppService : ApplicationService, IProductAppService
{
    private readonly IRepository<Product, Guid> _productRepository;
    private readonly IRepository<Category, Guid> _categoryRepository;
    private readonly IBlobContainer<BulkImportFileContainer> _fileContainer;
    private readonly ILogger<ProductAppService> _logger;

    [Authorize(ProductPermissions.Products.Import)]
    public async Task<BulkImportResultDto> BulkImportAsync(BulkImportFileDto input)
    {
        _logger.LogInformation(
            "[{Service}] BulkImportAsync - Started - FileName: {FileName}",
            nameof(ProductAppService), input.File.FileName);

        // Step 1: Validate file
        ValidateFile(input.File);

        // Step 2: Parse Excel
        var items = await ParseExcelAsync(input.File);

        if (!items.Any())
        {
            throw new UserFriendlyException("No data found in file");
        }

        // Step 3: Load reference data
        var categories = await _categoryRepository.GetListAsync();

        // Step 4: Validate all rows
        var validationErrors = await ValidateImportDataAsync(items, categories);

        if (validationErrors.Any())
        {
            return new BulkImportResultDto
            {
                IsSuccess = false,
                TotalRows = items.Count,
                ErrorCount = validationErrors.Count,
                Errors = validationErrors
            };
        }

        if (input.ValidateOnly)
        {
            return new BulkImportResultDto
            {
                IsSuccess = true,
                TotalRows = items.Count,
                Message = "Validation passed. Ready to import."
            };
        }

        // Step 5: Process import
        var result = await ProcessImportAsync(items, categories);

        _logger.LogInformation(
            "[{Service}] BulkImportAsync - Completed - Imported: {Count}",
            nameof(ProductAppService), result.SuccessCount);

        return result;
    }

    private void ValidateFile(IFormFile file)
    {
        var extension = Path.GetExtension(file.FileName).ToLowerInvariant();

        if (!BulkImportFileExtensions.Allowed.Contains(extension))
        {
            throw new UserFriendlyException(
                $"Invalid file type. Allowed: {string.Join(", ", BulkImportFileExtensions.Allowed)}");
        }

        if (file.Length > BulkImportFileExtensions.MaxFileSize)
        {
            throw new UserFriendlyException(
                $"File size exceeds maximum allowed ({BulkImportFileExtensions.MaxFileSize / 1024 / 1024}MB)");
        }
    }

    private async Task<List<BulkImportProductDto>> ParseExcelAsync(IFormFile file)
    {
        using var stream = new MemoryStream();
        await file.CopyToAsync(stream);
        stream.Position = 0;

        // Register encoding provider for older Excel formats
        System.Text.Encoding.RegisterProvider(
            System.Text.CodePagesEncodingProvider.Instance);

        using var importer = new ExcelImporter(stream);
        var sheet = importer.ReadSheet();

        return sheet.ReadRows<BulkImportProductDto>()
            .Where(x => !string.IsNullOrWhiteSpace(x.ProductCode))
            .ToList();
    }

    private async Task<List<BulkImportErrorDto>> ValidateImportDataAsync(
        List<BulkImportProductDto> items,
        List<Category> categories)
    {
        var errors = new List<BulkImportErrorDto>();
        var existingCodes = await GetExistingProductCodesAsync();

        for (int i = 0; i < items.Count; i++)
        {
            var rowNumber = i + 2; // Excel row (1-based + header)
            var item = items[i];

            // Trim inputs
            item.ProductCode = item.ProductCode?.Trim()?.ToUpperInvariant();
            item.Name = item.Name?.Trim();
            item.CategoryName = item.CategoryName?.Trim();

            // Validate required fields
            if (string.IsNullOrWhiteSpace(item.ProductCode))
            {
                errors.Add(new BulkImportErrorDto(rowNumber, "ProductCode", "Product Code is required"));
            }

            if (string.IsNullOrWhiteSpace(item.Name))
            {
                errors.Add(new BulkImportErrorDto(rowNumber, "Name", "Product Name is required"));
            }

            // Validate numeric fields
            if (!decimal.TryParse(item.PriceText, out var price) || price < 0)
            {
                errors.Add(new BulkImportErrorDto(rowNumber, "Price", $"Invalid price: {item.PriceText}"));
            }
            else
            {
                item.Price = Math.Round(price, 2);
            }

            if (!int.TryParse(item.StockText, out var stock) || stock < 0)
            {
                errors.Add(new BulkImportErrorDto(rowNumber, "Stock", $"Invalid stock: {item.StockText}"));
            }
            else
            {
                item.Stock = stock;
            }

            // Validate duplicates within file
            var duplicates = items
                .Select((x, idx) => (Item: x, Index: idx))
                .Where(x => x.Index != i &&
                    x.Item.ProductCode?.ToUpperInvariant() == item.ProductCode)
                .Select(x => $"Row {x.Index + 2}");

            if (duplicates.Any())
            {
                errors.Add(new BulkImportErrorDto(
                    rowNumber,
                    "ProductCode",
                    $"Duplicate in file: {string.Join(", ", duplicates)}"));
            }

            // Validate against existing data
            if (existingCodes.Contains(item.ProductCode))
            {
                errors.Add(new BulkImportErrorDto(
                    rowNumber,
                    "ProductCode",
                    $"Product code already exists: {item.ProductCode}"));
            }

            // Validate category
            if (!string.IsNullOrWhiteSpace(item.CategoryName))
            {
                var category = categories.FirstOrDefault(
                    c => c.Name.Equals(item.CategoryName, StringComparison.OrdinalIgnoreCase));

                if (category == null)
                {
                    errors.Add(new BulkImportErrorDto(
                        rowNumber,
                        "Category",
                        $"Category not found: {item.CategoryName}"));
                }
                else
                {
                    item.CategoryId = category.Id;
                }
            }
        }

        return errors;
    }

    private async Task<HashSet<string>> GetExistingProductCodesAsync()
    {
        var codes = await _productRepository
            .GetQueryableAsync()
            .ContinueWith(q => q.Result
                .Select(p => p.ProductCode.ToUpperInvariant())
                .ToHashSet());

        return codes;
    }

    private async Task<BulkImportResultDto> ProcessImportAsync(
        List<BulkImportProductDto> items,
        List<Category> categories)
    {
        var products = items.Select(item => new Product(
            GuidGenerator.Create(),
            item.ProductCode,
            item.Name,
            item.Price,
            item.Stock)
        {
            CategoryId = item.CategoryId
        }).ToList();

        await _productRepository.InsertManyAsync(products);

        return new BulkImportResultDto
        {
            IsSuccess = true,
            TotalRows = items.Count,
            SuccessCount = products.Count,
            Message = $"Successfully imported {products.Count} products"
        };
    }
}

4. Result DTOs

public class BulkImportResultDto
{
    public bool IsSuccess { get; set; }
    public int TotalRows { get; set; }
    public int SuccessCount { get; set; }
    public int ErrorCount { get; set; }
    public string Message { get; set; }
    public List<BulkImportErrorDto> Errors { get; set; } = new();
}

public class BulkImportErrorDto
{
    public int RowNumber { get; set; }
    public string Field { get; set; }
    public string Message { get; set; }

    public BulkImportErrorDto() { }

    public BulkImportErrorDto(int rowNumber, string field, string message)
    {
        RowNumber = rowNumber;
        Field = field;
        Message = message;
    }
}

Bulk Update Pattern

1. Bulk Update from Excel

public async Task<BulkImportResultDto> BulkUpdateAsync(BulkImportFileDto input)
{
    _logger.LogInformation("[{Service}] BulkUpdateAsync - Started", nameof(ProductAppService));

    ValidateFile(input.File);
    var items = await ParseExcelAsync(input.File);

    // Load existing products
    var productCodes = items
        .Select(x => x.ProductCode?.Trim()?.ToUpperInvariant())
        .Where(x => !string.IsNullOrEmpty(x))
        .ToList();

    var existingProducts = await _productRepository
        .GetListAsync(p => productCodes.Contains(p.ProductCode.ToUpper()));

    var errors = new List<BulkImportErrorDto>();
    var productsToUpdate = new List<Product>();

    foreach (var (item, index) in items.Select((x, i) => (x, i)))
    {
        var rowNumber = index + 2;
        item.ProductCode = item.ProductCode?.Trim()?.ToUpperInvariant();

        var product = existingProducts.FirstOrDefault(
            p => p.ProductCode.ToUpperInvariant() == item.ProductCode);

        if (product == null)
        {
            errors.Add(new BulkImportErrorDto(
                rowNumber, "ProductCode", $"Product not found: {item.ProductCode}"));
            continue;
        }

        // Update fields if provided
        if (!string.IsNullOrWhiteSpace(item.Name))
        {
            product.SetName(item.Name.Trim());
        }

        if (decimal.TryParse(item.PriceText, out var price))
        {
            product.SetPrice(Math.Round(price, 2));
        }

        if (int.TryParse(item.StockText, out var stock))
        {
            product.SetStock(stock);
        }

        productsToUpdate.Add(product);
    }

    if (errors.Any())
    {
        return new BulkImportResultDto
        {
            IsSuccess = false,
            TotalRows = items.Count,
            ErrorCount = errors.Count,
            Errors = errors
        };
    }

    if (productsToUpdate.Any())
    {
        await _productRepository.UpdateManyAsync(productsToUpdate);
    }

    _logger.LogInformation(
        "[{Service}] BulkUpdateAsync - Completed - Updated: {Count}",
        nameof(ProductAppService), productsToUpdate.Count);

    return new BulkImportResultDto
    {
        IsSuccess = true,
        TotalRows = items.Count,
        SuccessCount = productsToUpdate.Count,
        Message = $"Successfully updated {productsToUpdate.Count} products"
    };
}

2. Bulk Update from List

public async Task<BulkImportResultDto> BulkUpdateFromListAsync(
    List<UpdateProductDto> items)
{
    var ids = items.Select(x => x.Id).ToList();

    var products = await _productRepository
        .GetListAsync(p => ids.Contains(p.Id));

    var productsToUpdate = (
        from product in products
        join item in items on product.Id equals item.Id
        select UpdateProduct(product, item)
    ).ToList();

    await _productRepository.UpdateManyAsync(productsToUpdate);

    return new BulkImportResultDto
    {
        IsSuccess = true,
        SuccessCount = productsToUpdate.Count
    };
}

private Product UpdateProduct(Product product, UpdateProductDto input)
{
    product.SetName(input.Name?.Trim() ?? product.Name);
    product.SetPrice(input.Price ?? product.Price);
    product.SetStock(input.Stock ?? product.Stock);
    return product;
}

Excel Export Pattern

public async Task<FileDto> ExportToExcelAsync(ProductFilter filter)
{
    _logger.LogInformation("[{Service}] ExportToExcelAsync - Started", nameof(ProductAppService));

    var products = await GetFilteredProductsAsync(filter);

    var exportData = products.Select(p => new ProductExportDto
    {
        ProductCode = p.ProductCode,
        Name = p.Name,
        Price = p.Price,
        Stock = p.Stock,
        CategoryName = p.Category?.Name,
        CreatedAt = p.CreationTime
    }).ToList();

    using var stream = new MemoryStream();

    // Using ExcelMapper for export
    var excel = new ExcelMapper();
    excel.Save(stream, exportData, "Products");

    var fileName = $"Products_{DateTime.Now:yyyyMMdd_HHmmss}.xlsx";

    // Save to blob storage
    await _fileContainer.SaveAsync(fileName, stream.ToArray());

    return new FileDto
    {
        FileName = fileName,
        Content = stream.ToArray(),
        ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    };
}

Blob Storage for Templates

// File container definition
[BlobContainerName("bulk-import-templates")]
public class BulkImportTemplateContainer { }

// Get template
public async Task<FileDto> GetImportTemplateAsync()
{
    const string templateFileName = "ProductImportTemplate.xlsx";

    var content = await _fileContainer.GetAllBytesAsync(templateFileName);

    if (content == null)
    {
        throw new UserFriendlyException("Import template not found");
    }

    return new FileDto
    {
        FileName = templateFileName,
        Content = content,
        ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    };
}

Batch Processing with Progress

public async Task<BulkImportResultDto> ProcessLargeBatchAsync(
    List<CreateProductDto> items,
    IProgress<int> progress = null)
{
    const int batchSize = 100;
    var batches = items.Chunk(batchSize).ToList();
    var totalProcessed = 0;
    var errors = new List<BulkImportErrorDto>();

    foreach (var (batch, batchIndex) in batches.Select((b, i) => (b, i)))
    {
        try
        {
            var products = batch.Select(item => new Product(
                GuidGenerator.Create(),
                item.ProductCode,
                item.Name,
                item.Price,
                item.Stock
            )).ToList();

            await _productRepository.InsertManyAsync(products);

            totalProcessed += batch.Length;
            progress?.Report((int)((double)totalProcessed / items.Count * 100));

            _logger.LogInformation(
                "Processed batch {BatchIndex}/{TotalBatches}, Total: {Total}",
                batchIndex + 1, batches.Count, totalProcessed);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to process batch {BatchIndex}", batchIndex);
            errors.Add(new BulkImportErrorDto(
                batchIndex * batchSize,
                "Batch",
                $"Batch {batchIndex + 1} failed: {ex.Message}"));
        }
    }

    return new BulkImportResultDto
    {
        IsSuccess = !errors.Any(),
        TotalRows = items.Count,
        SuccessCount = totalProcessed,
        ErrorCount = errors.Count,
        Errors = errors
    };
}

Controller Implementation

[Route("api/products")]
public class ProductController : AbpController
{
    private readonly IProductAppService _productAppService;

    [HttpPost("bulk-import")]
    [Consumes("multipart/form-data")]
    public async Task<BulkImportResultDto> BulkImportAsync(
        [FromForm] BulkImportFileDto input)
    {
        return await _productAppService.BulkImportAsync(input);
    }

    [HttpPost("bulk-update")]
    [Consumes("multipart/form-data")]
    public async Task<BulkImportResultDto> BulkUpdateAsync(
        [FromForm] BulkImportFileDto input)
    {
        return await _productAppService.BulkUpdateAsync(input);
    }

    [HttpGet("export")]
    public async Task<IActionResult> ExportAsync([FromQuery] ProductFilter filter)
    {
        var file = await _productAppService.ExportToExcelAsync(filter);
        return File(file.Content, file.ContentType, file.FileName);
    }

    [HttpGet("import-template")]
    public async Task<IActionResult> GetTemplateAsync()
    {
        var file = await _productAppService.GetImportTemplateAsync();
        return File(file.Content, file.ContentType, file.FileName);
    }
}

Best Practices

  1. Always validate first - Parse and validate all rows before any database operations
  2. Use transactions - ABP's UoW handles this automatically for InsertManyAsync/UpdateManyAsync
  3. Batch large operations - Process in chunks of 100-500 records
  4. Report progress - Use IProgress<T> for long-running operations
  5. Log comprehensively - Log start, end, batch progress, and errors
  6. Provide templates - Store import templates in blob storage
  7. Validate duplicates - Check both within file and against existing data
  8. Trim inputs - Always trim and normalize string inputs
  9. Handle encoding - Register CodePagesEncodingProvider for older Excel formats
  10. Return detailed errors - Include row number and field for each validation error

References

平台分布

Codex

33.83%
按下载量换算39

Claude

31.38%
按下载量换算36

Cursor

16.46%
按下载量换算19

Gemini CLI

9.28%
按下载量换算11

安全审计

Gen Agent Trust Hub

未通过

Socket

通过

Snyk

通过

权限和风险

只读

该 Skill 主要提供规则、说明或参考内容,本身偏只读;真正读写文件、联网或执行命令仍取决于宿主 Agent 的任务。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在提醒或未通过项,安装前请自行复核。目前只有一个公开来源,建议同时核对源仓库。

继续浏览同类 Skills