2017-06-08 11 views
1

私はthis answerのアイデアに基づいて列挙型リストを実装しようとしています。私の目標は、ドメイン内でEnumを使用できるようにし、データベースから保存して取得するときにクラスインスタンスに変換させることです。それは(以下ソース)であるようなコードを使用しエンティティフレームワークの列挙型リストを模倣し、最初にコードを使用してDDD

は、私は、メッセージにDbUpdateExceptionを得る:PRIMARY KEY制約の

違反 'PK_dbo.Faculty'。オブジェクト 'dbo.Faculty'に重複キーを挿入できません。重複キー値は(0)です。 ステートメントが終了しました。

私はFacultyのすべてのインスタンスを新しくしているので、期待しています。

それを修正するには、私がいない成功を収めて few questions on this、 から解決策を試してみました。彼らはエンティティを添付するか、その状態をUnchangedに設定することを提案しました。だから私はSaveChanges()と使用を上書きしようとした:

ChangeTracker.Entries<Faculty>().ToList().ForEach(x => x.State = EntityState.Unchanged); 

ChangeTracker.Entries<Faculty>().ToList() 
    .ForEach(x => Entry(x.Entity).State = EntityState.Unchanged); 

とさえ

ChangeTracker.Entries<Department>().ToList().ForEach(department => 
{ 
    foreach (var faculty in department.Entity.Faculties) 
    { 
     Entry(faculty).State = EntityState.Unchanged; 
    } 
}); 

しかし、それらのすべてがメッセージをInvalidOperationExceptionを投げる:

追加情報:Sa 'TestEnum.Entities.Faculty'タイプの複数のエンティティが同じ主キー値を持つため、変更を受け入れることができませんでした。明示的に設定された主キー値が一意であることを確認します。データベース生成の主キーがデータベースとEntity Frameworkモデルで正しく構成されていることを確認します。 Entity Designerをデータベースファースト/モデルファースト構成に使用します。コードファースト構成のための「HasDatabaseGeneratedOption」流暢なAPIや 『DatabaseGeneratedAttribute』を使用してください。

どのようにデータベースにこれらを試してみて、挿入しないためにEFを指示することができますか?私は、次のよと私はSaveChanges()内で動作するように、この実装を必要とします。次のようにDDD設計ルールとドメインロジックから分離保つインフラ

コードは次のとおりです。

class Program 
{ 
    static void Main(string[] args) 
    { 
     using (var dbContext = new MyContext()) 
     { 
      var example = new Department(); 
      example.AddFaculty(FacultyEnum.Eng); 
      example.AddFaculty(FacultyEnum.Math); 

      dbContext.Department.Add(example); 

      var example2 = new Department(); 
      example2.AddFaculty(FacultyEnum.Math); 

      dbContext.Department.Add(example2); 

      dbContext.SaveChanges(); 

      var exampleFromDb1 = dbContext.Department.Find(1); 
      var exampleFromDb2 = dbContext.Department.Find(2); 
     } 
    } 
} 

public enum FacultyEnum 
{ 
    [Description("English")] 
    Eng, 
    [Description("Mathematics")] 
    Math, 
    [Description("Economy")] 
    Eco, 
} 

public class Department 
{ 
    public int Id { get; set; } 
    public virtual ICollection<Faculty> Faculties { get; set; } 

    public Department() 
    { 
     Faculties = new List<Faculty>(); 
    } 

    public void AddFaculty(FacultyEnum faculty) 
    { 
     Faculties.Add(faculty); 
    } 
} 

public class Faculty 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 
    public string Description { get; set; } 

    private Faculty(FacultyEnum @enum) 
    { 
     Id = (int)@enum; 
     Name = @enum.ToString(); 
     Description = @enum.GetEnumDescription(); 
    } 

    protected Faculty() { } //For EF 

    public static implicit operator Faculty(FacultyEnum @enum) => new Faculty(@enum); 

    public static implicit operator FacultyEnum(Faculty faculty) => (FacultyEnum)faculty.Id; 
} 

public class MyContext : DbContext 
{ 
    public DbSet<Department> Department { get; set; } 
    public DbSet<Faculty> Faculty { get; set; } 

    public MyContext() 
     : base(nameOrConnectionString: GetConnectionString()) 
    { 
     Database.SetInitializer(new MyDbInitializer()); 
    } 

    public int SaveSeed() 
    { 
     return base.SaveChanges(); 
    } 

    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
     modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); 
     modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>(); 
     modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>(); 

     modelBuilder.Properties<string>() 
      .Configure(p => p.HasMaxLength(100)); 

     modelBuilder.Configurations.Add(new DepartmentConfiguration()); 
     modelBuilder.Configurations.Add(new FacultyConfiguration()); 

     base.OnModelCreating(modelBuilder); 
    } 

    private static string GetConnectionString() 
    { 
     return @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=TestEnum;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False;MultipleActiveResultSets=true;"; 
    } 
} 

public class MyDbInitializer : DropCreateDatabaseIfModelChanges<MyContext> 
{ 
    protected override void Seed(MyContext context) 
    { 
     context.Faculty.SeedEnumValues<Faculty, FacultyEnum>(@enum => @enum); 
     context.SaveSeed(); 
    } 
} 

public class DepartmentConfiguration : EntityTypeConfiguration<Department> 
{ 
    public DepartmentConfiguration() 
    { 
     HasMany(x => x.Faculties) 
      .WithMany(); 
    } 
} 

public class FacultyConfiguration : EntityTypeConfiguration<Faculty> 
{ 
    public FacultyConfiguration() 
    { 
     Property(x => x.Id) 
      .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None); 
    } 
} 

public static class Extensions 
{ 
    public static string GetEnumDescription<TEnum>(this TEnum item) 
     => item.GetType() 
       .GetField(item.ToString()) 
       .GetCustomAttributes(typeof(DescriptionAttribute), false) 
       .Cast<DescriptionAttribute>() 
       .FirstOrDefault()?.Description ?? string.Empty; 

    public static void SeedEnumValues<T, TEnum>(this IDbSet<T> dbSet, Func<TEnum, T> converter) 
     where T : class => Enum.GetValues(typeof(TEnum)) 
           .Cast<object>() 
           .Select(value => converter((TEnum)value)) 
           .ToList() 
           .ForEach(instance => dbSet.AddOrUpdate(instance)); 
} 

答えて

1

私たちはクラスの新しいインスタンスを使用する場合、EFは常にDBに挿入しようとしますワン。これを回避する方法はChangeTracker内に格納されているものを「元に戻す」ことによって、DBからロードされたエンティティのみを使用します。

だから何私がしたことは、SaveChanges()メソッドをオーバーライドChangeTracker内の各Departmentをループ、それぞれの学部Idsのリストをつかむ、すべてのFacultyのオブジェクトのChangeTrackerエントリをクリアして、自分の部署に再びそれらを追加しますが、今使用していましたChangeTrackerまたはFind()のいずれかにあるエンティティ。

これは少し効率が悪いように見えました。テストを実行しました.1つはこのメソッドを使用し、もう1つはそれぞれの実行でDbの各Facultyを読み込みます。私はそれを1万回を走った、結果は3回の実験の平均である:

Enum list: 77642 ms 
Normal class: 70619 ms 

あなたが見ることができるように、スピードペナルティの約10%がこの方法を使用するためにありますが、それはコスト/利益を決定するのはあなた次第ですあなたのアプリケーションのために。私のアプリケーションは、その多くの操作の近くでは得られないので、私には、追加された表現力がコストを補う。

MyContext以外にも、いくつかのクラスが元の投稿から変更されました。また、すべてのユースケースをカバーするようにコンソールテストを拡張しました。

class Program 
{ 
    static void Main(string[] args) 
    { 
     var id = 0; 

     using (var dbContext = new MyContext()) 
     { 
      var department = new Department(); 
      department.AddFaculty(FacultyEnum.Eng); 
      department.AddFaculty(FacultyEnum.Math); 
      dbContext.Department.Add(department); 

      var department2 = new Department(); 
      department2.AddFaculty(FacultyEnum.Math); 
      dbContext.Department.Add(department2); 

      dbContext.SaveChanges(); 
      id = department.Id; 
     } 

     using (var dbContext = new MyContext()) 
     { 
      var department = dbContext.Department.Find(id); 
      department.AddFaculty(FacultyEnum.Eco); 
      dbContext.SaveChanges(); 
     } 

     using (var dbContext = new MyContext()) 
     { 
      var department = dbContext.Department.Find(id); 
      var faculty = department.Faculties.Where(x => x.Id == (int)FacultyEnum.Eng).FirstOrDefault(); 
      department.Faculties.Remove(faculty); 
      dbContext.SaveChanges(); 
     } 

     using (var dbContext = new MyContext()) 
     { 
      var department = dbContext.Department.Find(id); 
      Console.WriteLine($"Department Id {department.Id} has these faculties:"); 
      foreach (var faculty in department.Faculties) 
      { 
       Console.WriteLine($"- {faculty.Id}"); 
      } 
     } 

     Console.ReadKey(); 
    } 
} 

public class MyContext : DbContext 
{ 
    public DbSet<Department> Department { get; set; } 
    public DbSet<Faculty> Faculty { get; set; } 

    public MyContext() 
     : base(nameOrConnectionString: GetConnectionString()) 
    { 
     Database.SetInitializer(new MyDbInitializer()); 
    } 

    public override int SaveChanges() 
    { 
     CleanUpFaculties(); 

     return base.SaveChanges(); 
    } 

    private void CleanUpFaculties() 
    { 
     var departments = ChangeTracker 
      .Entries<Department>() 
      .Select(x => x.Entity) 
      .ToList(); 

     var cachedDataToReload = departments 
      .Select(department => new 
      { 
       Department = department, 
       FacultyIds = department.Faculties.Select(faculty => faculty.Id).ToList(), 
      }) 
      .ToList(); 

     CleanUpFacultiesOnChangeTracker(); 

     foreach (var item in cachedDataToReload) 
     { 
      var faculties = LoadFacultiesFromDb(item.FacultyIds); 

      typeof(Department).GetProperty("Faculties") 
       .SetValue(item.Department, faculties); 
     } 
    } 

    private void CleanUpFacultiesOnChangeTracker() 
    { 
     var changedEntries = ChangeTracker.Entries<Faculty>().Where(x => x.State != EntityState.Unchanged).ToList(); 

     foreach (var entry in changedEntries) 
     { 
      switch (entry.State) 
      { 
       case EntityState.Modified: 
        entry.CurrentValues.SetValues(entry.OriginalValues); 
        entry.State = EntityState.Unchanged; 
        break; 
       case EntityState.Added: 
        entry.State = EntityState.Detached; 
        break; 
       case EntityState.Deleted: 
        entry.State = EntityState.Unchanged; 
        break; 
      } 
     } 
    } 

    private ICollection<Faculty> LoadFacultiesFromDb(IEnumerable<FacultyEnum> facultyIds) 
    { 
     var destination = new List<Faculty>(); 

     foreach (var id in facultyIds) 
     { 
      var newFaculty = ChangeTracker 
       .Entries<Faculty>() 
       .Where(x => x.State == EntityState.Unchanged && x.Entity.Id == id) 
       .FirstOrDefault() 
       ?.Entity; 

      if (newFaculty == null) 
      { 
       newFaculty = Set<Faculty>().Find(id) ?? id; 
      } 

      destination.Add(newFaculty); 
     } 

     return destination; 
    } 

    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
     modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); 
     modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>(); 
     modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>(); 

     modelBuilder.Properties<string>() 
      .Configure(p => p.HasMaxLength(100)); 

     modelBuilder.Configurations.Add(new DepartmentConfiguration()); 
     modelBuilder.Configurations.Add(new FacultyConfiguration()); 

     base.OnModelCreating(modelBuilder); 
    } 

    private static string GetConnectionString() 
    { 
     return @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=TestEnum;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False;MultipleActiveResultSets=true;"; 
    } 
} 

public class MyDbInitializer : DropCreateDatabaseIfModelChanges<MyContext> 
{ 
    protected override void Seed(MyContext context) 
    { 
     context.Faculty.SeedEnumValues<Faculty, FacultyEnum>(theEnum => theEnum); 
     context.SaveChanges(); 

     base.Seed(context); 
    } 
} 

public class DepartmentConfiguration : EntityTypeConfiguration<Department> 
{ 
    public DepartmentConfiguration() 
    { 
     HasMany(x => x.Faculties) 
      .WithMany(); 
    } 
} 

public class FacultyConfiguration : EntityTypeConfiguration<Faculty> 
{ 
    public FacultyConfiguration() 
    { 
     Property(x => x.Id) 
      .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None); 
    } 
} 

public class Department 
{ 
    public int Id { get; private set; } 
    public virtual ICollection<Faculty> Faculties { get; private set; } 

    public Department() 
    { 
     Faculties = new List<Faculty>(); 
    } 

    public void AddFaculty(FacultyEnum faculty) 
    { 
     Faculties.Add(faculty); 
    } 
} 

public class Faculty 
{ 
    public FacultyEnum Id { get; private set; } 
    public string Name { get; private set; } 
    public string Description { get; private set; } 

    private Faculty(FacultyEnum theEnum) 
    { 
     Id = theEnum; 
     Name = theEnum.ToString(); 
     Description = theEnum.Description(); 
    } 

    protected Faculty() { } //For EF 

    public static implicit operator Faculty(FacultyEnum theEnum) => new Faculty(theEnum); 

    public static implicit operator FacultyEnum(Faculty faculty) => faculty.Id; 
} 

public enum FacultyEnum 
{ 
    [Description("English")] 
    Eng, 
    [Description("Mathematics")] 
    Math, 
    [Description("Economy")] 
    Eco, 
} 

public static class Extensions 
{ 
    public static string Description<TEnum>(this TEnum item) 
     => item.GetType() 
       .GetField(item.ToString()) 
       .GetCustomAttributes(typeof(DescriptionAttribute), false) 
       .Cast<DescriptionAttribute>() 
       .FirstOrDefault()?.Description ?? string.Empty; 

    public static void SeedEnumValues<T, TEnum>(this IDbSet<T> dbSet, Func<TEnum, T> converter) 
     where T : class => Enum.GetValues(typeof(TEnum)) 
           .Cast<object>() 
           .Select(value => converter((TEnum)value)) 
           .ToList() 
           .ForEach(instance => dbSet.AddOrUpdate(instance)); 
} 

出力は次のようになります。

// Department Id 1 has these faculties: 
// - Math 
// - Eco 
関連する問題