如何使用 C# 代码计算数据库中的对象 属性?
How to compute object property in database with C# code?
举个例子,假设我的数据库有一个 table 有数千艘船,每艘船可能有数千名乘客作为导航 属性:
public DbSet<Ship> Ship { get; set; }
public DbSet<Passenger> Passenger { get; set; }
public class Ship
{
public List<Passenger> passengers { get; set; }
//properties omitted for example
}
public class Passenger
{
//properties omitted for example
}
示例用例是有人正在为每 API 获取 all 艘船,并想知道每艘船是否空(0 名乘客),所以返回的 JSON 将包含一个船舶列表,每个船舶都有一个布尔值是否为空。
我目前的代码似乎很低效(包括所有乘客只是为了确定一艘船是否是空的):
List<Ship> ships = dbContext.Ship
.Include(x => x.passengers)
.ToList();
及以后当飞船序列化为 JSON:
jsonShip.isEmpty = !ship.passengers.Any();
我想要一个更高效(而不是臃肿)的替代方案来包括所有乘客。我有哪些选择?
我看过 computed columns 但他们似乎只支持 sql 作为字符串。如果可能的话,我想留在 C# 代码世界中,例如,通过在 SQL 查询中自动编织来正确设置 属性 将是最佳选择。
为 Ship
创建一个反映 JSON 结果形状的数据传输对象,例如 -
public class ShipDto
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsEmpty { get; set; }
}
然后在查询中使用投影 -
var ships = dbCtx.Ships
.Select(p => new ShipDto
{
Id = p.Id,
Name = p.Name,
IsEmpty = !p.Passengers.Any()
})
.ToList();
通常,API 需要生成各种形状的响应,而 DTO 会为您提供明确定义的模型来表示 API 响应的形状。域实体并不总是适合这个。
如果您的域实体 (Ship
) 有很多属性,那么在 .Select()
方法中复制所有这些属性可能会很麻烦。您可以使用 AutoMapper
为您映射它们。 AutoMapper
有一个 ProjectTo<T>()
方法可以生成 SQL 和 return 投影结果。比如你可以用一个映射配置 -
来实现上面的结果
CreateMap<Ship, ShipDto>()
.ForMember(d => d.IsEmpty, opt => opt.MapFrom(s => !s.Passengers.Any()));
和一个查询 -
var ships = Mapper.ProjectTo<ShipDto>(dbCtx.Ships).ToList();
假设 ShipDto
中的所有其他属性的命名与 Ship
实体中的相似。
编辑:
如果您不想要 DTO 模型 -
- 你可以在
Ship
模型中添加一个NotMapped
属性-
public class Ship
{
public int Id { get; set; }
public string Name { get; set; }
[NotMapped]
public bool IsEmpty { get; set; }
public List<Passenger> passengers { get; set; }
}
然后像 -
这样查询
var ships = dbCtx.Ships
.Select(p => new Ship
{
Id = p.Id,
Name = p.Name,
IsEmpty = !p.Passengers.Any()
})
.ToList();
- 您可以 return 匿名类型 -
var ships = dbCtx.Ships
.Select(p => new
{
Id = p.Id,
Name = p.Name,
IsEmpty = !p.Passengers.Any()
})
.ToList();
如果我理解正确的话...
一种方法是存储每个 Ship
实体内的乘客数量。如果您使用领域驱动设计,将 Ship
视为聚合根,并且仅通过在给定 Ship
实体上公开的方法添加或删除乘客,这会很好地工作,例如RegisterPassenger()
/ RemovePassenger()
。在这些方法中,增加或减少乘客数量以及添加或删除乘客。
然后,很明显,您可以使用 PassengerCount < 0
投影到您需要的布尔值来查询 Ships
数据库集。而且,很明显,它甚至不会触及 Passengers
table.
在传统的贫血域 ASP.NET 系统中,这种数据冗余可能有点冒险,因为属性通常是公开的 mutable,并且您有多个服务 'massage'实体,这是数据完整性丢失的潜在来源。
举个例子,假设我的数据库有一个 table 有数千艘船,每艘船可能有数千名乘客作为导航 属性:
public DbSet<Ship> Ship { get; set; }
public DbSet<Passenger> Passenger { get; set; }
public class Ship
{
public List<Passenger> passengers { get; set; }
//properties omitted for example
}
public class Passenger
{
//properties omitted for example
}
示例用例是有人正在为每 API 获取 all 艘船,并想知道每艘船是否空(0 名乘客),所以返回的 JSON 将包含一个船舶列表,每个船舶都有一个布尔值是否为空。
我目前的代码似乎很低效(包括所有乘客只是为了确定一艘船是否是空的):
List<Ship> ships = dbContext.Ship
.Include(x => x.passengers)
.ToList();
及以后当飞船序列化为 JSON:
jsonShip.isEmpty = !ship.passengers.Any();
我想要一个更高效(而不是臃肿)的替代方案来包括所有乘客。我有哪些选择?
我看过 computed columns 但他们似乎只支持 sql 作为字符串。如果可能的话,我想留在 C# 代码世界中,例如,通过在 SQL 查询中自动编织来正确设置 属性 将是最佳选择。
为 Ship
创建一个反映 JSON 结果形状的数据传输对象,例如 -
public class ShipDto
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsEmpty { get; set; }
}
然后在查询中使用投影 -
var ships = dbCtx.Ships
.Select(p => new ShipDto
{
Id = p.Id,
Name = p.Name,
IsEmpty = !p.Passengers.Any()
})
.ToList();
通常,API 需要生成各种形状的响应,而 DTO 会为您提供明确定义的模型来表示 API 响应的形状。域实体并不总是适合这个。
如果您的域实体 (Ship
) 有很多属性,那么在 .Select()
方法中复制所有这些属性可能会很麻烦。您可以使用 AutoMapper
为您映射它们。 AutoMapper
有一个 ProjectTo<T>()
方法可以生成 SQL 和 return 投影结果。比如你可以用一个映射配置 -
CreateMap<Ship, ShipDto>()
.ForMember(d => d.IsEmpty, opt => opt.MapFrom(s => !s.Passengers.Any()));
和一个查询 -
var ships = Mapper.ProjectTo<ShipDto>(dbCtx.Ships).ToList();
假设 ShipDto
中的所有其他属性的命名与 Ship
实体中的相似。
编辑:
如果您不想要 DTO 模型 -
- 你可以在
Ship
模型中添加一个NotMapped
属性-
public class Ship
{
public int Id { get; set; }
public string Name { get; set; }
[NotMapped]
public bool IsEmpty { get; set; }
public List<Passenger> passengers { get; set; }
}
然后像 -
这样查询var ships = dbCtx.Ships
.Select(p => new Ship
{
Id = p.Id,
Name = p.Name,
IsEmpty = !p.Passengers.Any()
})
.ToList();
- 您可以 return 匿名类型 -
var ships = dbCtx.Ships
.Select(p => new
{
Id = p.Id,
Name = p.Name,
IsEmpty = !p.Passengers.Any()
})
.ToList();
如果我理解正确的话...
一种方法是存储每个 Ship
实体内的乘客数量。如果您使用领域驱动设计,将 Ship
视为聚合根,并且仅通过在给定 Ship
实体上公开的方法添加或删除乘客,这会很好地工作,例如RegisterPassenger()
/ RemovePassenger()
。在这些方法中,增加或减少乘客数量以及添加或删除乘客。
然后,很明显,您可以使用 PassengerCount < 0
投影到您需要的布尔值来查询 Ships
数据库集。而且,很明显,它甚至不会触及 Passengers
table.
在传统的贫血域 ASP.NET 系统中,这种数据冗余可能有点冒险,因为属性通常是公开的 mutable,并且您有多个服务 'massage'实体,这是数据完整性丢失的潜在来源。