如何使用 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 模型 -

  1. 你可以在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();
  1. 您可以 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'实体,这是数据完整性丢失的潜在来源。