在Entity Framework Core中使用两列的一对多关系

时间:2021-09-12 20:49:40

In my project I have a table Translation that can have translations for any model. To achieve this, the table has two fields: Model and ModelId. The Model property holds an integer indicating the type of the model and the ModelId has the id of this model.
So, for example: the Product table has modeltype id 1. To get all translations for a product with id 317, I search for translations with Model=1 AND ModelId=317.

在我的项目中,我有一个表格翻译,可以翻译任何模型。为实现此目的,该表有两个字段:Model和ModelId。 Model属性包含一个表示模型类型的整数,ModelId具有此模型的id。因此,例如:Product表具有modeltype id 1.要获取ID为317的产品的所有翻译,我搜索Model = 1 AND ModelId = 317的翻译。

Now I would like to create this relation in Entity Framework Core. All my models inherit from the class BaseModel that has a property ModelType holding the id of the model type. This field is not mapped, so it is not available in the database.

现在我想在Entity Framework Core中创建这种关系。我的所有模型都继承自BaseModel类,该类具有一个属性ModelType,其中包含模型类型的id。此字段未映射,因此在数据库中不可用。

I have tried to create the relation using fluent api, but it doesn't allow me to specify more columns to filter on.

我试图使用流畅的api创建关系,但它不允许我指定更多的列来过滤。

modelBuilder.Entity<BaseModel>()
    .HasMany<Translation>(bm => bm.Translations)
    // Extra filters

Is there any way to create this relation without having to manually create a join for every query that requires translations?

有没有办法创建这种关系,而无需为每个需要翻译的查询手动创建连接?

2 个解决方案

#1


1  

Since modelBuilder.Entity<BaseModel>() will use TPH inheritance approach, I assume you are not using EF code first approach for database creation and you are using it to map your models to an existing database. Then you can try something like this:

由于modelBuilder.Entity ()将使用TPH继承方法,我假设您没有使用EF代码第一种方法来创建数据库,而是使用它来将模型映射到现有数据库。然后你可以尝试这样的事情:

Models:

楷模:

public class Translation
{
    public int Id { get; set; }
    public int Model { get; set; }
    public int ModelId { get; set; }
}

public class BaseModel
{
    public BaseModel(int modelType)
    {
        ModelType = modelType;
    }
    public int Id { get; set; }
    public int ModelType { get; set; }

    public ICollection<Translation> Translations { get; set; }// only for internal use
    public IEnumerable<Translation> ModelTypeTranslations
    {
        get
        {
            return this.Translations.Where(t => t.Model == this.ModelType);
        }
    }

}

public class SomeModel : BaseModel
{
    public SomeModel() : base(1) { }
    public int SomeProperty { get; set; }
}

public class AnotherModel : BaseModel
{
    public AnotherModel() : base(2) { }
    public int AnotherProperty { get; set; }
}

DbContext:

的DbContext:

public class MyDbContext: DbContext
{
    ...

    public DbSet<Translation> Translations { get; set; }
    public DbSet<SomeModel> SomeModels { get; set; }
    public DbSet<AnotherModel> AnotherModels { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        ...
        modelBuilder.Entity<Translation>().HasKey(e => e.Id);

        var baseModelTypes = typeof(BaseModel).Assembly.GetExportedTypes()
            .Where(t => typeof(BaseModel).IsAssignableFrom(t) && t != typeof(BaseModel)).ToList();

        foreach (var type in baseModelTypes)
        {
            modelBuilder.Entity<Translation>().HasOne(type).WithMany(nameof(BaseModel.Translations)).HasForeignKey(nameof(Translation.ModelId));

            modelBuilder.Entity(type).Ignore(nameof(BaseModel.ModelType));
            modelBuilder.Entity(type).Ignore(nameof(BaseModel.ModelTypeTranslations));
            modelBuilder.Entity(type).HasKey(nameof(BaseModel.Id));
        }
    }
}

As you can see you can use ModelTypeTranslations to get Translations only for current model type.

如您所见,您可以使用ModelTypeTranslations仅为当前模型类型获取Translations。

I should note this approach may have performance issues since it filters Translations by ModelType only in memory. Also I tried to avoid filtering in memory by using lazy loading but I got some exception even if I just installed that package without invoking optionsBuilder.UseLazyLoadingProxies(). I hope it will be fixed in the next releases.

我应该注意这种方法可能存在性能问题,因为它只在内存中按模型类型过滤Translations。此外,我试图通过使用延迟加载避免在内存中进行过滤,但即使我刚刚安装了该包而没有调用optionsBuilder.UseLazyLoadingProxies(),我也遇到了一些异常。我希望它将在下一个版本中修复。

#2


0  

Yes, there is. Use this:

就在这里。用这个:

modelBuilder.Entity<BaseModel>()
.HasMany(bm => bm.Translations)
.WithForeignKey(x => new { x.Key1, x.Key2 });

Key1 and Key2 obviously need to be the keys to the relation and you may also need to define them as such (in the same order) for the Translation entity.

Key1和Key2显然需要成为关系的关键,您可能还需要为Translation实体定义它们(按相同的顺序)。

#1


1  

Since modelBuilder.Entity<BaseModel>() will use TPH inheritance approach, I assume you are not using EF code first approach for database creation and you are using it to map your models to an existing database. Then you can try something like this:

由于modelBuilder.Entity ()将使用TPH继承方法,我假设您没有使用EF代码第一种方法来创建数据库,而是使用它来将模型映射到现有数据库。然后你可以尝试这样的事情:

Models:

楷模:

public class Translation
{
    public int Id { get; set; }
    public int Model { get; set; }
    public int ModelId { get; set; }
}

public class BaseModel
{
    public BaseModel(int modelType)
    {
        ModelType = modelType;
    }
    public int Id { get; set; }
    public int ModelType { get; set; }

    public ICollection<Translation> Translations { get; set; }// only for internal use
    public IEnumerable<Translation> ModelTypeTranslations
    {
        get
        {
            return this.Translations.Where(t => t.Model == this.ModelType);
        }
    }

}

public class SomeModel : BaseModel
{
    public SomeModel() : base(1) { }
    public int SomeProperty { get; set; }
}

public class AnotherModel : BaseModel
{
    public AnotherModel() : base(2) { }
    public int AnotherProperty { get; set; }
}

DbContext:

的DbContext:

public class MyDbContext: DbContext
{
    ...

    public DbSet<Translation> Translations { get; set; }
    public DbSet<SomeModel> SomeModels { get; set; }
    public DbSet<AnotherModel> AnotherModels { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        ...
        modelBuilder.Entity<Translation>().HasKey(e => e.Id);

        var baseModelTypes = typeof(BaseModel).Assembly.GetExportedTypes()
            .Where(t => typeof(BaseModel).IsAssignableFrom(t) && t != typeof(BaseModel)).ToList();

        foreach (var type in baseModelTypes)
        {
            modelBuilder.Entity<Translation>().HasOne(type).WithMany(nameof(BaseModel.Translations)).HasForeignKey(nameof(Translation.ModelId));

            modelBuilder.Entity(type).Ignore(nameof(BaseModel.ModelType));
            modelBuilder.Entity(type).Ignore(nameof(BaseModel.ModelTypeTranslations));
            modelBuilder.Entity(type).HasKey(nameof(BaseModel.Id));
        }
    }
}

As you can see you can use ModelTypeTranslations to get Translations only for current model type.

如您所见,您可以使用ModelTypeTranslations仅为当前模型类型获取Translations。

I should note this approach may have performance issues since it filters Translations by ModelType only in memory. Also I tried to avoid filtering in memory by using lazy loading but I got some exception even if I just installed that package without invoking optionsBuilder.UseLazyLoadingProxies(). I hope it will be fixed in the next releases.

我应该注意这种方法可能存在性能问题,因为它只在内存中按模型类型过滤Translations。此外,我试图通过使用延迟加载避免在内存中进行过滤,但即使我刚刚安装了该包而没有调用optionsBuilder.UseLazyLoadingProxies(),我也遇到了一些异常。我希望它将在下一个版本中修复。

#2


0  

Yes, there is. Use this:

就在这里。用这个:

modelBuilder.Entity<BaseModel>()
.HasMany(bm => bm.Translations)
.WithForeignKey(x => new { x.Key1, x.Key2 });

Key1 and Key2 obviously need to be the keys to the relation and you may also need to define them as such (in the same order) for the Translation entity.

Key1和Key2显然需要成为关系的关键,您可能还需要为Translation实体定义它们(按相同的顺序)。