Entity Framework Fluent API - Каскадное удаление - С интерфейсом - PullRequest
0 голосов
/ 04 мая 2020

В моей базе данных на основе модели данных / Entity Framework (EFCore) определены следующие отношения:

  1. A Person.cs, которая реализует интерфейс IRecord. Person имеет List из ExternalId.cs объектов.
  2. Класс ExternalId.cs имеет свойство, обратное к интерфейсу IRecord. Это отношение один ко многим (от одного IRecord ко многим ExternalId).

Желаемое поведение состоит в том, что при удалении объекта IRecord зависимые внешние идентификаторы тоже.

Другие модели / таблицы в моей базе данных также реализуют этот интерфейс (Regulation.cs, Organization.cs, et c.) И внешние идентификаторы, на которые они ссылаются, при условии того же поведения OnDelete.

Это то, что я настроил в ApplicationDbContext.cs:

modelBuilder.Entity<Customer>()
    .HasMany(rec => rec.ExternalIds)
    .WithOne(extId => (Customer)extId.Record)
    .OnDelete(DeleteBehavior.Cascade);

modelBuilder.Entity<Person>()
    .HasMany(rec => rec.ExternalIds)
    .WithOne(extId => (Person)extId.Record)
    .OnDelete(DeleteBehavior.Cascade);

modelBuilder.Entity<Organization>()
    .HasMany(rec => rec.ExternalIds)
    .WithOne(extId => (Organization)extId.Record)
    .OnDelete(DeleteBehavior.Cascade);

Что я делаю неправильно, что это работает не так, как ожидалось?

Это различные модели, описанные выше:

IRecord:

public interface IRecord {
    int Id { get; set; }
}

ExternalId:

public class ExternalId : IRecord
{
    public int Id { get; set; }

    [InverseProperty("ExternalIds")]
    public IRecord Record { get; set; }

    public string Service { get; set; } // Comes from DataSource Enum

    public string Purpose { get; set; } // Comes from IdType Enum

    public string Value { get; set; } // Value
}

Person

public class Person: IRecord
{
    #region Properties
    public int Id { get; set; }

    public string FirstName { get; set; }

    public string LastName { get; set; }

    [DataType(DataType.Date)]
    public DateTime? DateOfBirth { get; set; }

    public int ContactInformationId { get; set; }
    public virtual ContactInformation ContactInformation { get; set; }

    public string PictureURL { get; set; }

    public string Title { get; set; }

    public List<ExternalId> ExternalIds { get; set; }
}

Это имена таблиц:

public DbSet<ExternalId> Identifiers { get; set; }
public DbSet<Person> People { get; set; }

Когда я пытаюсь удалить объекты Person в моей базе данных (через _context.People.RemoveRange(peopleToDelete)), я получаю следующую ошибку:

Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. See the inner exception for details.
 ---> Microsoft.Data.SqlClient.SqlException (0x80131904): The DELETE statement conflicted with the REFERENCE constraint "FK_Identifiers_People_PersonId". The conflict occurred in database "OurGov", table "dbo.Identifiers", column 'PersonId'.
The DELETE statement conflicted with the REFERENCE constraint "FK_Identifiers_People_PersonId". The conflict occurred in database "OurGov", table "dbo.Identifiers", column 'PersonId'.

Неправильно ли я установил отношения? Я чувствую, что это связано с тем, что мое свойство отношений является интерфейсом, который я должен был привести.

Обновление - 2020-05-05

Я заметил, что моя SQL Таблица, которая была сгенерирована, имеет три столбца, PersonId, Регламент, RecordId. Класс Person имеет свой идентификатор, сохраненный в PersonId, в отличие от RecordId. Я могу видеть, что это было сгенерировано во время одной из моих миграций некоторое время назад.

Я подозреваю, что мог добавить миграцию и обновить базу данных до добавления обратного свойства и / или маркировки класса Person (а также Регламента). class) как реализующий интерфейс Record.

Как я могу очистить это, чтобы отбросить эти столбцы и чтобы все мои объекты IRecord упоминались в RecordId?

enter image description here

1 Ответ

0 голосов
/ 04 мая 2020

Эта модель сгенерировала для меня внешний ключ ON DELETE CASCADE. например,

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.SqlServer;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;

namespace EfCore3Test
{
    public interface IRecord
    {
        int Id { get; set; }
    }
    public class Person : IRecord
    {

        public int Id { get; set; }

        public string FirstName { get; set; }

        public string LastName { get; set; }

        [DataType(DataType.Date)]
        public DateTime? DateOfBirth { get; set; }

        public int ContactInformationId { get; set; }
        // public virtual ContactInformation ContactInformation { get; set; }

        public string PictureURL { get; set; }

        public string Title { get; set; }

        public List<ExternalId> ExternalIds { get; set; }
    }
    public class ExternalId : IRecord
    {
        public int Id { get; set; }

        [InverseProperty("ExternalIds")]
        public IRecord Record { get; set; }

        public string Service { get; set; } // Comes from DataSource Enum

        public string Purpose { get; set; } // Comes from IdType Enum

        public string Value { get; set; } // Value
    }

    public class Db : DbContext
    {
        public DbSet<ExternalId> Identifiers { get; set; }
        public DbSet<Person> People { get; set; }
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.Entity<Person>()
                        .HasMany(rec => rec.ExternalIds)
                        .WithOne(extId => (Person)extId.Record)
                        .OnDelete(DeleteBehavior.Cascade);
        }
        private static readonly ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
        {
            builder.AddFilter((category, level) =>
               category == DbLoggerCategory.Database.Command.Name
               && level == LogLevel.Information).AddConsole();
        });


        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseLoggerFactory(loggerFactory)
                          .UseSqlServer("Server=.;database=EfCore3Test;Integrated Security=true",
                                        o => o.UseRelationalNulls());

            base.OnConfiguring(optionsBuilder);
        }
    }





    class Program
    {

        static void Main(string[] args)
        {


            using var db = new Db();

            db.Database.EnsureDeleted();
            db.Database.EnsureCreated();
        }
    }
}

выходы

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (15ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT 1
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (5ms) [Parameters=[], CommandType='Text', CommandTimeout='60']
      IF SERVERPROPERTY('EngineEdition') <> 5
      BEGIN
          ALTER DATABASE [EfCore3Test] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
      END;
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (28ms) [Parameters=[], CommandType='Text', CommandTimeout='60']
      DROP DATABASE [EfCore3Test];
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (121ms) [Parameters=[], CommandType='Text', CommandTimeout='60']
      CREATE DATABASE [EfCore3Test];
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (39ms) [Parameters=[], CommandType='Text', CommandTimeout='60']
      IF SERVERPROPERTY('EngineEdition') <> 5
      BEGIN
          ALTER DATABASE [EfCore3Test] SET READ_COMMITTED_SNAPSHOT ON;
      END;
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT 1
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (5ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE [People] (
          [Id] int NOT NULL IDENTITY,
          [FirstName] nvarchar(max) NULL,
          [LastName] nvarchar(max) NULL,
          [DateOfBirth] datetime2 NULL,
          [ContactInformationId] int NOT NULL,
          [PictureURL] nvarchar(max) NULL,
          [Title] nvarchar(max) NULL,
          CONSTRAINT [PK_People] PRIMARY KEY ([Id])
      );
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE [Identifiers] (
          [Id] int NOT NULL IDENTITY,
          [RecordId] int NULL,
          [Service] nvarchar(max) NULL,
          [Purpose] nvarchar(max) NULL,
          [Value] nvarchar(max) NULL,
          CONSTRAINT [PK_Identifiers] PRIMARY KEY ([Id]),
          CONSTRAINT [FK_Identifiers_People_RecordId] FOREIGN KEY ([RecordId]) REFERENCES [People] ([Id]) ON DELETE CASCADE
      );
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE INDEX [IX_Identifiers_RecordId] ON [Identifiers] ([RecordId]);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...