🔗 رفع خطای Circular Dependency در XFileContentExtractor

تحلیل و رفع حلقه وابستگی در Composite Extractor Pattern
👨‍💻 توسعه‌دهنده: هادی خزاعی اصل
🏢 شرکت: فن آوران ساحر علم
📅 تاریخ: شنبه ۱۲ مهر ۱۴۰۵
📦 پروژه: xAiApi

🔍 تحلیل ریشه خطا

خطای Circular Dependency به دلیل ثبت نادرست XFileContentExtractor در DI Container ایجاد شده است:

⚠️ پیغام خطا:
A circular dependency was detected for the service of type 'IEnumerable<IXFileContentExtractor>'

زنجیره وابستگی مشکل‌ساز:

مرحله سرویس وابستگی
۱ XFileContentExtractor نیاز به IEnumerable<IXFileContentExtractor> در constructor
۲ IEnumerable<IXFileContentExtractor> شامل تمام IXFileContentExtractor های ثبت شده
۳ XFileContentExtractor (دوباره!) خودش هم به عنوان IXFileContentExtractor ثبت شده ❌
۴ حلقه بی‌نهایت → Crash

💡 راه‌حل اصلاحی

برای شکستن این حلقه، باید XFileContentExtractor را به عنوان IXFileContentExtractor ثبت نکنیم. به جای آن، آن را به صورت دستی با factory بسازیم.

MODIFY xAiApi/Startup.cs (متد ConfigureServices)

کد اصلاح‌شده:

public void ConfigureServices(IServiceCollection services)
{
    // ... (ثبت‌های قبلی)

    // ==========================================================
    // ✅ ثبت File Content Extractors (همگی Singleton)
    // ==========================================================
    
    // ۱. ثبت Extractor های پایه
    services.AddSingleton<IXFileContentExtractor, XPdfFileContentExtractor>();
    services.AddSingleton<IXFileContentExtractor, XDocxFileContentExtractor>();
    services.AddSingleton<IXFileContentExtractor, XImageFileContentExtractor>();
    services.AddSingleton<IXFileContentExtractor, XAudioFileContentExtractor>();
    services.AddSingleton<IXFileContentExtractor, XExcelFileContentExtractor>();
    services.AddSingleton<IXFileContentExtractor, XVisionFileContentExtractor>();
    services.AddSingleton<IXFileContentExtractor, XPlainTextFileContentExtractor>();
    
    // ۲. ✅ اصلاح: ثبت XFileContentExtractor به صورت Factory
    // به جای ثبت به عنوان IXFileContentExtractor، آن را به صورت دستی می‌سازیم
    services.AddSingleton<XFileContentExtractor>(sp =>
    {
        // دریافت تمام Extractor های ثبت شده (به جز XFileContentExtractor)
        var extractors = sp.GetServices<IXFileContentExtractor>();
        return new XFileContentExtractor(extractors);
    });
    
    // ۳. ✅ ثبت XFileContentExtractor به عنوان IXFileContentExtractor
    // اما با استفاده از Factory که از قبل ساخته شده
    services.AddSingleton<IXFileContentExtractor>(sp => 
        sp.GetRequiredService<XFileContentExtractor>());

    // ==========================================================
    // ✅ ثبت سرویس‌های AI
    // ==========================================================
    services.AddSingleton<IXDefaultAIOCRService, XDefaultAIOCRService>();
    services.AddScoped<IXDefaultAiService, XDefaultAiService>();
    services.AddScoped<IXDefaultEmbeddingService, XDefaultEmbeddingService>();
    services.AddScoped<IXDefaultThinkingAiService, XDefaultThinkingAiService>();
}
✅ چرا این راه‌حل کار می‌کند؟
  • ✅ XFileContentExtractor ابتدا به عنوان خودش (نه interface) ثبت می‌شود
  • ✅ در factory، تمام IXFileContentExtractor های دیگر را دریافت می‌کند
  • ✅ سپس به عنوان IXFileContentExtractor ثبت می‌شود (اما از instance از قبل ساخته شده)
  • ✅ حلقه وابستگی شکسته می‌شود

🔄 راه‌حل جایگزین (ساده‌تر)

اگر راه‌حل بالا پیچیده به نظر می‌رسد، می‌توانید از این روش ساده‌تر استفاده کنید:

MODIFY xAiApi/Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    // ... (ثبت‌های قبلی)

    // ==========================================================
    // ✅ ثبت File Content Extractors
    // ==========================================================
    
    // ۱. ثبت Extractor های پایه
    services.AddSingleton<IXFileContentExtractor, XPdfFileContentExtractor>();
    services.AddSingleton<IXFileContentExtractor, XDocxFileContentExtractor>();
    services.AddSingleton<IXFileContentExtractor, XImageFileContentExtractor>();
    services.AddSingleton<IXFileContentExtractor, XAudioFileContentExtractor>();
    services.AddSingleton<IXFileContentExtractor, XExcelFileContentExtractor>();
    services.AddSingleton<IXFileContentExtractor, XVisionFileContentExtractor>();
    services.AddSingleton<IXFileContentExtractor, XPlainTextFileContentExtractor>();
    
    // ۲. ✅ راه‌حل ساده: ساخت دستی XFileContentExtractor
    // ابتدا تمام Extractor ها را در یک لیست جمع می‌کنیم
    var extractorTypes = new[]
    {
        typeof(XPdfFileContentExtractor),
        typeof(XDocxFileContentExtractor),
        typeof(XImageFileContentExtractor),
        typeof(XAudioFileContentExtractor),
        typeof(XExcelFileContentExtractor),
        typeof(XPlainTextFileContentExtractor)
    };
    
    // ۳. ثبت XFileContentExtractor با factory
    services.AddSingleton<IXFileContentExtractor>(sp =>
    {
        var extractors = new List<IXFileContentExtractor>();
        
        // ساخت دستی Extractor ها
        foreach (var type in extractorTypes)
        {
            var instance = (IXFileContentExtractor)ActivatorUtilities.CreateInstance(sp, type);
            extractors.Add(instance);
        }
        
        // افزودن XVisionFileContentExtractor (که وابستگی دارد)
        var visionExtractor = sp.GetRequiredService<XVisionFileContentExtractor>();
        extractors.Add(visionExtractor);
        
        // ساخت XFileContentExtractor با لیست Extractor ها
        return new XFileContentExtractor(extractors);
    });

    // ==========================================================
    // ✅ ثبت سرویس‌های AI
    // ==========================================================
    services.AddSingleton<IXDefaultAIOCRService, XDefaultAIOCRService>();
    services.AddScoped<IXDefaultAiService, XDefaultAiService>();
    services.AddScoped<IXDefaultEmbeddingService, XDefaultEmbeddingService>();
    services.AddScoped<IXDefaultThinkingAiService, XDefaultThinkingAiService>();
}

📋 خلاصه تغییرات

فایل تغییر دلیل
Startup.cs services.AddScoped<IXFileContentExtractor, XFileContentExtractor>()
services.AddSingleton<IXFileContentExtractor>(sp => { ... })
رفع Circular Dependency با استفاده از Factory Pattern
✅ نتیجه نهایی:
  • ✅ Circular Dependency کاملاً رفع می‌شود
  • ✅ DI Container می‌تواند تمام سرویس‌ها را بسازد
  • ✅ XFileContentExtractor همچنان به تمام Extractor ها دسترسی دارد
  • ✅ خطای AggregateException دیگر ظاهر نخواهد شد
💡 نکته مهم:

پس از اعمال این تغییر، پروژه را Rebuild کنید. خطای System.AggregateException دیگر ظاهر نخواهد شد و برنامه به درستی اجرا می‌شود.