如何在 EF CodeFirst CTP5 中使用 Fluent API 使删除触发器正常工作?

发布于 2024-10-23 17:07:56 字数 2340 浏览 2 评论 0原文

我无法将引用完整性调低到足以允许我的删除触发器触发。

我有一个具有三个 FK 的依赖实体。我希望当任何主要实体被删除时它也被删除。

对于主体实体 Role 和 OrgUnit(见下文),我可以依靠约定来创建所需的一对多关系,并且级联删除可以完成我想要的操作,即:删除任一主体时,关联将被删除。

然而,对于Member,我有多个SQL Server不喜欢的级联删除路径(此处未显示),因此我需要使用流畅的API来禁用级联删除。

这是我的(简化的)模型:

public class Association
{
    public int id { get; set; }
    public int roleid { get; set; }
    public virtual Role role { get; set; }
    public int? memberid { get; set; }
    public virtual Member member { get; set; }
    public int orgunitid { get; set; }
    public virtual OrgUnit orgunit { get; set; }
}

public class Role
{
    public int id { get; set; }
    public virtual ICollection<Association> associations { get; set; }
}

public class Member
{
    public int id { get; set; }
    public virtual ICollection<Association> associations { get; set; }
}

public class Organization
{
    public int id { get; set; }
    public virtual ICollection<Association> associations { get; set; }
}

我第一次运行流畅的 API 代码如下所示:

protected override void OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder)
    {
        DbDatabase.SetInitializer<ConfDB_Model>(new ConfDBInitializer());

        modelBuilder.Entity<Member>()
            .HasMany(m=>m.assocations)
            .WithOptional(a=>a.member)
            .HasForeignKey(a=>a.memberId)
            .WillCascadeOnDelete(false);
    }

我的种子函数创建删除触发器:

    protected override void Seed(ConfDB_Model context)
    {
        context.Database.SqlCommand("CREATE TRIGGER MemberAssocTrigger ON dbo.Members FOR DELETE AS DELETE Assocations FROM Associations, deleted WHERE Associations.memberId = deleted.id");
    }

问题 -- 当我运行这个程序时,创建一个角色、一个成员、一个组织单元和一个将三者联系在一起的关联,一切都很好。当我删除角色时,关联会按照我的预期被级联删除,与 OrgUnit 相同。 -- 但是 -- 当我删除该成员时,我收到一个带有引用完整性错误的异常。我尝试设置 ON CASCADE SET NULL,因为我的 memberid FK 可为空,但 SQL 再次抱怨多个级联路径,因此显然我无法在成员关联关系中级联任何内容。

为了使其正常工作,我必须将以下代码添加到 Seed():

    context.Database.SqlCommand("ALTER TABLE dbo.ACLEntries DROP CONSTRAINT member_associations");

如您所见,这会删除模型构建器创建的约束。

问题:这感觉像是一个彻底的黑客攻击。有没有一种方法可以使用流畅的 API 让我说不应该检查引用完整性,或者让它足够放松,以便成员删除能够工作并允许触发触发器?

预先感谢您可以提供的任何帮助。尽管流畅的 API 可能很“流畅”,但我发现它们远非直观。

I am having trouble getting referential integrity dialled down enough to allow my delete trigger to fire.

I have a dependent entity with three FKs. I want it to be deleted when any of the principal entities is deleted.

For principal entities Role and OrgUnit (see below) I can rely on conventions to create the required one-many relationship and cascade delete does what I want, ie: Association is removed when either principal is deleted.

For Member, however, I have multiple cascade delete paths (not shown here) which SQL Server doesn't like, so I need to use fluent API to disable cascade deletes.

Here is my (simplified) model:

public class Association
{
    public int id { get; set; }
    public int roleid { get; set; }
    public virtual Role role { get; set; }
    public int? memberid { get; set; }
    public virtual Member member { get; set; }
    public int orgunitid { get; set; }
    public virtual OrgUnit orgunit { get; set; }
}

public class Role
{
    public int id { get; set; }
    public virtual ICollection<Association> associations { get; set; }
}

public class Member
{
    public int id { get; set; }
    public virtual ICollection<Association> associations { get; set; }
}

public class Organization
{
    public int id { get; set; }
    public virtual ICollection<Association> associations { get; set; }
}

My first run at fluent API code looks like this:

protected override void OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder)
    {
        DbDatabase.SetInitializer<ConfDB_Model>(new ConfDBInitializer());

        modelBuilder.Entity<Member>()
            .HasMany(m=>m.assocations)
            .WithOptional(a=>a.member)
            .HasForeignKey(a=>a.memberId)
            .WillCascadeOnDelete(false);
    }

My seed function creates the delete trigger:

    protected override void Seed(ConfDB_Model context)
    {
        context.Database.SqlCommand("CREATE TRIGGER MemberAssocTrigger ON dbo.Members FOR DELETE AS DELETE Assocations FROM Associations, deleted WHERE Associations.memberId = deleted.id");
    }

PROBLEM --
When I run this, create a Role, a Member, an OrgUnit, and an Association tying the three together all is fine. When I delete the Role, the Association gets cascade deleted as I expect, same with OrgUnit. -- HOWEVER -- when I delete the Member I get an exception with a referential integrity error. I have tried setting ON CASCADE SET NULL because my memberid FK is nullable but SQL complains again about multiple cascade paths, so apparently I can cascade nothing in the Member-Association relationship.

To get this to work I must add the following code to Seed():

    context.Database.SqlCommand("ALTER TABLE dbo.ACLEntries DROP CONSTRAINT member_associations");

As you can see, this drops the constraint created by the model builder.

QUESTION: this feels like a complete hack. Is there a way using fluent API for me to say that referential integrity should NOT be checked, or otherwise to get it to relax enough for the Member delete to work and allow the trigger to be fired?

Thanks in advance for any help you can offer. Although fluent APIs may be "fluent" I find them far from intuitive.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

审判长 2024-10-30 17:07:56
        var listTriggers = new List<string>();
        var listStoreProcedures = new List<string>();
        using (var command = _context.Database.GetDbConnection().CreateCommand())
        {
            command.CommandText = "select group_concat(TRIGGER_NAME) from information_schema.TRIGGERS where TRIGGER_SCHEMA = 'yourschema'";
            command.CommandType = CommandType.Text;
            _context.Database.OpenConnection();
            using (var reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    var value = reader[0].ToString();
                    if (!string.IsNullOrEmpty(value))
                        listTriggers.AddRange(value.Split(","));
                }
            }

            command.CommandText = "select group_concat(ROUTINE_NAME) from information_schema.ROUTINES where ROUTINE_TYPE = 'PROCEDURE' and ROUTINE_SCHEMA = 'yourschema'";
            using (var reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    var value = reader[0].ToString();
                    if (!string.IsNullOrEmpty(value))
                        listStoreProcedures.AddRange(value.Split(","));
                }
            }
        }

        foreach (var item in listTriggers)
            _context.Database.ExecuteSqlRaw(
quot;drop trigger if exists {item}");

        foreach (var item in listStoreProcedures)
            _context.Database.ExecuteSqlRaw(
quot;drop procedure if exists {item}");
        var listTriggers = new List<string>();
        var listStoreProcedures = new List<string>();
        using (var command = _context.Database.GetDbConnection().CreateCommand())
        {
            command.CommandText = "select group_concat(TRIGGER_NAME) from information_schema.TRIGGERS where TRIGGER_SCHEMA = 'yourschema'";
            command.CommandType = CommandType.Text;
            _context.Database.OpenConnection();
            using (var reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    var value = reader[0].ToString();
                    if (!string.IsNullOrEmpty(value))
                        listTriggers.AddRange(value.Split(","));
                }
            }

            command.CommandText = "select group_concat(ROUTINE_NAME) from information_schema.ROUTINES where ROUTINE_TYPE = 'PROCEDURE' and ROUTINE_SCHEMA = 'yourschema'";
            using (var reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    var value = reader[0].ToString();
                    if (!string.IsNullOrEmpty(value))
                        listStoreProcedures.AddRange(value.Split(","));
                }
            }
        }

        foreach (var item in listTriggers)
            _context.Database.ExecuteSqlRaw(
quot;drop trigger if exists {item}");

        foreach (var item in listStoreProcedures)
            _context.Database.ExecuteSqlRaw(
quot;drop procedure if exists {item}");
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文