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

类型化语言中产品聚合存储多类型属性的实现方案咨询

EAV模式下类型化产品属性的存储实现方案

针对你遇到的Product类无法统一存储不同类型ProductAttributeAssignment子类的问题,这里提供几种兼顾类型安全和业务扩展性的实现方案:

方案1:非泛型接口+泛型基类(推荐)

通过定义非泛型接口实现统一存储,同时用泛型基类保留类型信息,既满足List存储需求,又能在使用时获取强类型属性值。

代码实现

// 非泛型接口,用于统一存储
public interface IProductAttribute
{
    AttributeDefinition Attribute { get; }
}

// 泛型抽象基类,继承非泛型接口
public abstract class ProductAttributeAssignment<T> : IProductAttribute
{
    public AttributeDefinition Attribute { get; set; }
    public abstract T Value { get; set; }
}

// 具体属性子类
public class DecimalProductAttribute : ProductAttributeAssignment<double>
{
    public override double Value { get; set; }
}

public class VarcharProductAttribute : ProductAttributeAssignment<string>
{
    public override string Value { get; set; }
}

public class TextProductAttribute : ProductAttributeAssignment<string>
{
    public override string Value { get; set; }
}

// Product类定义
public class Product
{
    public string Name { get; set; }
    // 用非泛型接口的List统一存储所有属性
    public List<IProductAttribute> Attributes { get; set; } = new List<IProductAttribute>();

    // 类型安全的属性获取方法,避免强制转换
    public T? GetAttributeValue<T>(string attributeName)
    {
        var attribute = Attributes
            .OfType<ProductAttributeAssignment<T>>()
            .FirstOrDefault(a => a.Attribute.attributeName == attributeName);
        
        return attribute?.Value;
    }

    // 针对常用类型的快捷方法
    public double? GetDecimalAttributeValue(string attributeName)
    {
        return GetAttributeValue<double>(attributeName);
    }

    public string? GetStringAttributeValue(string attributeName)
    {
        return GetAttributeValue<string>(attributeName);
    }
}

优势

  • 完全保留属性的原生类型,支持数值计算、范围搜索等场景
  • 通过泛型和类型安全的获取方法,避免运行时类型转换错误
  • 扩展性强,新增属性类型只需继承ProductAttributeAssignment<T>即可

方案2:字典存储+类型转换封装

如果不想引入过多接口和泛型,可以用Dictionary<string, object>存储属性值,结合AttributeDefinition的类型标记做封装,确保类型安全。

代码实现

public class Product
{
    public string Name { get; set; }
    // 存储属性定义和对应值的字典
    public Dictionary<AttributeDefinition, object> Attributes { get; set; } = new Dictionary<AttributeDefinition, object>();

    // 设置属性值的类型安全方法
    public void SetAttributeValue(AttributeDefinition definition, object value)
    {
        // 校验类型匹配,避免错误赋值
        if (!IsValueMatchingType(definition, value))
        {
            throw new ArgumentException($"Value type does not match attribute {definition.attributeName}'s data type");
        }
        Attributes[definition] = value;
    }

    // 获取属性值的类型安全方法
    public T? GetAttributeValue<T>(AttributeDefinition definition)
    {
        if (Attributes.TryGetValue(definition, out var value))
        {
            return (T)value;
        }
        return default;
    }

    // 辅助方法:校验值与属性类型是否匹配
    private bool IsValueMatchingType(AttributeDefinition definition, object value)
    {
        return definition.dataType switch
        {
            DataType.Numeric => value is double,
            DataType.Varchar => value is string,
            DataType.Text => value is string,
            _ => false
        };
    }
}

// 补充DataType枚举定义
public enum DataType
{
    Numeric,
    Varchar,
    Text
}

优势

  • 实现简单,代码量少
  • 直接通过字典关联属性定义和值,逻辑清晰

劣势

  • 依赖object类型,虽然有封装校验,但仍存在一定的类型转换风险

方案3:访问者模式(适合复杂操作场景)

如果后续需要频繁对不同类型属性执行统一操作(比如批量导出、范围过滤),可以使用访问者模式,将属性操作与属性类型解耦。

代码实现

// 访问者接口
public interface IProductAttributeVisitor
{
    void Visit(DecimalProductAttribute attribute);
    void Visit(VarcharProductAttribute attribute);
    void Visit(TextProductAttribute attribute);
}

// 修改抽象基类,添加Accept方法
public abstract class ProductAttributeAssignment
{
    public AttributeDefinition Attribute { get; set; }
    public abstract void Accept(IProductAttributeVisitor visitor);
}

// 具体属性子类实现Accept方法
public class DecimalProductAttribute : ProductAttributeAssignment
{
    public double Value { get; set; }

    public override void Accept(IProductAttributeVisitor visitor)
    {
        visitor.Visit(this);
    }
}

public class VarcharProductAttribute : ProductAttributeAssignment
{
    public string Value { get; set; }

    public override void Accept(IProductAttributeVisitor visitor)
    {
        visitor.Visit(this);
    }
}

// Product类
public class Product
{
    public string Name { get; set; }
    public List<ProductAttributeAssignment> Attributes { get; set; } = new List<ProductAttributeAssignment>();

    // 执行访问者操作
    public void ExecuteVisitor(IProductAttributeVisitor visitor)
    {
        foreach (var attribute in Attributes)
        {
            attribute.Accept(visitor);
        }
    }
}

// 示例访问者:筛选数值属性大于指定值的属性
public class NumericRangeFilterVisitor : IProductAttributeVisitor
{
    private readonly double _minValue;
    public List<DecimalProductAttribute> MatchingAttributes { get; } = new List<DecimalProductAttribute>();

    public NumericRangeFilterVisitor(double minValue)
    {
        _minValue = minValue;
    }

    public void Visit(DecimalProductAttribute attribute)
    {
        if (attribute.Value > _minValue)
        {
            MatchingAttributes.Add(attribute);
        }
    }

    public void Visit(VarcharProductAttribute attribute) { }
    public void Visit(TextProductAttribute attribute) { }
}

优势

  • 完美支持不同类型属性的差异化操作,扩展新操作只需新增访问者类
  • 避免大量if-else类型判断,代码更整洁

劣势

  • 新增属性类型时需要修改访问者接口和所有实现类,仅适合属性类型相对稳定的场景

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 04:32:22