You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用C# Observer Pattern优化MVC+EF订单缺货通知功能?

Nice question! Let's walk through how to implement the C# Observer Pattern alongside Entity Framework's EntityState to make your out-of-stock notification workflow more modular, maintainable, and efficient. Here's a step-by-step breakdown with code examples tailored to your MVC+EF+React setup:

1. Core Concepts to Tie Together

First, let's align on why this combination works:

  • Observer Pattern: Decouples the "trigger" (marking an item as unavailable) from the "action" (notifying users). This lets you add new notification types (like SMS or in-app alerts) later without touching existing code.
  • EntityState: EF's ChangeTracker tracks when entities are modified, added, or deleted. We'll use this to detect exactly which OrderItems were marked as unavailable, avoiding redundant checks or notifications.
2. Define Observer & Subject Interfaces

Start with abstractions to keep your code flexible:

// Observer interface: Defines the contract for any service that needs to react to unavailable items
public interface IOrderItemObserver
{
    void OnItemUnavailable(OrderItem item);
}

// Subject interface: Manages observers and triggers notifications when state changes
public interface IOrderItemSubject
{
    void Attach(IOrderItemObserver observer);
    void Detach(IOrderItemObserver observer);
    void NotifyUnavailableItems();
}
3. Implement the Subject with EF EntityState Integration

The subject will use EF's ChangeTracker to find modified OrderItems where IsUnavailable was set to true:

public class OrderItemSubject : IOrderItemSubject
{
    private readonly List<IOrderItemObserver> _observers = new();
    private readonly AppDbContext _dbContext;

    public OrderItemSubject(AppDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public void Attach(IOrderItemObserver observer)
    {
        if (!_observers.Contains(observer))
        {
            _observers.Add(observer);
        }
    }

    public void Detach(IOrderItemObserver observer)
    {
        _observers.Remove(observer);
    }

    public void NotifyUnavailableItems()
    {
        // Use EF's ChangeTracker to filter only items just marked as unavailable
        var unavailableItems = _dbContext.ChangeTracker.Entries<OrderItem>()
            .Where(entry => 
                entry.State == EntityState.Modified && 
                entry.Property(p => p.IsUnavailable).CurrentValue == true &&
                entry.Property(p => p.IsUnavailable).OriginalValue == false) // Ensure it's a new change
            .Select(entry => entry.Entity)
            .ToList();

        // Notify all registered observers for each affected item
        foreach (var item in unavailableItems)
        {
            foreach (var observer in _observers)
            {
                observer.OnItemUnavailable(item);
            }
        }
    }
}
4. Implement the Observer (Email Notification Service)

Create an observer that handles sending emails to users. This keeps your notification logic separate from the MVC controller:

public class EmailNotificationObserver : IOrderItemObserver
{
    private readonly IEmailService _emailService;

    // Inject your existing email service for better testability
    public EmailNotificationObserver(IEmailService emailService)
    {
        _emailService = emailService;
    }

    public void OnItemUnavailable(OrderItem item)
    {
        // Fetch the user from the associated Order (adjust based on your entity relationships)
        var user = item.Order.User;
        var productName = item.Product.Name;

        var subject = "Your Ordered Item is Unavailable";
        var body = $"Hi {user.FullName},\n\nThe item \"{productName}\" from your recent order is currently out of stock. Please log in to our website to select a replacement or update your order.\n\nThanks,\nThe Team";

        // Add error handling/retry logic here if needed
        _emailService.SendEmail(user.Email, subject, body);
    }
}

// Example email service interface (replace with your actual implementation)
public interface IEmailService
{
    void SendEmail(string to, string subject, string body);
}
5. Integrate with MVC & EF

Now wire everything up in your MVC controller and DbContext to automate notifications:

Step 5.1: Register Services in Dependency Injection

In your Program.cs (or Startup.cs), register the subject and observer:

builder.Services.AddScoped<IOrderItemSubject, OrderItemSubject>();
builder.Services.AddScoped<IOrderItemObserver, EmailNotificationObserver>();
builder.Services.AddScoped<IEmailService, YourExistingEmailService>(); // Use your real email service

Step 5.2: Update the DbContext to Trigger Notifications Automatically

Modify your DbContext to call NotifyUnavailableItems after saving changes, so you don't have to trigger it manually in every controller:

public class AppDbContext : DbContext
{
    private readonly IOrderItemSubject _orderItemSubject;

    public AppDbContext(DbContextOptions<AppDbContext> options, IOrderItemSubject orderItemSubject) 
        : base(options)
    {
        _orderItemSubject = orderItemSubject;
        // Attach the observer (DI handles lifecycle management)
        var emailObserver = this.GetService<IOrderItemObserver>();
        if (emailObserver != null)
        {
            _orderItemSubject.Attach(emailObserver);
        }
    }

    public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
    {
        // Save changes first to persist the unavailable state
        var result = await base.SaveChangesAsync(cancellationToken);
        
        // Trigger notifications for items just marked as unavailable
        _orderItemSubject.NotifyUnavailableItems();
        
        return result;
    }
}

Step 5.3: Update Your MVC Controller Method

Your existing React-triggered API endpoint becomes simpler—just mark the item as unavailable and let EF handle the rest:

[ApiController]
[Route("api/orders/items")]
public class OrderItemController : ControllerBase
{
    private readonly AppDbContext _dbContext;

    public OrderItemController(AppDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    [HttpPost("{id}/mark-unavailable")]
    public async Task<IActionResult> MarkAsUnavailable(int id)
    {
        var orderItem = await _dbContext.OrderItems
            .Include(item => item.Order)
            .Include(item => item.Order.User)
            .Include(item => item.Product)
            .FirstOrDefaultAsync(item => item.Id == id);

        if (orderItem == null)
        {
            return NotFound("Order item not found");
        }

        // Mark the item as unavailable
        orderItem.IsUnavailable = true;
        
        // Save changes—this triggers the DbContext's SaveChangesAsync, which fires notifications
        await _dbContext.SaveChangesAsync();

        return Ok("Item marked as unavailable, notification sent");
    }
}
6. Key Benefits of This Approach
  • Decoupling: If you later want to add SMS notifications or in-app alerts, just create a new IOrderItemObserver implementation and register it—no changes to existing code.
  • Efficiency: Uses EF's EntityState to only process items that were actually just marked as unavailable, avoiding redundant checks.
  • Maintainability: Clear separation of concerns: the controller handles HTTP requests, the subject manages state changes, and observers handle actions.
  • Testability: You can mock the IEmailService or IOrderItemObserver to test without sending real emails.

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 11:44:31