You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

WPF应用搜索历史与结果本地存储优化方案问询

Efficient, Standardized Approach for WPF Search Settings Storage (No Database)

Great question—moving away from Settings.settings's JSON-to-XML overhead is a smart call, and we can fix the frequent serialization/deserialization issue with a few best practices tailored to WPF apps. Here's a practical, efficient solution:

Core Idea: Memory Cache + Controlled Persistence

Instead of reading/writing the JSON file every time a dialog opens/closes, load all settings into memory once at app startup, operate on the in-memory data during runtime, and only persist changes to the file at strategic, low-frequency times. This eliminates redundant I/O operations while keeping your data safe.

Step 1: Design a Centralized Settings Manager

Create a singleton (or dependency-injected) manager class to handle all search settings. This class will:

  • Load the JSON file into memory on app startup
  • Provide access to individual dialog settings
  • Track when data is modified ("dirty")
  • Persist changes to the file only when needed

Example implementation using System.Text.Json (built-in, no external dependencies):

using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System;

public class UserSearchSettingsManager
{
    // Singleton instance (use DI instead if following MVVM strictly)
    private static readonly Lazy<UserSearchSettingsManager> _instance = 
        new Lazy<UserSearchSettingsManager>(() => new UserSearchSettingsManager());
    public static UserSearchSettingsManager Instance => _instance.Value;

    private readonly string _settingsPath;
    private Dictionary<string, DialogSearchSettings> _allDialogSettings = new();
    private bool _isDirty = false;

    private UserSearchSettingsManager()
    {
        // Store settings in Windows' recommended user app data folder
        var appDataDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "YourAppName");
        Directory.CreateDirectory(appDataDir);
        _settingsPath = Path.Combine(appDataDir, "userSettings.json");
        
        LoadSettingsFromFile();
    }

    // Get settings for a specific dialog (uses unique ID to avoid conflicts)
    public DialogSearchSettings GetDialogSettings(string dialogUniqueId)
    {
        if (!_allDialogSettings.TryGetValue(dialogUniqueId, out var settings))
        {
            settings = new DialogSearchSettings();
            _allDialogSettings[dialogUniqueId] = settings;
            _isDirty = true;
        }
        return settings;
    }

    // Mark settings as modified after updates
    public void MarkSettingsDirty() => _isDirty = true;

    // Persist only if changes were made
    public void SaveSettingsToFile()
    {
        if (!_isDirty) return;

        try
        {
            // Use temp file to avoid corrupting the original if save fails
            var tempPath = _settingsPath + ".tmp";
            var json = JsonSerializer.Serialize(_allDialogSettings, new JsonSerializerOptions
            {
                WriteIndented = true,
                IgnoreNullValues = true,
                PropertyNameCaseInsensitive = true
            });
            
            File.WriteAllText(tempPath, json);
            File.Replace(tempPath, _settingsPath, _settingsPath + ".bak"); // Keep backup
            _isDirty = false;
        }
        catch (Exception ex)
        {
            // Log error (use your app's logging system)
            // Optionally notify user save failed
        }
    }

    private void LoadSettingsFromFile()
    {
        if (!File.Exists(_settingsPath)) return;

        try
        {
            var json = File.ReadAllText(_settingsPath);
            _allDialogSettings = JsonSerializer.Deserialize<Dictionary<string, DialogSearchSettings>>(json) 
                ?? new Dictionary<string, DialogSearchSettings>();
        }
        catch (Exception ex)
        {
            // Log error, reset to empty settings if file is corrupted
            _allDialogSettings = new Dictionary<string, DialogSearchSettings>();
        }
    }
}

// Data model for individual dialog settings
public class DialogSearchSettings
{
    // Limit history size to prevent unbounded growth
    private const int MaxHistoryEntries = 20;
    
    public List<SearchHistoryEntry> SearchHistory { get; set; } = new();
    public SearchResult? LastSearchResult { get; set; }

    public void AddToHistory(string query)
    {
        SearchHistory.Insert(0, new SearchHistoryEntry { Query = query, Timestamp = DateTime.UtcNow });
        if (SearchHistory.Count > MaxHistoryEntries)
            SearchHistory.RemoveRange(MaxHistoryEntries, SearchHistory.Count - MaxHistoryEntries);
    }
}

public class SearchHistoryEntry
{
    public string Query { get; set; } = string.Empty;
    public DateTime Timestamp { get; set; }
}

// Replace with your actual search result model
public class SearchResult
{
    public List<string> Results { get; set; } = new();
    // Add other result properties as needed
}

Step 2: Integrate with Your Dialogs

In each dialog's ViewModel (or code-behind, if not using MVVM), access the manager to read/write settings:

// Example ViewModel for a search dialog
public class CustomerSearchViewModel
{
    private const string DialogId = "CustomerSearchDialog"; // Unique ID for this dialog
    private readonly DialogSearchSettings _settings;

    public ObservableCollection<SearchHistoryEntry> SearchHistory { get; set; }

    public CustomerSearchViewModel()
    {
        _settings = UserSearchSettingsManager.Instance.GetDialogSettings(DialogId);
        SearchHistory = new ObservableCollection<SearchHistoryEntry>(_settings.SearchHistory);
    }

    // Called when user executes a search
    public void ExecuteSearch(string query)
    {
        // Perform your search logic here
        var searchResult = new SearchResult { Results = new List<string> { "Result 1", "Result 2" } };

        // Update settings
        _settings.LastSearchResult = searchResult;
        _settings.AddToHistory(query);
        SearchHistory.Insert(0, _settings.SearchHistory[0]); // Refresh UI

        // Mark settings as dirty (will be saved later)
        UserSearchSettingsManager.Instance.MarkSettingsDirty();
    }
}

Step 3: Trigger Persistence at Strategic Times

Avoid saving on every dialog close—instead, save at these safe, low-frequency points:

  1. App Exit: Add this to your App.xaml.cs:
    protected override void OnExit(ExitEventArgs e)
    {
        UserSearchSettingsManager.Instance.SaveSettingsToFile();
        base.OnExit(e);
    }
    
  2. Session Ending: Handle user logout/shutdown to avoid data loss:
    private void App_SessionEnding(object sender, SessionEndingCancelEventArgs e)
    {
        UserSearchSettingsManager.Instance.SaveSettingsToFile();
    }
    
  3. Auto-Save (Optional): For extra safety, add a timer to save every 5-10 minutes:
    private DispatcherTimer _autoSaveTimer;
    
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        _autoSaveTimer = new DispatcherTimer
        {
            Interval = TimeSpan.FromMinutes(5)
        };
        _autoSaveTimer.Tick += (s, args) => UserSearchSettingsManager.Instance.SaveSettingsToFile();
        _autoSaveTimer.Start();
    }
    

Step 4: Optional: Use Dependency Injection (MVVM Best Practice)

If you're following MVVM, replace the singleton with a DI-registered service for better testability:

// In App.xaml.cs OnStartup
var services = new ServiceCollection();
services.AddSingleton<UserSearchSettingsManager>();
services.AddTransient<CustomerSearchViewModel>();
// Register other services/views
var serviceProvider = services.BuildServiceProvider();

// Inject into ViewModel constructor
public CustomerSearchViewModel(UserSearchSettingsManager settingsManager)
{
    _settings = settingsManager.GetDialogSettings(DialogId);
    // ... rest of code
}

Key Benefits

  • Efficiency: Only read/write the JSON file 1-2 times per app session (plus optional auto-saves), eliminating redundant I/O.
  • Reliability: Uses temp files and backups to prevent data corruption.
  • Maintainability: Centralized manager keeps settings logic in one place, easy to update.
  • Scalability: Supports adding new dialogs by just using a unique ID.

内容的提问来源于stack exchange,提问作者Kurus

火山引擎 最新活动