using Microsoft.EntityFrameworkCore; using DramaLing.Api.Models.Entities; using System.Text.Json; namespace DramaLing.Api.Data; public class DramaLingDbContext : DbContext { public DramaLingDbContext(DbContextOptions options) : base(options) { } // DbSets public DbSet Users { get; set; } public DbSet UserSettings { get; set; } public DbSet Flashcards { get; set; } public DbSet Tags { get; set; } public DbSet FlashcardTags { get; set; } // StudyRecord removed - study system cleaned public DbSet ErrorReports { get; set; } public DbSet DailyStats { get; set; } public DbSet SentenceAnalysisCache { get; set; } public DbSet WordQueryUsageStats { get; set; } public DbSet AudioCaches { get; set; } public DbSet PronunciationAssessments { get; set; } public DbSet UserAudioPreferences { get; set; } public DbSet ExampleImages { get; set; } public DbSet FlashcardExampleImages { get; set; } public DbSet ImageGenerationRequests { get; set; } public DbSet OptionsVocabularies { get; set; } public DbSet FlashcardReviews { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // 設定表名稱 (與 Supabase 一致) modelBuilder.Entity().ToTable("user_profiles"); modelBuilder.Entity().ToTable("user_settings"); modelBuilder.Entity().ToTable("flashcards"); modelBuilder.Entity().ToTable("tags"); modelBuilder.Entity().ToTable("flashcard_tags"); // StudyRecord table removed modelBuilder.Entity().ToTable("error_reports"); modelBuilder.Entity().ToTable("daily_stats"); modelBuilder.Entity().ToTable("audio_cache"); modelBuilder.Entity().ToTable("pronunciation_assessments"); modelBuilder.Entity().ToTable("user_audio_preferences"); modelBuilder.Entity().ToTable("example_images"); modelBuilder.Entity().ToTable("flashcard_example_images"); modelBuilder.Entity().ToTable("image_generation_requests"); modelBuilder.Entity().ToTable("options_vocabularies"); modelBuilder.Entity().ToTable("sentence_analysis_cache"); modelBuilder.Entity().ToTable("word_query_usage_stats"); // 配置屬性名稱 (snake_case) ConfigureUserEntity(modelBuilder); ConfigureUserSettingsEntity(modelBuilder); ConfigureFlashcardEntity(modelBuilder); // ConfigureStudyEntities 已移除 - StudyRecord 實體已清理 ConfigureTagEntities(modelBuilder); ConfigureErrorReportEntity(modelBuilder); ConfigureDailyStatsEntity(modelBuilder); ConfigureSentenceAnalysisCacheEntity(modelBuilder); ConfigureWordQueryUsageStatsEntity(modelBuilder); ConfigureAudioEntities(modelBuilder); ConfigureImageGenerationEntities(modelBuilder); ConfigureOptionsVocabularyEntity(modelBuilder); // 複合主鍵 modelBuilder.Entity() .HasKey(ft => new { ft.FlashcardId, ft.TagId }); modelBuilder.Entity() .HasKey(fei => new { fei.FlashcardId, fei.ExampleImageId }); modelBuilder.Entity() .HasIndex(ds => new { ds.UserId, ds.Date }) .IsUnique(); // 外鍵關係 ConfigureRelationships(modelBuilder); } private void ConfigureUserEntity(ModelBuilder modelBuilder) { var userEntity = modelBuilder.Entity(); userEntity.Property(u => u.Id).HasColumnName("id"); userEntity.Property(u => u.Username).HasColumnName("username"); userEntity.Property(u => u.Email).HasColumnName("email"); userEntity.Property(u => u.PasswordHash).HasColumnName("password_hash"); userEntity.Property(u => u.DisplayName).HasColumnName("display_name"); userEntity.Property(u => u.AvatarUrl).HasColumnName("avatar_url"); userEntity.Property(u => u.SubscriptionType).HasColumnName("subscription_type"); userEntity.Property(u => u.Preferences) .HasColumnName("preferences") .HasConversion( v => System.Text.Json.JsonSerializer.Serialize(v, (System.Text.Json.JsonSerializerOptions)null), v => System.Text.Json.JsonSerializer.Deserialize>(v, (System.Text.Json.JsonSerializerOptions)null) ?? new Dictionary()); // 新增個人化欄位映射 userEntity.Property(u => u.EnglishLevel).HasColumnName("english_level"); userEntity.Property(u => u.EnglishLevelNumeric) .HasColumnName("english_level_numeric") .HasDefaultValue(2); // 預設 A2 userEntity.Property(u => u.LevelUpdatedAt).HasColumnName("level_updated_at"); userEntity.Property(u => u.IsLevelVerified).HasColumnName("is_level_verified"); userEntity.Property(u => u.LevelNotes).HasColumnName("level_notes"); userEntity.Property(u => u.CreatedAt).HasColumnName("created_at"); userEntity.Property(u => u.UpdatedAt).HasColumnName("updated_at"); // Add unique indexes userEntity.HasIndex(u => u.Email).IsUnique(); userEntity.HasIndex(u => u.Username).IsUnique(); } private void ConfigureFlashcardEntity(ModelBuilder modelBuilder) { var flashcardEntity = modelBuilder.Entity(); flashcardEntity.Property(f => f.Id).HasColumnName("id"); flashcardEntity.Property(f => f.UserId).HasColumnName("user_id"); flashcardEntity.Property(f => f.Word).HasColumnName("word"); flashcardEntity.Property(f => f.Translation).HasColumnName("translation"); flashcardEntity.Property(f => f.Definition).HasColumnName("definition"); flashcardEntity.Property(f => f.PartOfSpeech).HasColumnName("part_of_speech"); flashcardEntity.Property(f => f.Pronunciation).HasColumnName("pronunciation"); flashcardEntity.Property(f => f.Example).HasColumnName("example"); flashcardEntity.Property(f => f.ExampleTranslation).HasColumnName("example_translation"); // 已刪除的復習相關屬性配置 // EasinessFactor, IntervalDays, NextReviewDate, MasteryLevel, // TimesReviewed, TimesCorrect, LastReviewedAt 已移除 flashcardEntity.Property(f => f.IsFavorite).HasColumnName("is_favorite"); flashcardEntity.Property(f => f.IsArchived).HasColumnName("is_archived"); // 難度等級映射 - 使用數字格式 flashcardEntity.Property(f => f.DifficultyLevelNumeric).HasColumnName("difficulty_level_numeric"); flashcardEntity.Property(f => f.CreatedAt).HasColumnName("created_at"); flashcardEntity.Property(f => f.UpdatedAt).HasColumnName("updated_at"); } // ConfigureStudyEntities 方法已移除 - StudyRecord 實體已清理 private void ConfigureTagEntities(ModelBuilder modelBuilder) { var tagEntity = modelBuilder.Entity(); tagEntity.Property(t => t.Id).HasColumnName("id"); tagEntity.Property(t => t.UserId).HasColumnName("user_id"); tagEntity.Property(t => t.Name).HasColumnName("name"); tagEntity.Property(t => t.Color).HasColumnName("color"); tagEntity.Property(t => t.UsageCount).HasColumnName("usage_count"); tagEntity.Property(t => t.CreatedAt).HasColumnName("created_at"); var flashcardTagEntity = modelBuilder.Entity(); flashcardTagEntity.Property(ft => ft.FlashcardId).HasColumnName("flashcard_id"); flashcardTagEntity.Property(ft => ft.TagId).HasColumnName("tag_id"); } private void ConfigureErrorReportEntity(ModelBuilder modelBuilder) { var errorEntity = modelBuilder.Entity(); errorEntity.Property(e => e.Id).HasColumnName("id"); errorEntity.Property(e => e.UserId).HasColumnName("user_id"); errorEntity.Property(e => e.FlashcardId).HasColumnName("flashcard_id"); errorEntity.Property(e => e.ReportType).HasColumnName("report_type"); errorEntity.Property(e => e.Description).HasColumnName("description"); errorEntity.Property(e => e.StudyMode).HasColumnName("study_mode"); errorEntity.Property(e => e.Status).HasColumnName("status"); errorEntity.Property(e => e.AdminNotes).HasColumnName("admin_notes"); errorEntity.Property(e => e.ResolvedAt).HasColumnName("resolved_at"); errorEntity.Property(e => e.ResolvedBy).HasColumnName("resolved_by"); errorEntity.Property(e => e.CreatedAt).HasColumnName("created_at"); } private void ConfigureDailyStatsEntity(ModelBuilder modelBuilder) { var statsEntity = modelBuilder.Entity(); statsEntity.Property(d => d.Id).HasColumnName("id"); statsEntity.Property(d => d.UserId).HasColumnName("user_id"); statsEntity.Property(d => d.Date).HasColumnName("date"); statsEntity.Property(d => d.WordsStudied).HasColumnName("words_studied"); statsEntity.Property(d => d.WordsCorrect).HasColumnName("words_correct"); statsEntity.Property(d => d.StudyTimeSeconds).HasColumnName("study_time_seconds"); statsEntity.Property(d => d.SessionCount).HasColumnName("session_count"); statsEntity.Property(d => d.CardsGenerated).HasColumnName("cards_generated"); statsEntity.Property(d => d.AiApiCalls).HasColumnName("ai_api_calls"); statsEntity.Property(d => d.CreatedAt).HasColumnName("created_at"); } private void ConfigureUserSettingsEntity(ModelBuilder modelBuilder) { var settingsEntity = modelBuilder.Entity(); settingsEntity.Property(us => us.Id).HasColumnName("id"); settingsEntity.Property(us => us.UserId).HasColumnName("user_id"); settingsEntity.Property(us => us.DailyGoal).HasColumnName("daily_goal"); settingsEntity.Property(us => us.ReminderTime).HasColumnName("reminder_time"); settingsEntity.Property(us => us.ReminderEnabled).HasColumnName("reminder_enabled"); settingsEntity.Property(us => us.DifficultyPreference).HasColumnName("difficulty_preference"); settingsEntity.Property(us => us.AutoPlayAudio).HasColumnName("auto_play_audio"); settingsEntity.Property(us => us.ShowPronunciation).HasColumnName("show_pronunciation"); settingsEntity.Property(us => us.CreatedAt).HasColumnName("created_at"); settingsEntity.Property(us => us.UpdatedAt).HasColumnName("updated_at"); } private void ConfigureSentenceAnalysisCacheEntity(ModelBuilder modelBuilder) { var cacheEntity = modelBuilder.Entity(); cacheEntity.Property(sac => sac.Id).HasColumnName("id"); cacheEntity.Property(sac => sac.InputTextHash).HasColumnName("input_text_hash"); cacheEntity.Property(sac => sac.InputText).HasColumnName("input_text"); cacheEntity.Property(sac => sac.CorrectedText).HasColumnName("corrected_text"); cacheEntity.Property(sac => sac.HasGrammarErrors).HasColumnName("has_grammar_errors"); cacheEntity.Property(sac => sac.GrammarCorrections).HasColumnName("grammar_corrections"); cacheEntity.Property(sac => sac.AnalysisResult).HasColumnName("analysis_result"); cacheEntity.Property(sac => sac.HighValueWords).HasColumnName("high_value_words"); cacheEntity.Property(sac => sac.IdiomsDetected).HasColumnName("idioms_detected"); cacheEntity.Property(sac => sac.CreatedAt).HasColumnName("created_at"); cacheEntity.Property(sac => sac.ExpiresAt).HasColumnName("expires_at"); cacheEntity.Property(sac => sac.AccessCount).HasColumnName("access_count"); cacheEntity.Property(sac => sac.LastAccessedAt).HasColumnName("last_accessed_at"); } private void ConfigureWordQueryUsageStatsEntity(ModelBuilder modelBuilder) { var statsEntity = modelBuilder.Entity(); statsEntity.Property(wq => wq.Id).HasColumnName("id"); statsEntity.Property(wq => wq.UserId).HasColumnName("user_id"); statsEntity.Property(wq => wq.Date).HasColumnName("date"); statsEntity.Property(wq => wq.SentenceAnalysisCount).HasColumnName("sentence_analysis_count"); statsEntity.Property(wq => wq.HighValueWordClicks).HasColumnName("high_value_word_clicks"); statsEntity.Property(wq => wq.LowValueWordClicks).HasColumnName("low_value_word_clicks"); statsEntity.Property(wq => wq.TotalApiCalls).HasColumnName("total_api_calls"); statsEntity.Property(wq => wq.UniqueWordsQueried).HasColumnName("unique_words_queried"); statsEntity.Property(wq => wq.CreatedAt).HasColumnName("created_at"); statsEntity.Property(wq => wq.UpdatedAt).HasColumnName("updated_at"); } private void ConfigureRelationships(ModelBuilder modelBuilder) { // User relationships modelBuilder.Entity() .HasOne(f => f.User) .WithMany(u => u.Flashcards) .HasForeignKey(f => f.UserId) .OnDelete(DeleteBehavior.Cascade); // Study relationships 已移除 - StudyRecord 實體已清理 // FlashcardReview relationships modelBuilder.Entity() .HasOne(fr => fr.Flashcard) .WithMany() .HasForeignKey(fr => fr.FlashcardId) .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() .HasOne(fr => fr.User) .WithMany() .HasForeignKey(fr => fr.UserId) .OnDelete(DeleteBehavior.Cascade); // 複習記錄唯一性約束 (每個用戶每張卡片只能有一條記錄) modelBuilder.Entity() .HasIndex(fr => new { fr.FlashcardId, fr.UserId }) .IsUnique(); // Tag relationships modelBuilder.Entity() .HasOne(ft => ft.Flashcard) .WithMany(f => f.FlashcardTags) .HasForeignKey(ft => ft.FlashcardId) .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() .HasOne(ft => ft.Tag) .WithMany(t => t.FlashcardTags) .HasForeignKey(ft => ft.TagId) .OnDelete(DeleteBehavior.Cascade); // Error report relationships modelBuilder.Entity() .HasOne(er => er.User) .WithMany(u => u.ErrorReports) .HasForeignKey(er => er.UserId) .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() .HasOne(er => er.Flashcard) .WithMany(f => f.ErrorReports) .HasForeignKey(er => er.FlashcardId) .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() .HasOne(er => er.ResolvedByUser) .WithMany() .HasForeignKey(er => er.ResolvedBy) .OnDelete(DeleteBehavior.SetNull); // User settings relationship modelBuilder.Entity() .HasOne(us => us.User) .WithOne(u => u.Settings) .HasForeignKey(us => us.UserId) .OnDelete(DeleteBehavior.Cascade); // Daily stats relationship modelBuilder.Entity() .HasOne(ds => ds.User) .WithMany(u => u.DailyStats) .HasForeignKey(ds => ds.UserId) .OnDelete(DeleteBehavior.Cascade); // Sentence analysis cache configuration modelBuilder.Entity() .HasIndex(sac => sac.InputTextHash) .HasDatabaseName("IX_SentenceAnalysisCache_Hash"); modelBuilder.Entity() .HasIndex(sac => sac.ExpiresAt) .HasDatabaseName("IX_SentenceAnalysisCache_Expires"); modelBuilder.Entity() .HasIndex(sac => new { sac.InputTextHash, sac.ExpiresAt }) .HasDatabaseName("IX_SentenceAnalysisCache_Hash_Expires"); // Word query usage stats configuration modelBuilder.Entity() .HasOne(wq => wq.User) .WithMany() .HasForeignKey(wq => wq.UserId) .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() .HasIndex(wq => new { wq.UserId, wq.Date }) .IsUnique() .HasDatabaseName("IX_WordQueryUsageStats_UserDate"); modelBuilder.Entity() .HasIndex(wq => wq.CreatedAt) .HasDatabaseName("IX_WordQueryUsageStats_CreatedAt"); // Audio entities relationships ConfigureAudioRelationships(modelBuilder); } private void ConfigureAudioEntities(ModelBuilder modelBuilder) { // AudioCache configuration var audioCacheEntity = modelBuilder.Entity(); audioCacheEntity.Property(ac => ac.Id).HasColumnName("id"); audioCacheEntity.Property(ac => ac.TextHash).HasColumnName("text_hash"); audioCacheEntity.Property(ac => ac.TextContent).HasColumnName("text_content"); audioCacheEntity.Property(ac => ac.Accent).HasColumnName("accent"); audioCacheEntity.Property(ac => ac.VoiceId).HasColumnName("voice_id"); audioCacheEntity.Property(ac => ac.AudioUrl).HasColumnName("audio_url"); audioCacheEntity.Property(ac => ac.FileSize).HasColumnName("file_size"); audioCacheEntity.Property(ac => ac.DurationMs).HasColumnName("duration_ms"); audioCacheEntity.Property(ac => ac.CreatedAt).HasColumnName("created_at"); audioCacheEntity.Property(ac => ac.LastAccessed).HasColumnName("last_accessed"); audioCacheEntity.Property(ac => ac.AccessCount).HasColumnName("access_count"); audioCacheEntity.HasIndex(ac => ac.TextHash) .IsUnique() .HasDatabaseName("IX_AudioCache_TextHash"); audioCacheEntity.HasIndex(ac => ac.LastAccessed) .HasDatabaseName("IX_AudioCache_LastAccessed"); // PronunciationAssessment configuration var pronunciationEntity = modelBuilder.Entity(); pronunciationEntity.Property(pa => pa.Id).HasColumnName("id"); pronunciationEntity.Property(pa => pa.UserId).HasColumnName("user_id"); pronunciationEntity.Property(pa => pa.FlashcardId).HasColumnName("flashcard_id"); pronunciationEntity.Property(pa => pa.TargetText).HasColumnName("target_text"); pronunciationEntity.Property(pa => pa.AudioUrl).HasColumnName("audio_url"); pronunciationEntity.Property(pa => pa.OverallScore).HasColumnName("overall_score"); pronunciationEntity.Property(pa => pa.AccuracyScore).HasColumnName("accuracy_score"); pronunciationEntity.Property(pa => pa.FluencyScore).HasColumnName("fluency_score"); pronunciationEntity.Property(pa => pa.CompletenessScore).HasColumnName("completeness_score"); pronunciationEntity.Property(pa => pa.ProsodyScore).HasColumnName("prosody_score"); pronunciationEntity.Property(pa => pa.PhonemeScores).HasColumnName("phoneme_scores"); pronunciationEntity.Property(pa => pa.Suggestions).HasColumnName("suggestions"); // StudySessionId removed pronunciationEntity.Property(pa => pa.PracticeMode).HasColumnName("practice_mode"); pronunciationEntity.Property(pa => pa.CreatedAt).HasColumnName("created_at"); pronunciationEntity.HasIndex(pa => new { pa.UserId, pa.FlashcardId }) .HasDatabaseName("IX_PronunciationAssessment_UserFlashcard"); // StudySessionId index removed // UserAudioPreferences configuration var audioPrefsEntity = modelBuilder.Entity(); audioPrefsEntity.Property(uap => uap.UserId).HasColumnName("user_id"); audioPrefsEntity.Property(uap => uap.PreferredAccent).HasColumnName("preferred_accent"); audioPrefsEntity.Property(uap => uap.PreferredVoiceMale).HasColumnName("preferred_voice_male"); audioPrefsEntity.Property(uap => uap.PreferredVoiceFemale).HasColumnName("preferred_voice_female"); audioPrefsEntity.Property(uap => uap.DefaultSpeed).HasColumnName("default_speed"); audioPrefsEntity.Property(uap => uap.AutoPlayEnabled).HasColumnName("auto_play_enabled"); audioPrefsEntity.Property(uap => uap.PronunciationDifficulty).HasColumnName("pronunciation_difficulty"); audioPrefsEntity.Property(uap => uap.TargetScoreThreshold).HasColumnName("target_score_threshold"); audioPrefsEntity.Property(uap => uap.EnableDetailedFeedback).HasColumnName("enable_detailed_feedback"); audioPrefsEntity.Property(uap => uap.UpdatedAt).HasColumnName("updated_at"); } private void ConfigureAudioRelationships(ModelBuilder modelBuilder) { // PronunciationAssessment relationships modelBuilder.Entity() .HasOne(pa => pa.User) .WithMany() .HasForeignKey(pa => pa.UserId) .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() .HasOne(pa => pa.Flashcard) .WithMany() .HasForeignKey(pa => pa.FlashcardId) .OnDelete(DeleteBehavior.SetNull); // StudySession relationship removed // UserAudioPreferences relationship modelBuilder.Entity() .HasOne(uap => uap.User) .WithOne() .HasForeignKey(uap => uap.UserId) .OnDelete(DeleteBehavior.Cascade); } private void ConfigureImageGenerationEntities(ModelBuilder modelBuilder) { // ExampleImage configuration var exampleImageEntity = modelBuilder.Entity(); exampleImageEntity.Property(ei => ei.Id).HasColumnName("id"); exampleImageEntity.Property(ei => ei.RelativePath).HasColumnName("relative_path"); exampleImageEntity.Property(ei => ei.AltText).HasColumnName("alt_text"); exampleImageEntity.Property(ei => ei.GeminiPrompt).HasColumnName("gemini_prompt"); exampleImageEntity.Property(ei => ei.GeminiDescription).HasColumnName("gemini_description"); exampleImageEntity.Property(ei => ei.ReplicatePrompt).HasColumnName("replicate_prompt"); exampleImageEntity.Property(ei => ei.ReplicateModel).HasColumnName("replicate_model"); exampleImageEntity.Property(ei => ei.ReplicateVersion).HasColumnName("replicate_version"); exampleImageEntity.Property(ei => ei.GeminiCost).HasColumnName("gemini_cost"); exampleImageEntity.Property(ei => ei.ReplicateCost).HasColumnName("replicate_cost"); exampleImageEntity.Property(ei => ei.TotalGenerationCost).HasColumnName("total_generation_cost"); exampleImageEntity.Property(ei => ei.FileSize).HasColumnName("file_size"); exampleImageEntity.Property(ei => ei.ImageWidth).HasColumnName("image_width"); exampleImageEntity.Property(ei => ei.ImageHeight).HasColumnName("image_height"); exampleImageEntity.Property(ei => ei.ContentHash).HasColumnName("content_hash"); exampleImageEntity.Property(ei => ei.QualityScore).HasColumnName("quality_score"); exampleImageEntity.Property(ei => ei.ModerationStatus).HasColumnName("moderation_status"); exampleImageEntity.Property(ei => ei.ModerationNotes).HasColumnName("moderation_notes"); exampleImageEntity.Property(ei => ei.AccessCount).HasColumnName("access_count"); exampleImageEntity.Property(ei => ei.CreatedAt).HasColumnName("created_at"); exampleImageEntity.Property(ei => ei.UpdatedAt).HasColumnName("updated_at"); exampleImageEntity.HasIndex(ei => ei.ContentHash).IsUnique(); exampleImageEntity.HasIndex(ei => ei.AccessCount); // FlashcardExampleImage configuration var flashcardImageEntity = modelBuilder.Entity(); flashcardImageEntity.Property(fei => fei.FlashcardId).HasColumnName("flashcard_id"); flashcardImageEntity.Property(fei => fei.ExampleImageId).HasColumnName("example_image_id"); flashcardImageEntity.Property(fei => fei.DisplayOrder).HasColumnName("display_order"); flashcardImageEntity.Property(fei => fei.IsPrimary).HasColumnName("is_primary"); flashcardImageEntity.Property(fei => fei.ContextRelevance).HasColumnName("context_relevance"); flashcardImageEntity.Property(fei => fei.CreatedAt).HasColumnName("created_at"); // ImageGenerationRequest configuration var generationRequestEntity = modelBuilder.Entity(); generationRequestEntity.Property(igr => igr.Id).HasColumnName("id"); generationRequestEntity.Property(igr => igr.UserId).HasColumnName("user_id"); generationRequestEntity.Property(igr => igr.FlashcardId).HasColumnName("flashcard_id"); generationRequestEntity.Property(igr => igr.OverallStatus).HasColumnName("overall_status"); generationRequestEntity.Property(igr => igr.GeminiStatus).HasColumnName("gemini_status"); generationRequestEntity.Property(igr => igr.ReplicateStatus).HasColumnName("replicate_status"); generationRequestEntity.Property(igr => igr.OriginalRequest).HasColumnName("original_request"); generationRequestEntity.Property(igr => igr.GeminiPrompt).HasColumnName("gemini_prompt"); generationRequestEntity.Property(igr => igr.GeneratedDescription).HasColumnName("generated_description"); generationRequestEntity.Property(igr => igr.FinalReplicatePrompt).HasColumnName("final_replicate_prompt"); generationRequestEntity.Property(igr => igr.GeneratedImageId).HasColumnName("generated_image_id"); generationRequestEntity.Property(igr => igr.GeminiErrorMessage).HasColumnName("gemini_error_message"); generationRequestEntity.Property(igr => igr.ReplicateErrorMessage).HasColumnName("replicate_error_message"); generationRequestEntity.Property(igr => igr.GeminiProcessingTimeMs).HasColumnName("gemini_processing_time_ms"); generationRequestEntity.Property(igr => igr.ReplicateProcessingTimeMs).HasColumnName("replicate_processing_time_ms"); generationRequestEntity.Property(igr => igr.TotalProcessingTimeMs).HasColumnName("total_processing_time_ms"); generationRequestEntity.Property(igr => igr.GeminiCost).HasColumnName("gemini_cost"); generationRequestEntity.Property(igr => igr.ReplicateCost).HasColumnName("replicate_cost"); generationRequestEntity.Property(igr => igr.TotalCost).HasColumnName("total_cost"); generationRequestEntity.Property(igr => igr.CreatedAt).HasColumnName("created_at"); generationRequestEntity.Property(igr => igr.GeminiStartedAt).HasColumnName("gemini_started_at"); generationRequestEntity.Property(igr => igr.GeminiCompletedAt).HasColumnName("gemini_completed_at"); generationRequestEntity.Property(igr => igr.ReplicateStartedAt).HasColumnName("replicate_started_at"); generationRequestEntity.Property(igr => igr.ReplicateCompletedAt).HasColumnName("replicate_completed_at"); generationRequestEntity.Property(igr => igr.CompletedAt).HasColumnName("completed_at"); // 關聯關係 flashcardImageEntity .HasOne(fei => fei.Flashcard) .WithMany(f => f.FlashcardExampleImages) // 指定反向導航屬性 .HasForeignKey(fei => fei.FlashcardId) .OnDelete(DeleteBehavior.Cascade); flashcardImageEntity .HasOne(fei => fei.ExampleImage) .WithMany(ei => ei.FlashcardExampleImages) .HasForeignKey(fei => fei.ExampleImageId) .OnDelete(DeleteBehavior.Cascade); generationRequestEntity .HasOne(igr => igr.User) .WithMany() .HasForeignKey(igr => igr.UserId) .OnDelete(DeleteBehavior.Cascade); generationRequestEntity .HasOne(igr => igr.Flashcard) .WithMany() .HasForeignKey(igr => igr.FlashcardId) .OnDelete(DeleteBehavior.Cascade); generationRequestEntity .HasOne(igr => igr.GeneratedImage) .WithMany() .HasForeignKey(igr => igr.GeneratedImageId) .OnDelete(DeleteBehavior.SetNull); } private void ConfigureOptionsVocabularyEntity(ModelBuilder modelBuilder) { var optionsVocabEntity = modelBuilder.Entity(); // Configure column names (snake_case) optionsVocabEntity.Property(ov => ov.Id).HasColumnName("id"); optionsVocabEntity.Property(ov => ov.Word).HasColumnName("word"); optionsVocabEntity.Property(ov => ov.CEFRLevel).HasColumnName("cefr_level"); optionsVocabEntity.Property(ov => ov.PartOfSpeech).HasColumnName("part_of_speech"); optionsVocabEntity.Property(ov => ov.WordLength).HasColumnName("word_length"); optionsVocabEntity.Property(ov => ov.IsActive).HasColumnName("is_active"); optionsVocabEntity.Property(ov => ov.CreatedAt).HasColumnName("created_at"); optionsVocabEntity.Property(ov => ov.UpdatedAt).HasColumnName("updated_at"); // Configure default values optionsVocabEntity.Property(ov => ov.IsActive).HasDefaultValue(true); optionsVocabEntity.Property(ov => ov.CreatedAt).HasDefaultValueSql("CURRENT_TIMESTAMP"); optionsVocabEntity.Property(ov => ov.UpdatedAt).HasDefaultValueSql("CURRENT_TIMESTAMP"); } }