Is there a nice pattern for implementing an abstract DbContext in EF7 / .Net Core 3 to avoid duplication of shared entities / config across projects? - asp.net-core

I have a number of different projects that all implement the same schema for configuration, security and audit and are looking for a pattern that would allow me to put these schema definitions in an abstract classes (entity, configuration and dbcontext) that can be extended in the concrete implementations if needed. My current POC fails when the base configurations is applied. I get:
A key cannot be configured on 'UserRole' because it is a derived type. The key must be configured on the root type.
Any help / pointers will be greatly appreciated!
I have the following code samples....
Abstract base classes
RoleBase
public abstract class RoleBase
{
public RoleBase()
{
this.UserRoles = new List<UserRoles>();
}
public long Id { get; set; }
public string Name { get; set; }
public virtual IEnumerable<UserRoleBase> UserRoles { get; set; }
}
UserBase
public abstract class UserBase
{
public long Id { get; set; }
public string Username { get; set; }
public string Email { get; set; }
public virtual ICollection<UserRoleBase> UserRoles { get; set; }
}
UserRoleBase
public abstract class UserRoleBase
{
public long Id { get; set; }
public long RoleId { get; set; }
public long UserId { get; set; }
public bool Deleted { get; set; }
public virtual RoleBase Role { get; set; }
public virtual UserBase User { get; set; }
}
Each of these have an abstract configuration class for the base class...
RoleBase Configuration
public abstract class RoleConfiguration<T> : IEntityTypeConfiguration<T>
where T : RoleBase
{
public virtual void Configure(EntityTypeBuilder<T> builder)
{
// Primary Key
builder.HasKey(t => t.Id);
// Properties
builder.Property(t => t.Name)
.IsRequired()
.HasMaxLength(50);
// Table & Column Mappings
builder.ToTable("Role", "Security");
builder.Property(t => t.Id).HasColumnName("Id");
builder.Property(t => t.Name).HasColumnName("Name");
}
}
UserBase Configuration
public abstract class UserConfiguration<TBase> : IEntityTypeConfiguration<TBase>
where TBase : UserBase
{
public virtual void Configure(EntityTypeBuilder<TBase> builder)
{
// Primary Key
builder.HasKey(t => t.Id);
// Properties
builder.Property(t => t.Username).IsRequired().HasMaxLength(255);
builder.Property(t => t.Email).IsRequired().HasMaxLength(255);
// Table & Column Mappings
builder.ToTable("User", "Security");
builder.Property(t => t.Id).HasColumnName("Id");
builder.Property(t => t.Username).HasColumnName("Username");
builder.Property(t => t.Email).HasColumnName("Email");
}
}
UserRoleBase Configuration
public abstract class UserRoleConfiguration<T> : IEntityTypeConfiguration<T>
where T : UserRoleBase
{
public virtual void Configure(EntityTypeBuilder<T> builder)
{
// Primary Key
builder.HasKey(t => t.Id);
// Properties
builder.Property(t => t.RoleId).IsRequired();
builder.Property(t => t.UserId).IsRequired();
builder.Property(t => t.Deleted).IsRequired();
// Table & Column Mappings
builder.ToTable("UserRole", "Security");
builder.Property(t => t.Id).HasColumnName("Id");
builder.Property(t => t.RoleId).HasColumnName("RoleId");
builder.Property(t => t.UserId).HasColumnName("UserId");
builder.Property(t => t.Deleted).HasColumnName("Deleted");
// Relationships
builder.HasOne(t => t.Role)
.WithMany(t => (ICollection<TBase>)t.UserRoles)
.HasForeignKey(d => d.RoleId)
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(t => t.UserDetail)
.WithMany(t => (ICollection<TBase>)t.UserRoles)
.HasForeignKey(d => d.UserDetailId)
.OnDelete(DeleteBehavior.Restrict);
}
And a concrete implementation of the base classes:
Role
public class Role : RoleBase
{
}
User
public class User : UserBase
{
// Extension properties
public string FirstName { get; set; }
public string LastName { get; set; }
public string Phone { get; set; }
public string Mobile { get; set; }
}
UserRole
public class UserRole : UserRoleBase
{
}
And a concrete implementation of the configuration
RoleConfiguration
public class RoleConfiguration : Base.Configurations.RoleConfiguration<Role>
{
public override void Configure(EntityTypeBuilder<Role> builder)
{
base.Configure(builder);
this.ConfigureEntity(builder);
}
private void ConfigureEntity(EntityTypeBuilder<Role> builder)
{
}
}
UserConfiguration
public class UserConfiguration : Base.Configurations.UserConfiguration<User>
{
public override void Configure(EntityTypeBuilder<User> builder)
{
base.Configure(builder);
this.ConfigureEntity(builder);
}
private void ConfigureEntity(EntityTypeBuilder<User> builder)
{
//Registration of extension properties
builder.Property(t => t.FirstName).HasColumnName("FirstName");
builder.Property(t => t.LastName).HasColumnName("LastName");
builder.Property(t => t.Phone).HasColumnName("Phone");
builder.Property(t => t.Mobile).HasColumnName("Mobile");
}
}
UserRoleConfiguration
public class UserRoleConfiguration : Base.Configurations.UserRoleConfiguration<UserRole>
{
public override void Configure(EntityTypeBuilder<UserRole> builder)
{
base.Configure(builder);
this.ConfigureEntity(builder);
}
private void ConfigureEntity(EntityTypeBuilder<UserRole> builder)
{
}
}
And the base context
public abstract class BaseDbContext: DbContext
{
public BaseDbContext(DbContextOptions<BaseDbContext> options)
: base(options)
{
}
// https://github.com/aspnet/EntityFramework.Docs/issues/594
protected BaseDbContext(DbContextOptions options)
: base(options)
{
}
public DbSet<RoleBase> Roles { get; set; }
public DbSet<UserBase> Users { get; set; }
public DbSet<UserRoleBase> UserRoles { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
And the concrete context
public class MyDbContext: BaseDbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options)
:base(options)
{
}
protected MyDbContext(DbContextOptions options)
: base(options)
{
}
public new DbSet<Role> Roles { get; set; }
public new DbSet<User> Users { get; set; }
public new DbSet<UserRole> UserRoles { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new RoleConfiguration());
modelBuilder.ApplyConfiguration(new UserConfiguration());
modelBuilder.ApplyConfiguration(new UserRoleConfiguration());
base.OnModelCreating(modelBuilder);
}
}
So all this works for items that does not have navigation properties and migrates to the database fine as long as there is no navigation properties. I can see the extension properties on User being done as long as I comment out all the navigation properties.
With the navigation properties present, I get an error on the base configuration class. after concrete implementation called base.Configure(builder);
I get the following error message on builder.HasKey(t => t.Id); and for the above sample code it would be on...
public abstract class UserRoleConfiguration<T> : IEntityTypeConfiguration<T>
where T : UserRoleBase
{
public virtual void Configure(EntityTypeBuilder<T> builder)
{
// Primary Key
builder.HasKey(t => t.Id);
System.InvalidOperationException: 'A key cannot be configured on 'UserRole' because it is a derived type. The key must be configured on the root type 'UserRoleBase'. If you did not intend for 'UserRoleBase' to be included in the model, ensure that it is not included in a DbSet property on your context, referenced in a configuration call to ModelBuilder, or referenced from a navigation property on a type that is included in the model.'
Is there a way in which I can keep these relational configuration in the abstract base class so that I would not need to replicate it in each concrete implementation of the base classes? Or is there a different approach that can be followed to overcome this issue?

System.InvalidOperationException: 'A key cannot be configured on 'UserRole' because it is a derived type. The key must be configured on the root type 'UserRoleBase'. If you did not intend for 'UserRoleBase' to be included in the model, ensure that it is not included in a DbSet property on your context, referenced in a configuration call to ModelBuilder, or referenced from a navigation property on a type that is included in the model.'
From the error, you could use Key attribute on the id of the base model to specify the primary key .
From breaking changes included in EF Core 3.0 , ToTable on a derived type throws an exception , currently it isn't valid to map a derived type to a different table. This change avoids breaking in the future when it becomes a valid thing to do.
You could use Data Annotations on the base model to configure the table that a type maps to:
[Table("Role", Schema = "Security")]
public abstract class RoleBase
{
public RoleBase()
{
this.UserRoles = new List<UserRoles>();
}
[Key]
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<UserRoleBase> UserRoles { get; set; }
}

Related

How can I make a foreign key relationship between an identity server column and another custom table?

I use identity server.
I know that if I want to add new columns, I make a new class which will inherit from IdentityUser, and in SQL Server with EF, the column will be generated. But I want to have a "Gender" column which will have a foreign key relationship with another custom-made table: "1" will be for "male", "2" for "female" etc.
I also want to make a similar relationship with another table where the programming languages of every employee will be stored.
Is that possible?
public ApplicationUser : IdentityUser
{
public string FullName {get ; set; }
public int GenderId { get ; set ;}
public ICollection<ProgrammingLanguages> PL {get ; set ;}
}
Basically you need to implement one-to-many relationship between User and Gender tables and many-to-many relationship between User and ProgrammingLanguage. Many-to-many relationship requires extra table which will contain foreign keys to User and ProgrammingLanguage tables. You then need to override OnModelCreating() method and don't forget to call the base implementation of OnModelCreating() method so that let the base implementation to setup relationships between identity tables. You can read more on how to implement relationships between entities here. Here is the sample code how this can be done:
public ApplicationUser : IdentityUser
{
public string FullName { get; set; }
public int GenderId { get; set; }
public Gender Gender { get; set; }
public ICollection<UserProgrammingLanguage> UserProgrammingLanguages { get; set;}
}
public class ProgrammingLanguage
{
public int Id { get; set; }
public string Name { get; set; }
}
public class UserProgrammingLanguage
{
public string UserId { get; set; }
public ApplicationUser User { get; set; }
public int ProgrammingLanguageId { get; set; }
public ProgrammingLanguage ProgrammingLanguage { get; set; }
}
public class Gender
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ApplicationContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<Gender> Genders { get; set; }
public DbSet<ProgrammingLanguage> ProgrammingLanguages { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<UserProgrammingLanguage>(entity =>
{
entity.HasKey(pl => { pl.UserId, pl.ProgrammingLanguageId });
entity.HasOne(pl => pl.User)
.WithMany(u => u.UserProgrammingLanguages)
.HasForeignKey(pl => pl.UserId);
entity.HasOne(pl => pl.ProgrammingLanguage)
.WithMany()
.HasForeignKey(pl => pl.ProgrammingLanguageId);
}
builder.Entity<ApplicationUser>(entity =>
{
entity.HasOne(u => u.Gender)
.WithMany()
.HasForeignKey(u => u.GenderId)
.OnDelete(DeleteBehavior.Cascade);
})
base.OnModelCreating(builder);
}
}

exception:"type was not mapped" in entityframework codefirst with layers

i'm trying to apply LAYERS Concept on demo project developed using mvc and entity framework both
Data Annotations : for validations in Data Access Layer and
Fluent API : for mapping and tables relations
Problem : DbContext didn't Create DB and there is a Runtime Exception :
The type 'Domain.DataLayer.Member' was not mapped. Check that the type has not been explicitly excluded by using the Ignore method or NotMappedAttribute data annotation. Verify that the type was defined as a class, is not primitive, nested or generic, and does not inherit from EntityObject.
Code : my solutions consists of :
1- class library (Domain.Classes project): where i wrote all of my classes
public class Member
{
public int Id { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public string FullName { get; set; }
}
2- DAL (Domain.DataLayer project): also another class library and i referenced domain.classes
namespace Domain.DataLayer.Repositories
{
[MetadataType(typeof(MemberMetadata))]
public partial class Member : Classes.Member , IValidatableObject
{
public Member()
{
Tasks = new HashSet<Task>();
History = new HashSet<Commint>();
}
public string ConfirmPassword { get; set; }
public HashSet<Task> Tasks { get; set; }
public HashSet<Commint> History { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var result = new List<ValidationResult>();
if (!string.Equals(Password,ConfirmPassword))
{
result.Add(new ValidationResult("mismatch pwsd", new[] {"ConfirmPassword" }));
}
return result;
}
}
}
and i used repository pattern :
public class MemberRepository : IRepository<Member>
{
public Task<IQueryable<Member>> GetAllEntities()
{
return Task<IQueryable<Member>>.Factory.StartNew(() => new Context().Members.AsQueryable());
}
}
3-BLL : for sake of simplicity : there is no Business Logic Layer
4- PL (Domain.Application MVC Project) : Member Controller :
public async Task<ActionResult> Index()
{
var members = await _repository.GetAllEntities();
return View(members);
}
Note : i depended on DbContext to create DB with name like : Domain.DataLayer.Context but it didn't craete DB so i created the DB and passed the connectionString through Context constructor like this :
namespace Domain.DataLayer
{
public class Context : DbContext
{
public Context(): base("InterviewDemo") // i tried also base("name=InterviewDemo")
{
}
public DbSet<Member> Members { get; set; }
public DbSet<Task> Tasks { get; set; }
public DbSet<Commint> TaskHistory { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new MemberConfig());
modelBuilder.Configurations.Add(new TaskConfig());
modelBuilder.Configurations.Add(new CommintConfig());
base.OnModelCreating(modelBuilder);
}
}
}

Entity Framework Code First Many-to-Many relationship and inheritance

Forgive me if this question has been answered somewhere, I have been having a hard time finding a solution for this problem.
I am trying to set up EF Code First on an MVC4 Project. I have a User and Customer that both inherit from Person. I then have a Template object that has a Many-to-Many relationship with Customer and a One-to-Many relationship with User. Here is how I have it set up:
MODELS
public class Person
{
[Key]
public int PersonID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string FullName
{
get
{
return String.Format("{0} {1}", FirstName, LastName);
}
}
public string Email { get; set; }
public virtual List<Template> Templates { get; set; }
}
public class User : Person
{
....
}
public class Customer : Person
{
....
}
public class Template
{
public int TemplateId { get; set; }
public string TemplateName { get; set; }
public virtual List<Customer> Customers { get; set; }
[ForeignKey("User")]
public int UserId { get; set; }
public virtual User User { get; set; }
}
CONTEXT
public class ProjectContext : DbContext
{
public ProjectContext()
: base("name=ProjectDB")
{
}
public DbSet<Template> Templates { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<Customer> Customers { get; set; }
public DbSet<Person> People { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions
.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Template>()
.HasMany(x => x.Customers)
.WithMany(x => x.Templates)
.Map(x => x.MapLeftKey("TemplateId")
.MapRightKey("PersonId")
.ToTable("TemplateCustomer")
);
}
}
If I remove the Person DBSet out of the context this works fine but sets up TPT inheritance. I would like to use TPH inheritance, but when I enable migrations with the Person DBSet in the context it chokes:
NavigationProperty 'Templates' is not valid. Type 'MvcProject.Models.Customer' of FromRole 'Template_Customers_Target' in AssociationType 'MvcProject.Models.Template_Customers' must exactly match with the type 'MvcProject.Models.Person' on which this NavigationProperty is declared on.
Where am I going wrong here?
You cannot inherit navigation properties from a base entity. They always must be declared in the class the other end of the relationship is refering to.
Template.Customers is refering to Customer (not to Person), hence the inverse navigation property Templates must be declared in Customer (not in Person)
Template.User is refering to User (not to Person), hence the inverse navigation property Templates must be declared in User (not in Person)
So, basically you must move the Templates collection from Person into both derived classes:
public class Person
{
// no Templates collection here
}
public class User : Person
{
//...
public virtual List<Template> Templates { get; set; }
}
public class Customer : Person
{
//...
public virtual List<Template> Templates { get; set; }
}
Then you can define the two relationships with Fluent API like so:
modelBuilder.Entity<Template>()
.HasMany(t => t.Customers)
.WithMany(c => c.Templates) // = Customer.Templates
.Map(x => x.MapLeftKey("TemplateId")
.MapRightKey("PersonId")
.ToTable("TemplateCustomer"));
modelBuilder.Entity<Template>()
.HasRequired(t => t.User)
.WithMany(u => u.Templates) // = User.Templates
.HasForeignKey(t => t.UserId);
Change your HasMany selector to People:
modelBuilder.Entity<Template>()
.HasMany(x => x.People) // here
.WithMany(x => x.Templates)
.Map(x => x.MapLeftKey("TemplateId")
.MapRightKey("PersonId")
.ToTable("TemplateCustomer")
);

mapping one-to-many relation with Fluent NHibernate does not contain any child when browsing through one-end

I've created the following domain classes:
public class Car
{
public virtual int Id { get; set; }
public virtual string Registration { get; set; }
public virtual User ResponsibleContact { get; set; }
protected Car()
{}
public Fahrzeug(User responsibleContact, string registration)
{
ResponsibleContact = responsibleContact;
Registration = registration;
ResponsibleContact.Cars.Add(this);
}
}
public class User
{
public virtual int Id { get; set; }
public virtual byte[] EncryptedUsername { get; set; }
public virtual byte[] EncryptedPassword { get; set; }
public virtual IList<Car> Cars { get; private set; }
public virtual string Username
{
get
{
var decrypter = UnityContainerProvider.GetInstance().UnityContainer.Resolve<IRijndaelCrypting>();
return decrypter.DecryptString(EncryptedUsername);
}
}
protected User()
{ }
public User(byte[] encryptedUser, byte[] encryptedPassword)
{
Cars = new List<Car>();
EncryptedUsername = encryptedUser;
EncryptedPassword = encryptedPassword;
}
}
and the mapping classes:
public class CarMap : ClassMap<Car>
{
public CarMap()
{
Id(c => c.Id).GeneratedBy.Native();
Map(c => c.Registration);
References(c => c.ResponsibleContact).Not.Nullable();
}
}
public class UserMap : ClassMap<User>
{
public UserMap()
{
Id(st => st.Id).GeneratedBy.Native();
Map(st => st.EncryptedUsername).Column("Username");
Map(st => st.EncryptedPassword).Column("Password");
HasMany(st => st.Cars).Inverse().AsBag();
}
}
If I query some Member objects, I get the members, but the cars collection is empty!
If I query some Cars I got all the cars with the right Member. But within the member, the cars collection is also empty!
Is there anybody who has an Idea of what can happened?
you have to make sure the foreign key column of the collection and the reference is the same otherwise there is a mismatch.
References(c => c.ResponsibleContact, "ResponsibleContact_id").Not.Nullable();
and
HasMany(st => st.Cars).Inverse().KeyColumn("ResponsibleContact_id");

How to do inheritance in fluent NH without discriminator type column

I have 2 classes:
public class MyBaseClass
{
public virtual int Id { get; set; }
public virtual string BaseProperty { get; set; }
}
public class MyClass : MyBaseClass
{
public virtual string ChildProperty { get; set; }
}
I want to map each of them to its own table (fluent NH). How to do it with no discriminator type column added to [MyBaseClass] table? So I expect [MyBaseClass] table consists of BaseProperty and Id columns only, MyClass consists of Id, BaseProperty and ChildProperty columns.
Thanks
You can try to put IgnoreBase to on MyBaseClass. It will say for FNH to map those classes independently
I've just found this (http://wiki.fluentnhibernate.org/Fluent_mapping#Components):
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Child : Parent
{
public string AnotherProperty { get; set; }
}
If you wanted to map this as a
table-per-subclass, you'd do it like
this:
public class ParentMap : ClassMap<Parent>
{
public ParentMap()
{
Id(x => x.Id);
Map(x => x.Name);
}
}
public class ChildMap : SubclassMap<Child>
{
public ChildMap()
{
Map(x => x.AnotherProperty);
}
}
So looks like this approach does not require any Db changes like adding special fields to my tables. The only problem is I do not know how to do the same in AutoMapping with Override statements. We do mapping this way:
public class AutoMappingConfiguration : DefaultAutomappingConfiguration
{
public override bool IsDiscriminated(Type type)
{
return true;
}
public override bool ShouldMap(Type type)
{
return type.In(typeof(MyBaseClass),typeof(MyClass),...)
}
...
}
FluentNHibernate.Automapping.AutoPersistenceModel Instance =
AutoMap.AssemblyOf<MyBaseClass>(new AutoMappingConfiguration())
.Override<MyBaseClass>(m =>
{
...
}
}
So I'm not sure how to apply SubClass instruction in my case. Any advise?
Thanks.