我正在使用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;
}
}
它大部分时间都有效,但有时候我必须真正解开如何访问某些属性并且我并不完全满足。所以我有这种感觉,我没有做到这一点。
我希望你理解我的愿景,也许有人在类似的问题上挣扎。感谢先进的帮助!
答案 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
消除了堆栈跟踪。)