如果我的一个模型有另一个模型的两个集合呢?

huangapple go评论54阅读模式
英文:

What if one of my models has two collections of another model?

问题

以下是翻译好的内容:

一个事件恰好有一个所有者和0到N个主持人。所有者和主持人都是AppUser。用户可以拥有多个事件并主持多个事件。因此我们有:

public class AppUser
{
    public int Id { get; private set; }
    public ICollection<Event>? EventsOwned { get; set; }
    public ICollection<Event>? EventsHosting { get; set; }
}

public class Event
{
    public int Id { get; private set; }
    public AppUser Owner { get; set; }
    public ICollection<AppUser>? Hosts { get; set; }
}

那么... 我该如何将AppUser.EventsOwned关联到Event.Owner以及AppUser.EventsHosting关联到Event.Hosts?是否有一些流畅的命令可以设置明确的关系?

在涉及到删除时,我该如何设置以下情况:

  1. 如果删除了一个事件,它会从EventsOwned和EventsHosting集合中移除该事件,但不会删除AppUser。
  2. 如果删除了一个AppUser,对于EventsHosting中的任何事件,它会从Hosts中移除该用户,但这不会删除相关联的事件。
  3. 如果删除了一个AppUser,它应该级联删除EventsOwned中的事件。

这三个条件是否需要进行设置?

英文:

An event has exactly 1 owner and 0..N hosts. Both owner and host are an AppUser. And a user can own many events and host many events. So we have:

public class AppUser
{
    public int Id { get; private set; }
    public ICollection&lt;Event&gt;? EventsOwned { get; set; }
    public ICollection&lt;Event&gt;? EventsHosting { get; set; }
}

public class Event
{
	public int Id { get; private set; }
	public AppUser Owner { get; set; }
    public ICollection&lt;AppUser &gt;? Hosts { get; set; }

}

So... how do I tie AppUser.EventsOwned to Event.Owner and AppUser.EventsHosting to Event.Hosts? Is there some fluent command that sets the explicit relationship?

And when it comes to deleting, how do I set it that:

  1. If an Event is deleted, while it removes that event from the EventsOwned and EventsHosting collections, it does not delete the AppUser.
  2. If an AppUser is deleted, for any event in EventsHosting, while it removes that user from Hosts, this deletion does not delete the associated Event.
  3. If an AppUser is deleted, it should cascade delete the events in EventsOwned.

Anything I need to set up for these three criteria?

答案1

得分: 1

你需要明确你的关系。例如,所有权看起来是事件和所有者之间的多对一关系,而托管看起来是多对多。

首先是Owner-EventsOwned关系以及如何明确声明外键,因为EF的约定会查找AppUserId而不是OwnerId,因为约定遵循类型名称而不是属性名称。这可以通过FK属性或配置来完成,但考虑到用户具有双向引用,你可能希望明确配置如下:

modelBuilder.Entity<Event>()
    .HasOne(x => x.Owner)
    .WithMany(x => x.EventsOwned)
    .HasForeignKey("OwnerId");

对于Event.Hosts -> User.EventsHosted之间的关联,这也需要配置:

modelBuilder.Entity<Event>()
    .HasMany(x => x.Hosts)
    .WithMany(x => x.EventsHosted);

你可能还想配置关系的连接表。

当涉及到导航属性时,我的建议是只在真正需要从一侧或另一侧引用项目的情况下引入双向引用。通常,对于像这样引用用户的情况,我不会在用户一侧的导航属性上费心,除非我想要执行像投影用户以及所拥有和/或托管的事件数量之类的操作。如果我想要由用户托管或拥有的事件,我总是可以编写如下查询:

var userEvents = await context.Events
    .Where(x => x.OwnerId == userId 
        || x.Hosts.Any(h => h.Id == userId))
    .ToListAsync();

...这通常比通过用户获取事件更灵活:

var user = await context.Users
    .Include(x => x.OwnedEvents)
    .Include(x => x.HostedEvents)
    .SingleAsync(x => x.Id == userId);
var events = user.OwnedEvents.Union(user.HostedEvents).ToList();

编辑: 如果要查看没有双向引用的配置,我们会从用户表中删除EventsOwned和EventsHosted,然后在为所有者和主办方配置关系时,它们会变成如下所示:

modelBuilder.Entity<Event>()
    .HasOne(x => x.Owner)
    .WithMany()
    .HasForeignKey("OwnerId");

modelBuilder.Entity<Event>()
    .HasMany(x => x.Hosts)
    .WithMany();

在Event.Hosts是多对多的情况下,EF将在后台创建一个名为EventHosts的关系表,其中包含EventId和HostId。如果要显式映射此关系表,可以使用以下代码:

modelBuilder.Entity<Event>()
    .HasMany(x => x.Hosts)
    .WithMany()
    .UsingEntity("EventHost",
        l => l.HasOne(typeof(Event))
           .WithMany()
           .HasForeignKey("EventId")
           .HasPrincipalKey(nameof(Event.EventId)),
        r => r.HasOne(typeof(User))
            .WithMany()
            .HasForeignKey("HostId")
            .HasPrincipalKey(nameof(User.UserId)),
        j => j.HasKey("EventId", "HostId"));

有关配置多对多关系选项的详细信息请参阅:https://learn.microsoft.com/en-us/ef/core/modeling/relationships/many-to-many

一个常见的例子,几乎肯定要避免双向引用的情况是跟踪CreatedBy用户引用,其中每个实体都跟踪了一个CreatedBy和LastModifiedBy用户引用。你可能有100个与各种用户相关联的实体,但将它们映射为双向引用将会导致用户表中有200个集合,如OrdersICreated、OrdersIModifiedLast、ProductsICreated等等。

正如前面提到的,双向引用在两个实体可能都能充当聚合根并包含涉及另一个实体的查询条件时很有用。通常情况下,如果默认使用单向引用并仅在实际添加价值的地方添加双向引用,代码会更简洁。

英文:

You will need to be explicit with your relationships. For instance the ownership looks like a many-to-one between event and owner, while the hosting looks like a many-to-many.

The first thing will be the Owner-EventsOwned relationship and how the FK will need to be explicitly declared as EF's conventions would be looking for an AppUserId rather than something like an OwnerId since convention follows the Type name, not the property name. This can be done with the FK attribute or via configuration, but given the bi-directional refence on User you may want to configure this explicitly:

modelBuilder.Entity&lt;Event&gt;()
    .HasOne(x =&gt; x.Owner)
    .WithMany(x =&gt; x.EventsOwned)
    .HasForeignKey(&quot;OwnerId&quot;);

For the association between Event.Hosts -> User.EventsHosted, this will also need configuration:

modelBuilder.Entity&lt;Event&gt;()
    .HasMany(x =&gt; x.Hosts)
    .WithMany(x =&gt; x.EventsHosted);

You may want to also configure the joining table for the relationship here.

When it comes to navigation properties my advice is to only introduce bi-directional references where it really makes sense to reference items from one side or the other equally. Normally for something like this with references to users I wouldn't bother with navigation properties on the User side of things unless I'm going to want to do something like project users with a count of events owned and/or hosted. If I want events hosted or owned by I user I can always write queries like:

var userEvents = await context.Events
    .Where(x =&gt; x.OwnerId == userId 
        || x.Hosts.Any(h =&gt; h.Id == userId)
    .ToListAsync();

... which is often more flexible than getting events via the User:

var user = await context.Users
    .Include(x =&gt; x.OwnedEvents)
    .Include(x =&gt; x.HostedEvents)
    .SingleAsync(x =&gt; x.Id == userId);
var events = user.OwnedEvents.Union(user.HostedEvents).ToList();

Edit: To see what the configuration looks like without bi-directional references we would remove the EventsOwned and EventsHosted from the User table, then when configuring the relationships for the Owner and Hosts, these change to:

modelBuilder.Entity&lt;Event&gt;()
    .HasOne(x =&gt; x.Owner)
    .WithMany()
    .HasForeignKey(&quot;OwnerId&quot;);

modelBuilder.Entity&lt;Event&gt;()
    .HasMany(x =&gt; x.Hosts)
    .WithMany();

In the case of Event.Hosts being a many-to-many, EF will expect/create a joining table behind the scenes likely named EventHosts containing the EventId and HostId. If you want to explicitly map this relationship table, then

modelBuilder.Entity&lt;Event&gt;()
    .HasMany(x =&gt; x.Hosts)
    .WithMany();
    .UsingEntity(&quot;EventHost&quot;,
        l =&gt; l.HasOne(typeof(Event))
           .WithMany()
           .HasForeignKey(&quot;EventId&quot;)
           .HasPrincipalKey(nameof(Event.EventId)),
        r =&gt; r.HasOne(typeof(User))
            .WithMany()
            .HasForeignKey(&quot;HostId&quot;)
            .HasPrincipalKey(nameof(User.UserId)),
        j =&gt; j.HasKey(&quot;EventId&quot;, &quot;HostId&quot;));

Details about configuring options for many-to-many relationships are outlined here: https://learn.microsoft.com/en-us/ef/core/modeling/relationships/many-to-many

A common example where you almost certainly want to avoid bi-directional references would be something like tracking a CreatedBy user reference where every entity tracks a CreatedBy and LastModifiedBy reference to a user. You might have 100 entities associated to various users, but mapping those as bi-directional references would have a user table with 200 collections for OrdersICreated, OrdersIModifiedLast, ProductsICreated, ... etc.

As mentioned, a bidirectional reference is useful when both entities might equally share serving as an aggregate root and contain query conditions involving the other. Things are typically kept a lot simpler when defaulting to unidirectional references and adding bi-direction only where it adds practical value.

huangapple
  • 本文由 发表于 2023年7月7日 08:09:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/76633191.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定