[英]In Entity Framework Core, if I define these relationships, will it create a cyclic relationship between two entities?
I have an event scheduling app with a SQL Server database.我有一个带有 SQL Server 数据库的事件调度应用程序。 It is running as part of an ASP.NET web app using Entity Framework Core.它作为使用 Entity Framework Core 的 ASP.NET Web 应用程序的一部分运行。 It has the following tables/entities:它具有以下表/实体:
User
: may join many events as a participant User
:可以作为参与者参加许多活动
Event
: may have many users as participants. Event
:可能有许多用户作为参与者。 It's also created by a single user它也是由单个用户创建的
This many-to-many relationship can be resolved using a join table: UserEvent
.这种多对多关系可以使用连接表来解决: UserEvent
。
I end up with this relationship diagram.我最终得到了这张关系图。
However, my problem is that I am not sure that it is okay if a single User
is linked to a single Event
twice, both as a participant and as a creator.但是,我的问题是我不确定如果单个User
作为参与者和创建者两次链接到单个Event
是否可以。 The creator of the event would have their ID stored as a FK in the Event
they created and they would have their ID stored as a FK in the UserEvent
that registers them as a participant of the Event
.事件的创建者会将他们的 ID 作为 FK 存储在他们创建的Event
中,他们会将他们的 ID 作为 FK 存储在将他们注册为Event
参与者的UserEvent
中。 The app assumes if they create the event, they are attending it.该应用程序假定如果他们创建了活动,他们就会参加。
Is there a better way to configure this relationship?有没有更好的方法来配置这种关系?
When I try to add the migration I get the following error:当我尝试添加迁移时,出现以下错误:
Introducing FOREIGN KEY constraint 'FK_Events_MyUsers_CreatorID' on table 'Events' may cause cycles or multiple cascade paths.在表“事件”上引入外键约束“FK_Events_MyUsers_CreatorID”可能会导致循环或多个级联路径。 Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.指定 ON DELETE NO ACTION 或 ON UPDATE NO ACTION,或修改其他 FOREIGN KEY 约束。
Here is an example of my code:这是我的代码示例:
User
model: User
模型:
public class User
{
public User() {
UserName = "Default";
}
public long ID { get; set; }
public string UserName { get; set; }
public virtual List<Event>? UserEvents { get; set; }
}
Event
model: Event
模型:
public class Event
{
public Event()
{
EventName = "Default Event Name";
}
public long ID { get; set; }
public long CreatorID { get; set; }
public User Creator { get; set; }
public virtual List<User> EventUsers { get; set; }
public string EventName { get; set; }
}
Override of OnModelCreating
method:重写OnModelCreating
方法:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<User>(
b => {
b.Property(user => user.ID)
.IsRequired();
b.Property(user => user.UserName)
.IsRequired();
b.HasKey(user => user.ID);
b.HasMany(user => user.UserEvents)
.WithMany(e => e.EventUsers)
.UsingEntity(join => join.ToTable("UserEvents"));
b.ToTable("MyUsers");
});
builder.Entity<Event>(
b => {
b.Property(e => e.ID)
.IsRequired();
b.Property(e => e.EventName)
.IsRequired();
b.Property(e => e.CreatorID)
.IsRequired();
b.HasKey(e => e.ID);
b.HasMany(e => e.EventUsers)
.WithMany(user => user.UserEvents)
.UsingEntity(join => join.ToTable("UserEvents"));
b.HasOne(e => e.Creator)
.WithMany();
b.ToTable("Events");
});
}
When I remove the CreatorID
and Creator
columns from the Event
table, it migrates fine.当我从Event
表中删除CreatorID
和Creator
列时,它可以正常迁移。
How can I reorganize my relationships to get allow events to have a Creator
field and/or track which User
created an Event
?我如何重新组织我的关系以允许事件具有Creator
字段和/或跟踪哪个User
创建了Event
?
@SNBS Deserves all credit for the answer. @SNBS 值得所有的答案。 See their post below.请参阅下面的帖子。 I had to tweak their code to get it to compile.我不得不调整他们的代码才能编译。
To avoid the relationship detailed in the question it was necessary to add a BIT field to the UserEvent
table to represent the user "Role" within that event.为了避免问题中详述的关系,有必要向UserEvent
表添加一个 BIT 字段以表示该事件中的用户“角色”。
To ensure that an event could not have more than one creator I had to add a custom SQL function and enforce it as a constraint on the UserEvent
table.为确保一个事件不能有多个创建者,我必须添加自定义 SQL 函数并将其强制执行为UserEvent
表的约束。
This explanation assumes you're using Visual Studio.此说明假定您使用的是 Visual Studio。
If you are new to SQL Server like me you might wonder how to add the function.如果您像我一样不熟悉 SQL Server,您可能想知道如何添加该函数。 In visual studio within the SQL Server Object Explorer.在 SQL Server 对象资源管理器中的 visual studio 中。 Right click on: Programmability->Functions -> Add New Scalar Valued Function
.右键单击: Programmability->Functions -> Add New Scalar Valued Function
。
After writing the code in the new window make sure to click "Update Database" and check your code compiles.在新窗口中编写代码后,请确保单击“更新数据库”并检查代码是否编译。 If it works you should be able to see you new function in the explorer.如果它有效,您应该能够在资源管理器中看到您的新功能。
CREATE FUNCTION CheckOneCreator (@EventId BIGINT)
RETURNS BIT
AS
BEGIN
-- Create and open a cursor
DECLARE Participants CURSOR
FOR
SELECT UserID
FROM UserEvents
WHERE EventID = @EventId;
OPEN Participants;
-- Creators count
DECLARE @CreatorsCount INT;
SELECT @CreatorsCount = 0;
-- Iterate over participants
DECLARE @ParticipantId BIGINT;
FETCH NEXT FROM Participants
INTO @ParticipantId;
WHILE @@FETCH_STATUS = 0
BEGIN
-- Check whether the participant is the creator
DECLARE @IsCreator BIT;
SELECT @IsCreator = (
SELECT TOP 1 IsCreator
FROM UserEvents
WHERE UserID = @ParticipantId
AND EventID = @EventId
);
-- Increase creators count
IF @IsCreator = 1 SELECT @CreatorsCount = (@CreatorsCount + 1);
FETCH NEXT FROM Participants
INTO @ParticipantId;
END
IF @CreatorsCount = 1 RETURN 1;
RETURN 0;
END
To add this function as a constraint right click on your database in the SQL Object Explorer in Visual Studio and select New Object.要将此函数添加为约束,请在 Visual Studio 的 SQL 对象资源管理器中右键单击您的数据库,然后选择新建对象。 A code window will open.将打开一个代码窗口。 Once you type the code in again ensure you click "Update Database" and see if your code compiles.再次输入代码后,确保单击“更新数据库”并查看代码是否编译。 If successful you will see the constraint added under the table.如果成功,您将在表格下方看到添加的约束。
ALTER TABLE [dbo].[UserEvents]
ADD CONSTRAINT [SingleCreator] CHECK ([dbo].[CheckOneCreator]([EventID])=(1));
I think it would be better if each user had a role (creator or general participant) in all events he takes part in. You may create a new column in table UserEvent
and name it IsCreator
(data type — bit
, which is the SQL equivalent of .NET type bool
).我认为如果每个用户在他参与的所有事件中都有一个角色(创建者或一般参与者)会更好。您可以在表UserEvent
中创建一个新列并将其命名为IsCreator
(数据类型 - bit
,这是 SQL 等效项.NET 类型bool
)。 This column should contain 0 for general participants and 1 for creators.此列应包含 0(代表一般参与者)和 1(代表创作者)。
UPDATE : a constraint is necessary to check that an event can have only one creator.更新:需要一个约束来检查事件是否只能有一个创建者。 Firstly, create a function in your database using this SQL code:首先,使用以下 SQL 代码在您的数据库中创建一个函数:
CREATE FUNCTION CheckOneCreator (@EventId BIGINT)
RETURNS BIT
AS
BEGIN
-- Create and open a cursor
DECLARE Participants CURSOR
FOR
SELECT UserId
FROM UserEvent
WHERE EventId = @EventId;
OPEN Participants;
-- Creators count
DECLARE @CreatorsCount INT;
SELECT @CreatorsCount = 0;
-- Iterate over participants
DECLARE @ParticipantId BIGINT;
FETCH NEXT FROM Participants
INTO @ParticipantId;
WHILE @@FETCH_STATUS = 0
BEGIN
-- Check whether the participant is the creator
DECLARE @IsCreator BIT;
SELECT @IsCreator = (
SELECT TOP 1 IsCreator
FROM UserEvent
WHERE UserId = @ParticipantId
AND EventId = @EventId
);
-- Increase creators count
IF @IsCreator SELECT @CreatorsCount = (@CreatorsCount + 1);
FETCH NEXT FROM Participants
INTO @ParticipantId;
END
IF @CreatorsCount != 1 RETURN 0;
ELSE RETURN 1;
END
And define a constraint for table UserEvent
:并为表UserEvent
定义一个约束:
ALTER TABLE UserEvent
ADD CONSTRAINT CHECK (CheckOneCreator(EventId) = 1);
PS Use this function only if you really don't want a user to be linked twice (because the queries take time). PS 仅当您确实不希望用户被链接两次时才使用此功能(因为查询需要时间)。 And create an index for columns UserId
and EventId
in table UserEvent
.并为表UserEvent
中的列UserId
和EventId
创建索引。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.