gpt4 book ai didi

c# - 我可以在 EF Core 中使用带有外键的接口(interface)并使用 Fluent API 将其设置为外键吗?

转载 作者:太空宇宙 更新时间:2023-11-03 22:43:15 25 4
gpt4 key购买 nike

我正在尝试限制几个 generic只允许使用的方法 Entitiesinherit来自 IParentOf<TChildEntity> interface ,以及访问 Entity's Foreign Key (父 ID)Generically .

演示;

public void AdoptAll<TParentEntity, TChildEntity>(TParentEntity parent,
TParentEntity adoptee)
where TParentEntity : DataEntity, IParentOf<TChildEntity>
where TChildEntity : DataEntity, IChildOf<TParentEntity>
{
foreach (TChildEntity child in (IParentOf<TChildEntity>)parent.Children)
{
(IChildOf<TParentEntity)child.ParentId = adoptee.Id;
}
}

子实体类模型看起来像这样,

public class Account : DataEntity, IChildOf<AccountType>, IChildOf<AccountData>
{
public string Name { get; set; }

public string Balance { get; set; }

// Foreign Key and Navigation Property for AccountType
int IChildOf<AccountType>.ParentId{ get; set; }
public virtual AccountType AccountType { get; set; }

// Foreign Key and Navigation Property for AccountData
int IChildOf<AccountData>.ParentId{ get; set; }
public virtual AccountData AccountData { get; set; }
}

首先,这可能吗?或者它会在 EF 中崩溃吗?

其次,由于外键不遵循约定(而且有多个),我该如何通过 Fluent Api 设置它们?我可以在数据注释中看到如何执行此操作。

我希望这是清楚的,我已经考虑了一段时间并试图解决它,所以我可以遵循我的论点,但它可能没有被清楚地传达,所以如果需要请寻求澄清。我想要这样做的原因是为了使代码安全以及自动化大量手动更改添加新关联和实体所需的类。

谢谢。

编辑

我决定创建一些基础类来实现这个想法并进行测试,我的代码如下。

public abstract class ChildEntity : DataEntity
{
public T GetParent<T>() where T : ParentEntity
{
foreach (var item in GetType().GetProperties())
{
if (item.GetValue(this) is T entity)
return entity;
}

return null;
}
}

public abstract class ParentEntity : DataEntity
{
public ICollection<T> GetChildren<T>() where T : ChildEntity
{
foreach (var item in GetType().GetProperties())
{
if (item.GetValue(this) is ICollection<T> collection)
return collection;
}

return null;
}
}

public interface IParent<TEntity> where TEntity : ChildEntity
{
ICollection<T> GetChildren<T>() where T : ChildEntity;
}

public interface IChild<TEntity> where TEntity : ParentEntity
{
int ForeignKey { get; set; }

T GetParent<T>() where T : ParentEntity;
}

public class ParentOne : ParentEntity, IParent<ChildOne>
{
public string Name { get; set; }
public decimal Amount { get; set; }

public virtual ICollection<ChildOne> ChildOnes { get; set; }
}

public class ParentTwo : ParentEntity, IParent<ChildOne>
{
public string Name { get; set; }
public decimal Value { get; set; }

public virtual ICollection<ChildOne> ChildOnes { get; set; }
}

public class ChildOne : ChildEntity, IChild<ParentOne>, IChild<ParentTwo>
{
public string Name { get; set; }
public decimal Balance { get; set; }

int IChild<ParentOne>.ForeignKey { get; set; }
public virtual ParentOne ParentOne { get; set; }

int IChild<ParentTwo>.ForeignKey { get; set; }
public virtual ParentTwo ParentTwo { get; set; }
}

Data Entity简单地给出每个 entity一个Id property .

我设置了标准的通用存储库,其中包含用于中介的工作单元类。 AdoptAll 方法在我的程序中看起来像这样。

public void AdoptAll<TParentEntity, TChildEntity>(TParentEntity parent,
TParentEntity adoptee, UoW uoW)
where TParentEntity : DataEntity, IParent<TChildEntity>
where TChildEntity : DataEntity, IChild<TParentEntity>
{
var currentParent = uoW.GetRepository<TParentEntity>().Get(parent.Id);
foreach (TChildEntity child in currentParent.GetChildren<TChildEntity>())
{
child.ForeignKey = adoptee.Id;
}
}

这似乎工作正常并且没有错误(最小测试)这样做有什么重大缺陷吗?

谢谢。

编辑二

这是DbContext中的OnModelCreating方法,为每个实体设置外键。这有问题吗?

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);

modelBuilder.Entity<ChildOne>()
.HasOne(p => p.ParentOne)
.WithMany(c => c.ChildOnes)
.HasForeignKey(fk => ((IChild<ParentOne>)fk).ForeignKey);

modelBuilder.Entity<ChildOne>()
.HasOne(p => p.ParentTwo)
.WithMany(c => c.ChildOnes)
.HasForeignKey(fk => ((IChild<ParentTwo>)fk).ForeignKey);
}

最佳答案

根据更新的示例,您希望从实体类公共(public)接口(interface)中隐藏显式 FK,但仍然让它对 EF Core 可见并映射到数据库中的 FK 列。

第一个问题是显式实现的接口(interface)成员不能直接被 EF 发现。此外,它没有好的名称,因此默认约定不适用。

例如,没有流畅配置的 EF Core 将正确地在 Parent 之间创建一对多关联。和 Child实体,但因为它不会发现 int IChild<Parent>.ForeignKey { get; set; }属性,它将通过 ParentOneId 维护 FK 属性值/ParentTwoId shadow properties而不是通过接口(interface)显式属性。换句话说,这些属性不会由 EF Core 填充,也不会被更改跟踪器考虑。

要让 EF Core 使用它们,您需要分别使用 HasForeignKey 映射 FK 属性和数据库列名。和 HasColumnName流畅的 API 方法重载接受 string属性名称。请注意,字符串属性名称必须完全符合命名空间。同时 Type.FullName为非泛型类型提供该字符串,没有像 IChild<ParentOne> 这样的泛型类型的属性/方法(结果必须是 "Namespace.IChild<Namespace.ParentOne>" ),所以让我们首先为此创建一些助手:

static string ChildForeignKeyPropertyName<TParent>() where TParent : ParentEntity
=> $"{typeof(IChild<>).Namespace}.IChild<{typeof(TParent).FullName}>.{nameof(IChild<TParent>.ForeignKey)}";

static string ChildForeignKeyColumnName<TParent>() where TParent : ParentEntity
=> $"{typeof(TParent).Name}Id";

下一步是创建一个辅助方法来执行必要的配置:

static void ConfigureRelationship<TChild, TParent>(ModelBuilder modelBuilder)
where TChild : ChildEntity, IChild<TParent>
where TParent : ParentEntity, IParent<TChild>
{
var childEntity = modelBuilder.Entity<TChild>();

var foreignKeyPropertyName = ChildForeignKeyPropertyName<TParent>();
var foreignKeyColumnName = ChildForeignKeyColumnName<TParent>();
var foreignKey = childEntity.Metadata.GetForeignKeys()
.Single(fk => fk.PrincipalEntityType.ClrType == typeof(TParent));

// Configure FK column name
childEntity
.Property<int>(foreignKeyPropertyName)
.HasColumnName(foreignKeyColumnName);


// Configure FK property
childEntity
.HasOne<TParent>(foreignKey.DependentToPrincipal.Name)
.WithMany(foreignKey.PrincipalToDependent.Name)
.HasForeignKey(foreignKeyPropertyName);
}

如您所见,我正在使用 EF Core 提供的元数据服务来查找相应导航属性的名称。

但这种泛型方法其实也显示了这种设计的局限性。通用约束允许我们使用

childEntity.Property(c => c.ForeignKey)

编译很好,但在运行时不起作用。它不仅适用于流畅的 API 方法,而且基本上适用于任何涉及表达式树的通用方法(如 LINQ to Entities 查询)。当使用公共(public)属性隐式实现接口(interface)属性时,不存在此问题。

我们稍后会回到这个限制。要完成映射,请将以下内容添加到您的 OnModelCreating覆盖:

ConfigureRelationship<ChildOne, ParentOne>(modelBuilder);
ConfigureRelationship<ChildOne, ParentTwo>(modelBuilder);

现在 EF Core 将正确加载/考虑您明确实现的 FK 属性。

现在回到限制。使用像您的 AdoptAll 这样的通用对象服务没有问题。方法或 LINQ to Objects。但是您不能在用于访问 EF Core 元数据的表达式中或在 LINQ to Entities 查询中访问这些属性。在后一种情况下,您应该通过导航属性访问它,或者在这两种情况下,您都应该通过从 ChildForeignKeyPropertyName<TParent>() 返回的名称访问它。方法。实际上查询会起作用,但会被评估 locally从而导致性能问题或意外行为。

例如

static IEnumerable<TChild> GetChildrenOf<TChild, TParent>(DbContext db, int parentId)
where TChild : ChildEntity, IChild<TParent>
where TParent : ParentEntity, IParent<TChild>
{
// Works, but causes client side filter evalution
return db.Set<TChild>().Where(c => c.ForeignKey == parentId);

// This correctly translates to SQL, hence server side evaluation
return db.Set<TChild>().Where(c => EF.Property<int>(c, ChildForeignKeyPropertyName<TParent>()) == parentId);
}

简而言之,这是可能的,但要小心使用,并确保它在它允许的有限通用服务场景中是值得的。替代方法不使用接口(interface),而是使用 EF Core 元数据、反射或 Func<...>(的组合)/Expression<Func<..>>类似于 Queryable 的通用方法参数扩展方法。

编辑:关于第二个问题编辑,流畅的配置

modelBuilder.Entity<ChildOne>()
.HasOne(p => p.ParentOne)
.WithMany(c => c.ChildOnes)
.HasForeignKey(fk => ((IChild<ParentOne>)fk).ForeignKey);

modelBuilder.Entity<ChildOne>()
.HasOne(p => p.ParentTwo)
.WithMany(c => c.ChildOnes)
.HasForeignKey(fk => ((IChild<ParentTwo>)fk).ForeignKey);

ChildOne 生成以下迁移

migrationBuilder.CreateTable(
name: "ChildOne",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
ForeignKey = table.Column<int>(nullable: false),
Name = table.Column<string>(nullable: true),
Balance = table.Column<decimal>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ChildOne", x => x.Id);
table.ForeignKey(
name: "FK_ChildOne_ParentOne_ForeignKey",
column: x => x.ForeignKey,
principalTable: "ParentOne",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_ChildOne_ParentTwo_ForeignKey",
column: x => x.ForeignKey,
principalTable: "ParentTwo",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});

注单ForeignKey列并尝试将其用作 ParentOne 的外键和 ParentTwo .它遇到与直接使用受限接口(interface)属性相同的问题,因此我认为它不起作用。

关于c# - 我可以在 EF Core 中使用带有外键的接口(interface)并使用 Fluent API 将其设置为外键吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51211301/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com