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

Web API返回散点图数据:如何用匿名类型规避大量分支判断?

Clean Approach for Handling 30 Scatterplot Axis Combinations

Awesome question—having to handle 30 combinations with a giant block of if/else statements is a surefire way to write code that’s hard to maintain and debug. Let’s break down a clean, scalable approach using the strategy pattern paired with dictionary-based dispatch, plus anonymous types to skip creating dozens of unnecessary DTOs:


1. Abstract Score Retrieval Logic with Strategy Pattern

First, we’ll define a common interface for all score retrieval strategies. This lets us encapsulate the logic for each axis type (named score, attribute ID, domain ID) into separate, focused classes.

public interface IScoreProvider
{
    // Returns employee IDs paired with their scores for the given axis
    IEnumerable<(int EmployeeId, decimal Score)> GetScores(Axis axis, int companyId);
}

Next, implement a provider class for each axis type. This keeps your data access logic isolated and easy to test:

// Provider for named scores
public class NamedScoreProvider : IScoreProvider
{
    private readonly IEmployeeScoreRepository _scoreRepo;

    public NamedScoreProvider(IEmployeeScoreRepository scoreRepo)
    {
        _scoreRepo = scoreRepo;
    }

    public IEnumerable<(int EmployeeId, decimal Score)> GetScores(Axis axis, int companyId)
    {
        return _scoreRepo.GetNamedScores(
            companyId: companyId,
            teamId: axis.TeamId,
            scoreName: axis.NamedScore
        );
    }
}

// Provider for attribute ID scores
public class AttributeScoreProvider : IScoreProvider
{
    private readonly IEmployeeAttributeRepository _attributeRepo;

    public AttributeScoreProvider(IEmployeeAttributeRepository attributeRepo)
    {
        _attributeRepo = attributeRepo;
    }

    public IEnumerable<(int EmployeeId, decimal Score)> GetScores(Axis axis, int companyId)
    {
        // Assume AttributeId is validated to be non-null before this point
        return _attributeRepo.GetAttributeScores(
            companyId: companyId,
            teamId: axis.TeamId,
            attributeId: axis.AttributeId.Value
        );
    }
}

// Provider for domain ID scores
public class DomainScoreProvider : IScoreProvider
{
    private readonly IEmployeeDomainRepository _domainRepo;

    public DomainScoreProvider(IEmployeeDomainRepository domainRepo)
    {
        _domainRepo = domainRepo;
    }

    public IEnumerable<(int EmployeeId, decimal Score)> GetScores(Axis axis, int companyId)
    {
        // Assume DomainId is validated to be non-zero before this point
        return _domainRepo.GetDomainScores(
            companyId: companyId,
            teamId: axis.TeamId,
            domainId: axis.DomainId.Value
        );
    }
}

2. Use a Dictionary to Map Axis Types to Providers

Instead of if/else checks, we’ll use a dictionary to match each axis’s configuration to the correct provider. This makes adding new axis types trivial (just add a new entry to the dictionary).

In your controller’s constructor (using dependency injection to inject the providers):

private readonly Dictionary<Func<Axis, bool>, IScoreProvider> _scoreProviderMap;

public EmployeeScoresController(
    NamedScoreProvider namedProvider,
    AttributeScoreProvider attributeProvider,
    DomainScoreProvider domainProvider
)
{
    _scoreProviderMap = new Dictionary<Func<Axis, bool>, IScoreProvider>
    {
        // Match axis with non-empty NamedScore
        { ax => !string.IsNullOrWhiteSpace(ax.NamedScore), namedProvider },
        // Match axis with non-null AttributeId
        { ax => ax.AttributeId.HasValue, attributeProvider },
        // Match axis with non-zero DomainId
        { ax => ax.DomainId.HasValue && ax.DomainId != 0, domainProvider }
    };
}

3. Add Validation to Ensure Valid Axis Configurations

Before processing, add a validation step to make sure each axis has exactly one valid score type specified (no mixed values):

private bool IsAxisValid(Axis axis)
{
    var validFlags = new List<bool>
    {
        !string.IsNullOrWhiteSpace(axis.NamedScore),
        axis.AttributeId.HasValue,
        axis.DomainId.HasValue && axis.DomainId != 0
    };

    // Ensure exactly one score type is set
    return validFlags.Count(flag => flag) == 1;
}

4. Implement the Controller Logic

Now, tie it all together to fetch scores for both axes, merge them, and return anonymous types for the scatterplot:

[HttpPost("{id}/scatterplot")]
public ActionResult Scatterplot([FromRoute] int id, [FromBody] ScatterplotAxis axisRequest)
{
    // Validate both axes first
    if (!IsAxisValid(axisRequest.XAxis) || !IsAxisValid(axisRequest.YAxis))
    {
        return BadRequest("Each axis must specify exactly one score type (named score, attribute ID, or domain ID).");
    }

    // Helper method to get scores for a single axis
    IEnumerable<(int EmployeeId, decimal Score)> GetAxisScores(Axis axis)
    {
        var provider = _scoreProviderMap.First(pair => pair.Key(axis)).Value;
        return provider.GetScores(axis, id);
    }

    // Fetch scores for X and Y axes, convert to dictionaries for fast lookup
    var xScores = GetAxisScores(axisRequest.XAxis).ToDictionary(s => s.EmployeeId);
    var yScores = GetAxisScores(axisRequest.YAxis).ToDictionary(s => s.EmployeeId);

    // Find employees present in both datasets and generate scatterplot data
    var commonEmployeeIds = xScores.Keys.Intersect(yScores.Keys);
    var scatterplotData = commonEmployeeIds.Select(empId => new
    {
        EmployeeId = empId,
        XScore = xScores[empId].Score,
        YScore = yScores[empId].Score
    });

    return Ok(scatterplotData);
}

Key Benefits of This Approach

  • No giant if/else blocks: All logic is encapsulated in focused provider classes.
  • Scalable: Adding a new score type only requires a new provider class and a dictionary entry—no changes to existing logic (follows the Open/Closed Principle).
  • Clean data return: Anonymous types let you return exactly the data the scatterplot needs without creating 30+ custom DTO classes.
  • Testable: Each provider can be tested independently, making unit tests easier to write and maintain.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 03:40:04