简体   繁体   English

实体框架查询多个 inheritance 共享属性

[英]Entity Framework query on multiple inheritance shared properties

I have a table using TPH in Entity Framework Core.我在 Entity Framework Core 中有一个使用 TPH 的表。 I've created a simple example to transmit the idea using animals, for example a Dog and a Dolphin are mammals and a Dolphin and a Salmon are marine animals, but a Dog is not marine and a Salmon is not a mammal, it's a fish.我创建了一个简单的例子来使用动物来传达这个想法,例如狗和海豚是哺乳动物,海豚和鲑鱼是海洋动物,但是狗不是海洋动物,鲑鱼不是哺乳动物,它是鱼.

You can see here some code:你可以在这里看到一些代码:

public interface IMammal
{
    public int NeocortexSize { get; set; }
}

public interface IFish
{
    public int GillSize { get; set; }
}

public interface IMarine
{
    public int MaxDepth { get; set; }
}
public abstract class Animal
{
    protected Animal(AnimalType animalType)
    {
        Type = animalType;
    }

    public AnimalType Type { get; }
}

public class Dog : Animal, IMammal
{
    public Dog() : base(AnimalType.Dog) {}

    public int NeocortexSize { get; set; }
}

public class Salmon: Animal, IFish, IMarine
{
    public Salmon() : base(AnimalType.Salmon) {}

    public int GillSize { get; set; }

    public int MaxDepth { get; set; }
}

public class Dolphin : Animal, IMammal, IMarine
{
    public Dolphin() : base(AnimalType.Dolphin) {}

    public int NeocortexSize { get; set; }

    public int MaxDepth { get; set; }
}

My question is on how to query for things that are on an interface, for example MaxDepth > 650 , because the only way that I've found to do it feels a little bit hacky.我的问题是关于如何查询界面上的东西,例如MaxDepth > 650 ,因为我发现这样做的唯一方法感觉有点老套。 You just cast to some concrete animal that implements the interface.您只需转换为实现接口的具体动物。 From reading the code, it looks that you would only get Dolphin , but it will return Dolphin and Salmon at runtime (which is fine, it's what I want).通过阅读代码,您似乎只会得到Dolphin ,但它会在运行时返回DolphinSalmon (这很好,这就是我想要的)。

// Hacky way that works
// You get a `List<Animal>` with `Salmon` and `Dolphin` inside
context.Animals.Where(animal => (animal as Dolphin).MaxDepth > 650).ToList()

// runtime error because LINQ to sql doesn't know how to translate
context.Animals.Where(animal => (animal as IMarine).MaxDepth > 650)
context.Animals.Cast<IMarine>.Where(marine => marine.MaxDepth > 650)

Is there a better way to do it?有更好的方法吗?

I would do我会做

context.Animals.Where(animal => (animal is IMarine) && (animal as IMarine).MaxDepth > 650) 

This way you avoid runtime error, as you check if animal is marine first, so you will not try to treat something that isn't IMarine as IMarine.这样可以避免运行时错误,因为您首先检查动物是否是海洋动物,因此您不会尝试将不是 IMarine 的东西视为 IMarine。

Or even better:或者更好:

    context.Animals.OfType<IMarine>().Where(marine => marine.MaxDepth > 650) 

An interface is not an implementation.接口不是实现。 It's just a contract, ie a "promise" that something will be available or "requirement" that something should be implemented.这只是一个合同,即“承诺”某些东西将可用或“要求”某些东西应该被实施。

By manually setting the column name, you are forcing something that is not supposed to be like that.通过手动设置列名,您会强制执行不应该那样的操作。 That fact that your "hacky" query works is a result of that.您的“hacky”查询有效的事实就是这样的结果。 But you are actually asking for problems, because EF now internally sets all objects in the list to be of type Dolphin .但是您实际上是在问问题,因为 EF 现在在内部将列表中的所有对象设置为Dolphin类型。 That could cause exceptions when you cast the objects to that type (which would be legal).当您将对象转换为该类型(这是合法的)时,这可能会导致异常。

Interfaces are not entities.接口不是实体。 Ie by casting to an interface EF cannot distinguish an object of a specific type, which it needs for the relational mapping.即通过强制转换为接口,EF 无法区分特定类型的 object,而这是关系映射所需的。 Interfaces do not represent inheritance: there's no "is a" relationship.接口不代表 inheritance:没有“是”关系。 It's more a "guarantees these methods and properties", but EF cannot work with those.它更像是“保证这些方法和属性”,但 EF 不能使用这些方法和属性。

You could get the specific rows in your current setup using raw SQL queries, Eg Dapper您可以使用原始 SQL 查询获取当前设置中的特定行,例如 Dapper

    using (var connection = new SqlConnection(connString))
    {
        var animals = connection.Query<Animal>(
            "SELECT * FROM Animals WHERE MaxDepth > 650");
        ... do stuff with animals
    }

C# doesn't have multiple inheritance, and interfaces don't work, so you'll have to find another solution. C#没有多个inheritance,接口也不行,只能另寻他法了。 You can for instance think of composition with "abilities".例如,您可以考虑具有“能力”的构图。 Ie a Fish 'has a' SwimmingAbility that 'has a' MaxDepth property.Fish “具有” SwimmingAbility ,“具有” MaxDepth属性。 You would distribute data over multiple tables.您会将数据分布在多个表上。 And the "ability" tables will only have rows related to a few specific animals.而“能力”表将只有与少数特定动物相关的行。

If you think about that, it will also give a cleaner solution, because else you would get a gigantic single table with many many columns for all properties of subspecies.如果你考虑一下,它也会提供一个更清晰的解决方案,因为否则你会得到一个巨大的单一表格,其中包含亚种所有属性的许多列。 And most columns will be NULL for a specific animal.对于特定动物,大多数列将是NULL

The downside is additional time required to fetch relational data from separate tables.缺点是需要额外的时间从单独的表中获取关系数据。

By the way, if you use a document-based database (NoSql) this will be easier.顺便说一句,如果您使用基于文档的数据库 (NoSql),这会更容易。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM