I'm trying to implement an Enum list based on the idea from this answer. My goal is to be able to use an Enum inside my Domain, and have it converted to a class instance when saving and retrieving it from the database.
Using the code as it is (source below), I get a DbUpdateException
with the message:
Violation of PRIMARY KEY constraint 'PK_dbo.Faculty'. Cannot insert duplicate key in object 'dbo.Faculty'. The duplicate key value is (0). The statement has been terminated.
Which is expected, since I'm newing up every instance of Faculty.
To fix it, I tried the solutions from a
few
questions
on
this,
with no success. They suggested to either attach the entity or to set it's state to Unchanged. So I tried overriding SaveChanges()
and use:
ChangeTracker.Entries<Faculty>().ToList().ForEach(x => x.State = EntityState.Unchanged);
and
ChangeTracker.Entries<Faculty>().ToList()
.ForEach(x => Entry(x.Entity).State = EntityState.Unchanged);
and even
ChangeTracker.Entries<Department>().ToList().ForEach(department =>
{
foreach (var faculty in department.Entity.Faculties)
{
Entry(faculty).State = EntityState.Unchanged;
}
});
But all of them throw an InvalidOperationException
with the message:
Additional information: Saving or accepting changes failed because more than one entity of type 'TestEnum.Entities.Faculty' have the same primary key value. Ensure that explicitly set primary key values are unique. Ensure that database-generated primary keys are configured correctly in the database and in the Entity Framework model. Use the Entity Designer for Database First/Model First configuration. Use the 'HasDatabaseGeneratedOption" fluent API or 'DatabaseGeneratedAttribute' for Code First configuration.
How can I instruct EF to not try and insert these into the database? I need this implementation to work inside SaveChanges()
as I'm following DDD design rules and keeping Infrastructure separated from Domain logic.
The code is as follows:
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));
}