在 OData 中,多态(Polymorphism)指:
- 一个 EntitySet 可以包含同一基类(Base EntityType)的多个派生类(Derived EntityType)实例。
- 派生类继承基类的所有属性,并可扩展新的属性或重写行为。
例如,定义一个基类 Shape(包含 Id 和 Area 属性),其派生类 Circle(新增 Radius 属性)和 Rectangle(新增 Width、Height 属性),三者可共同存储在 Shapes 这个 EntitySet 中。
namespace Lesson2.Models
{
public class Shape
{
public int Id { get; set; }
public double Area { get; set; }
}
}
namespace Lesson2.Models
{
public class Circle : Shape
{
public double Radius { get; set; }
}
}
namespace Lesson2.Models
{
public class Rectangle : Shape
{
public double Length { get; set; }
public double Width { get; set; }
}
}
元数据声明:多态的 “契约”
OData 服务通过元数据($metadata 接口)明确声明多态支持。核心规则如下:
- 基类与派生类的关系定义
在元数据 XML 中,派生类通过 BaseType 属性关联基类
<EntityType Name="Shape">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="Edm.Int32" Nullable="false" />
<Property Name="Area" Type="Edm.Double" Nullable="false" />
</EntityType>
<EntityType Name="Circle" BaseType="Lesson2.Models.Shape">
<Property Name="Radius" Type="Edm.Double" Nullable="false" />
</EntityType>
<EntityType Name="Rectangle" BaseType="Lesson2.Models.Shape">
<Property Name="Length" Type="Edm.Double" Nullable="false" />
<Property Name="Width" Type="Edm.Double" Nullable="false" />
</EntityType>
- EntitySet 关联基类
EntitySet 只需关联基类,派生类自动被包含:
<EntitySet Name="Shapes" EntityType="Lesson2.Models.Shape" />
此时,Shapes EntitySet 可包含 Shape、Circle、Rectangle 类型的实体。
客户端访问:多态实体的精准操作
客户端可通过 OData 协议提供的类型限定符或 $cast 操作符,实现对派生类实体的精准访问。
- 直接访问派生类类型
若已知实体类型为派生类,可通过 URI 路径直接访问其扩展属性。例如,查询 ID 为 2 的 Circle Entity:
curl --location 'http://localhost:5119/odata/Shapes(2)/Lesson2.Models.Circle'
{
"@odata.context": "http://localhost:5119/odata/$metadata#Shapes/Lesson2.Models.Circle/$entity",
"Id": 2,
"Area": 38.5,
"Radius": 3.5
}
- 过滤派生类类型Entity
如果EntitySet中既有基类类型Entity,还有其他派生类类型Entity,则我们可以在URI中指定派生类类型名,筛选出该类型的所有Entity。例如,查询实际类型是Lesson2.Models.Rectangle
的所有Entity。
curl --location 'http://localhost:5119/odata/Shapes/Lesson2.Models.Rectangle'
{
"@odata.context": "http://localhost:5119/odata/$metadata#Shapes/Lesson2.Models.Rectangle",
"value": [
{
"Id": 3,
"Area": 40.0,
"Length": 8.0,
"Width": 5.0
}
]
}
服务端实现:多态的存储与处理
服务端需通过 OData 框架(如 ASP.NET Core OData)配置多态支持,并确保派生类实体的正确存储与查询。
- Edm 模型配置
我们既可以通过上面定义每个类以及其继承关系的形式表达它们之间的关系,还可以通过 IEdmModel 构建器声明基类与派生类的关系:
public static IEdmModel BuildEdmModel()
{
var builder = new ODataConventionModelBuilder();
// 定义基类 Shape
var shape = builder.EntityType<Shape>();
shape.HasKey(s => s.Id);
shape.Property(s => s.Area);
// 定义派生类 Circle(继承 Shape)
var circle = builder.EntityType<Circle>().DerivesFrom<Shape>();
circle.Property(c => c.Radius);
// 定义派生类 Rectangle(继承 Shape)
var rectangle = builder.EntityType<Rectangle>().DerivesFrom<Shape>();
rectangle.Property(r => r.Width);
rectangle.Property(r => r.Height);
// 声明 EntitySet Shapes(关联基类 Shape)
builder.EntitySet<Shape>("Shapes");
return builder.GetEdmModel();
}
- 控制器处理多态实体
服务端控制器需支持接收和返回不同类型的实体。例如,ShapesController 可通过基类类型返回所有形状(包括派生类):
[EnableQuery]
public ActionResult<IEnumerable<Shape>> Get()
{
return Ok(shapes);
}
对应于
- Request
curl --location 'http://localhost:5119/odata/Shapes'
- Response
{
"@odata.context": "http://localhost:5119/odata/$metadata#Shapes",
"value": [
{
"Id": 1,
"Area": 28.0
},
{
"@odata.type": "#Lesson2.Models.Circle",
"Id": 2,
"Area": 38.5,
"Radius": 3.5
},
{
"@odata.type": "#Lesson2.Models.Rectangle",
"Id": 3,
"Area": 40.0,
"Length": 8.0,
"Width": 5.0
}
]
}
也可以通过派生类型返回特定派生类型的Entity
[EnableQuery]
public ActionResult<IEnumerable<Rectangle>> GetFromRectangle()
{
return Ok(shapes.OfType<Rectangle>().ToList());
}
对应于
- Request
curl --location 'http://localhost:5119/odata/Shapes/Lesson2.Models.Rectangle'
- Response
{
"@odata.context": "http://localhost:5119/odata/$metadata#Shapes/Lesson2.Models.Rectangle",
"value": [
{
"Id": 3,
"Area": 40.0,
"Length": 8.0,
"Width": 5.0
}
]
}
多态支持的实际价值
- 灵活扩展:允许在不修改 EntitySet 定义的情况下,新增派生类(如 Triangle),适配业务需求的动态变化。
- 统一入口:通过基类 EntitySet 管理所有相关实体,简化客户端 API 设计(无需为每个派生类单独定义接口)。
- 复杂查询:支持跨派生类的联合查询(如统计所有形状的总面积)或分类查询(如仅查询圆形),满足多样化数据需求。