Введение ограничения FOREIGN KEY может привести к циклам или нескольким каскадным путям. Указать без удаления действия
Пользователь, работодатель, кандидаты и Job, работодатель может создавать несколько заданий, и каждое задание может иметь только одного работодателя, кандидат может подать заявку на многие задания, и каждое задание может иметь несколько примененных членов.
Итак, соотношение выглядит так:
![enter image description here]()
Я использую первый подход сущностного кода, на данный момент, если я удалю работодателя, он удалит все связанные задания и пользователя из базы данных, и если я удалю кандидата, он удалит пользователя:
modelBuilder.Entity<Employer>()
.HasRequired(e => e.User)
.WithOptional(e => e.Employer).WillCascadeOnDelete();
//member is candidate
modelBuilder.Entity<Member>()
.HasRequired(e => e.User)
.WithOptional(e => e.Member).WillCascadeOnDelete();
modelBuilder.Entity<Employer>()
.HasMany(a => a.Jobs)
.WithRequired(b => b.Employer)
.WillCascadeOnDelete();
Все работает отлично, за исключением случаев, когда я указываю много-много отношений между кандидатами и заданием и обновляю базу данных с помощью "update-database", она дает мне эту ошибку:
Представление ограничения FOREIGN KEY "FK_dbo.MemberJobMap_dbo.Jobs_JobId" в таблице "MemberJobMap" может вызывать циклы или несколько каскадных путей. Укажите ON DELETE NO ACTION или ON UPDATE NO ACTION или измените другие ограничения FOREIGN KEY.
Не удалось создать ограничение. См. Предыдущие ошибки.
Вот как я определил многие отношения:
modelBuilder.Entity<Member>()
.HasMany(m => m.Jobs)
.WithMany(j => j.Members)
.Map(c =>
{
c.MapLeftKey("Id");
c.MapRightKey("JobId");
c.ToTable("MemberJobMap");
});
и когда я добавляю миграцию:
CreateTable(
"dbo.MemberJobMap",
c => new
{
Id = c.String(nullable: false, maxLength: 128),
JobId = c.Int(nullable: false),
})
.PrimaryKey(t => new { t.Id, t.JobId })
.ForeignKey("dbo.Members", t => t.Id, cascadeDelete: true)
.ForeignKey("dbo.Jobs", t => t.JobId, cascadeDelete: true)
.Index(t => t.Id)
.Index(t => t.JobId);
Я попробовал изменить cascadeDelete на false, но это дает мне ошибку, когда я удаляю кандидата, у которого есть прикладные задания, или когда я пытаюсь удалить задание с применением кандидатов.
Как исправить эту ошибку? Так что:
- Когда задание удалено, оно будет удалять связанные
rowsjobmap строк таблицы без каких-либо других таблиц
- Когда кандидат удаляется, он удаляет связанные
rowsjobmap строк таблицы и строки пользовательских таблиц без каких-либо
другая таблица
- При сохранении всех других указанных действий удаления каскада
тот же
Ответы
Ответ 1
Я исправил эту проблему
Проблема возникает из-за того, что у меня есть два каскадных пути удаления в таблицу CandidateJobMap:
Если я удалю работодателя, он собирается удалить связанные задания работодателя, которые, в свою очередь, удаляют таблицу CandidateJobMap:
Employer- > Jobs- > CandidateJobMap
Если я удалю кандидата, его будет удалять таблица CandidateJobMap:
как принадлежность > CandidateJobMap
Итак, чтобы обойти эту проблему, я должен отключить один из путей удаления, вы не можете указать WillCascadeDelete (false), когда вы создаете много-много отношений, поэтому вместо этого вам нужно изменить миграцию следующим образом:
CreateTable(
"dbo.MemberJobMap",
c => new
{
Id = c.String(nullable: false, maxLength: 128),
JobId = c.Int(nullable: false),
})
.PrimaryKey(t => new { t.Id, t.JobId })
.ForeignKey("dbo.Members", t => t.Id, cascadeDelete: false) <--------cascade delete to false
.ForeignKey("dbo.Jobs", t => t.JobId, cascadeDelete: true)
.Index(t => t.Id)
.Index(t => t.JobId);
Теперь, когда вы установили каскадное удаление в значение false, когда кандидат был удален, он не удалит связанные строки CandidateJobMap, это приведет к другой ошибке при попытке удалить кандидата, где он также является связанным ключом в CandidateJobMap, поэтому перед удалением кандидата необходимо вручную удалить связанные строки в CandidateJobMap:
//remove all applied jobs from user, without doing this, you will receive an error
foreach (var appliedjob in user.Member.Jobs.ToList())
{
user.Member.Jobs.Remove(appliedjob);
}
//before you can delete the user
await UserManager.DeleteAsync(user);
Не уверен, что это лучший способ, но это сработало для меня.
Ответ 2
Я понимаю, что это довольно старый, но я столкнулся с этой проблемой, и есть некоторые комментарии, которые не были рассмотрены.
В частности, "вы скрыли проблему..."
Каскад из нескольких путей должен работать, потому что он прав, что удаление одного из предков должно иметь эффект удаления потомка. Но это только в теории, на самом деле SQL Server просто не позволяет этого, следовательно, ошибки. Одно из решений находится здесь: solving-the-sql-server-multiple-cascade-path-issue-with-a-trigger.
Он предлагает удалить все действия, вызывающие оскорбление, и заменить их всеми триггерами для удаления записей. Я предпочитаю работать на верхнем уровне каскадной цепи. В его примере я просто отрезал его в "родительской" записи с помощью INSTEAD OF DELETE для детей, и пусть каскад позаботится обо всем остальном.
Я делаю это по двум причинам.
-
Это должно быть сделано в базе данных, потому что это последняя линия защиты для плохих данных... вроде как опорожнение карманов непосредственно перед тем, как одежда входит в шайбу. Забота о вещах означает, что вам не нужно копировать код во все разные модели, которые вы могли бы построить с этой БД в будущем, и вы не должны рассчитывать на всех новичков-разработчиков, чтобы позаботиться об этом.
-
делать это у самого верхнего предка, чтобы все остальные отношения оставались, включая те, которые вы могли бы добавить в будущем.
Надеюсь, это поможет,
Mike
Ответ 3
Я разработал бы что-то вроде этого.
User_Type <-- Tow types of users Candidates and Employers
USERS <-- Common Fields for Candidates and Employers , along with one column to
-- identify if it is Candidate or an Employer referencing back to User_Type
Emp_Details <-- Only columns that an employer will have referencing back to Users table
Can_Details <-- Only columns that a Candidate will have referencing back to Users table
Jobs <-- Jobs published by a user who is an employer referencing back to Users table
CandidateJobs <-- A composite key referencing back to Jobs(JobId) and Users who are
--Candidates