将子类转换为父类。我是以正确的方式做到的吗?

时间:2018-01-26 17:50:07

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

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

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

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

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

    }

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

   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;
        }
    }

它大部分时间都有效,但有时候我必须真正解开如何访问某些属性并且我并不完全满足。所以我有这种感觉,我没有做到这一点。

我希望你理解我的愿景,也许有人在类似的问题上挣扎。感谢先进的帮助!

3 个答案:

答案 0 :(得分:4)

如果通过EF Core TPH inheritance strategy实现,[ { id: "0901d63880504cab", repositoryObjectName: "0b01d63880504b29", repositoryType: "dcp_collection_structure", modifiedDate: "1/16/2018 2:12:57 PM", createdDate: "1/5/2018 3:49:52 PM", size: "502" }, { id: "0b01d63880504bc9", repositoryObjectName: "10 Guide Info", repositoryType: "dcp_folder", modifiedDate: "1/5/2018 3:12:04 PM", createdDate: "1/5/2018 3:12:04 PM", size: "0" } ] 鉴别器,则无需直接使用鉴别器。查询特定派生类型的EF Core方法是使用OfType方法:

Type

答案 1 :(得分:1)

首先,如果您使用Set<T>而非DbSet之类的Buildings属性,则会让您的生活更轻松。无论建筑物的实际类型如何,这只会返回Building的实例。但是,由于您已经在使用特定TBuilding类型的泛型方法中,因此您实际上可以这样做:

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

您应该保留单独的Type属性,因为您正在添加一个失败点。如果未正确设置,即使构建的类型正确,代码的某些部分也会失败。如果您正确使用泛型(如上面的Set<T>),您可以完成所有操作而无需了解特定类型。

现在,一个限制是泛型方法的类型约束(可能是类似的)。这不是一件坏事,实际上应该在那里。但是,如果基本上将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;
}

答案 2 :(得分:1)

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

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

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

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

JsonConvert.DeserializeObject<TBuilding>将反序列化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消除了堆栈跟踪。)