【问题标题】:Casting child class to parent class. Am I doing it in right way?将子类转换为父类。我是否以正确的方式做这件事?
【发布时间】:2018-01-26 17:50:06
【问题描述】:

我正在使用 ASP.NET Core 和带有 MySQL 的 EF Core 制作休息服务器。

数据库中有两个表 - 建筑物和建筑物参数。每种建筑类型都有不同的变量,所以我决定用这种方式解决这个问题。

所以有模型Building,但也有很多继承自Building 的建筑类。一个小例子:

 [BuildingType(EBuildingType.Stable)]
    public class Building_Stable : Building
    {
        public int Slots
        {
            get
            {
                return GetArgumentValueOrDefault<int>("Slots");
            }
            set
            {
                UpdateOrCreateArgument("Slots", value);
            }
        }

    }

我可以访问所有 Building 属性,并且可以以非常愉快的方式获取和设置其他属性(数据库中的参数)。当我想从我的存储库中构建时,我正在这样做:

   public async Task<TBuilding> GetByType<TBuilding>(User user) where TBuilding : Building
    {
        try
        {
            if (user == null) return null;

            EBuildingType? type;
            type = _buildingTypesCollection.GetType(typeof(TBuilding));
            if (type == null) return null;

            Building uBuilding = await _dbContext.Buildings.Where(b => b.Type == type && b.OwnerId == user.Id).Include(p => p.Arguments).FirstOrDefaultAsync();

            var parent = JsonConvert.SerializeObject(uBuilding);
            TBuilding c = JsonConvert.DeserializeObject<TBuilding>(parent);
            return c;

        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

它在大多数情况下都有效,但有时我不得不对如何访问某些属性感到非常困惑,而且我并不完全满足。所以我有这种感觉,我做的不对。

【问题讨论】:

  • 我猜你正在使用 EF TPH 继承,Type 属性是鉴别器?

标签: c# entity-framework asp.net-core entity-framework-core


【解决方案1】:

如果这是通过 EF Core TPH inheritance strategy 实现的,Type鉴别器,则无需直接使用鉴别器。 EF Core查询具体派生类型的方式是使用OfType方法:

if (user == null) return null;

TBuilding c = await _dbContext.Buildings
    .Include(p => p.Arguments)
    .OfType<TBuilding>() // <--
    .Where(b => b.OwnerId == user.Id) // <-- no type filter
    .FirstOrDefaultAsync();

return c;

【讨论】:

    【解决方案2】:

    首先,如果您使用Set&lt;T&gt; 而不是像Buildings 这样的命名DbSet 属性,您的生活会更轻松。无论建筑物的实际类型如何,这只会返回Building 的实例。但是,由于您已经在使用特定 TBuilding 类型的通用方法中,那么您实际上可以这样做:

    var building = await _dbContext.Set<TBuilding>().Where(b.OwnerId == user.Id).Include(p => p.Arguments).FirstOrDefaultAsync();
    

    您应该保留一个单独的Type 属性,因为您正在添加一个故障点。如果没有正确设置,即使建筑物可能是正确的类型,您的部分代码也会失败。如果你正确使用泛型(就像上面的Set&lt;T&gt;),你可以完成几乎所有的事情,而不需要知道具体的类型。

    现在,一个限制是泛型方法的类型约束(可能还有类似的方法)。这不是一件坏事,它实际上应该在那里。但是,如果本质上将TBuilding 向上转换为Building,则意味着您只能访问Building 的成员,而不能访问更具体的派生类型。如果需要访问特定派生类型的成员,可以使用 switch 进行模式匹配(需要 C# 7+):

    switch (building)
    {
        case Building_Stable stable:
            // `stable` is now a declared variable of type `Building_Stable`
            // You can use this variable to work with specific members of this type
            break;
    }
    

    【讨论】:

      【解决方案3】:

      我最近遇到了这个确切的实现:

      var parent = JsonConvert.SerializeObject(uBuilding);
      TBuilding c = JsonConvert.DeserializeObject<TBuilding>(parent);
      

      假设您用于TBuilding 的通用参数是Building,但您的存储库返回一个SkyScraper,它继承自Building。随后的序列化/反序列化可能会产生意想不到的影响。

      将对象作为其基本类型显然对对象本身没有影响。但是序列化对象然后将其反序列化为其基本类型并不是强制转换。它返回一个基本类型的新对象。所以一旦它被序列化和反序列化,你得到的结果就不再是Skyscraper

      JsonConvert.DeserializeObject&lt;TBuilding&gt; 将反序列化 Building 所需的所有内容,并忽略 Skyscraper 的其他属性。所以你得到的只是Building

      要解决该问题,请稍作更改:

      var parent = JsonConvert.SerializeObject(uBuilding);
      TBuilding c = JsonConvert.DeserializeObject(parent, typeof(uBuilding))
      

      现在反序列化时将考虑原始类型。结果将被转换为Building,但其实际类型将是Skyscraper


      您通过序列化和反序列化对象以创建副本所做的操作称为“克隆”。您将创建一个与原始对象具有相同属性的新对象,而不是获取对原始对象的引用。

      这样做是有正当理由的,但你没有在这里提到你这样做的原因。您是否需要,或者您是否可以完全跳过该步骤并将方法简化为:

      public async Task<TBuilding> GetByType<TBuilding>(User user) where TBuilding : Building
      {
          if (user == null) return null;
      
          EBuildingType? type;
          type = _buildingTypesCollection.GetType(typeof(TBuilding));
          if (type == null) return null;
      
          Building uBuilding = await _dbContext.Buildings.Where(b => b.Type == type && b.OwnerId == user.Id).Include(p => p.Arguments).FirstOrDefaultAsync();
      
          return uBuilding;
       }
      

      无论TBuilding 是什么,结果可能实际上不是那种类型 - 它是从它继承的东西。但这没关系 - 这就是它应该如何工作的方式。如果您正在寻找 Building 并得到 Skyscraper,那没关系,因为 Skyscraper Building

      catch ex/throw ex 是不必要的 - 它实际上比没有异常处理更糟糕,因为throw ex 会清除堆栈跟踪。)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-01-30
        相关资源
        最近更新 更多