Creating a DbContext Interface with EF6 and Identity - entity-framework

I want to create an interface for my ApplicationDbContext. However, it inherits from IdentityDbContext.
public class ApplicationDbContext
: IdentityDbContext<ApplicationUser, ApplicationRole, int,
ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>, IDbContext
Currently, IDbContext doesn't have any references to the IdentityDbContext tables such as Users, Roles, etc... So when I use the interface in my code anything that is accessing the Identity tables will not work because the interface doesn't include those. I've tried to add an
IDbSet<ApplicationUser>
to my ApplicationDbContext so that I can reference it in the interface but it gets confusing because ApplicationUser is a custom implementation of IdentityUser. How can I create this interface so that I can reference all the tables that my ApplicationDbContext has access to, but through the interface?

You just need to replicate the properties in your interface. For example:
public interface IDbContext
{
IDbSet<ApplicationUser> Users { get; set; }
IDbSet<ApplicationRole> Roles { get; set; }
// etc
}
You could also abstract the interface to make it more directly compatible with the Identity objects:
public interface IDbContext<TUser, TRole, TKey, TUserLogin, TUserRole, TUserClaim>
where TUser : IdentityUser<TKey, TUserLogin, TUserRole, TUserClaim>
where TRole : IdentityRole<TKey, TUserRole>
where TUserLogin : IdentityUserLogin<TKey>
where TUserRole : IdentityUserRole<TKey>
where TUserClaim : IdentityUserClaim<TKey>
{
IDbSet<TUser> Users { get; set; }
IDbSet<TRole> Roles { get; set; }
// etc
}
And tweak your context:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, int,
ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>,
IDbContext<ApplicationUser, ApplicationRole, int, ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>

Related

EF Core Fluent API, set IsRequired on all entities to generate a non-null db column

I'm working on a Razor pages web app which works directly with a db context...yes this is not ideal but is what I'm stuck with for the time being.
In the data model, each object inherits from a base entity class containing audit data, e.g.:
public class BaseEntity
{
[Key]
public int Id { get; set; }
public DateTime CreatedOn { get; set; }
public string CreatedBy { get; set; }
...etc.
public class Table1 : BaseEntity
{
public string TestItemName { get; set; }
}
In the database, I want CreatedBy to be required (not null), but I don't want to use the [Required] attribute since this will trigger the UI to validate the CreatedBy column. I don't want to expose this column in the UI and instead have service code which updates all of the audit properties based on Add/Insert.
What I'm looking for is a way via Fluent API which will give me the column type in the db that I need, e.g. NVARCHAR(MAX) NOT NULL.
I can accomplish this in the OnModelCreating method in the dbcontext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Table1>()
.Property(o => o.CreatedBy)
.IsRequired();
However this would require me to create a similar entry for every table in the model.
Is there code I can use in OnModelCreating which could accomplish this for all entities? Something like this (this is just pseudo-code, but looking to give an idea):
var entityTypes = modelBuilder.Model.GetEntityTypes().Select(o => o.GetType()).ToList();
entityTypes.ForEach(e =>
{
e.Property("CreatedBy").IsRequired();
});
Implement your entity configurations in discrete classes that implement IEntityTypeConfiguration. Your implementations should inherit from a base implementation that configures BaseEntity and the Configure method should be virtual with overriding implementations calling the base class' method:
public abstract class BaseEntityConfiguration<TEntity>
: IEntityTypeConfiguration<TEntity>
where TEntity : BaseEntity
{
public virtual void Configure(EntityTypeBuilder<TEntity> builder)
{
builder.Property(be => be.CreatedBy)
.IsRequired();
// etc
}
}
public class SomeEntityConfiguration : BaseEntityConfiguration<SomeEntity>
{
public override void Configure(EntityTypeBuilder<SomeEntity> builder)
{
// call base class method to configure BaseEntity properties
base.Configure(builder);
// configure remaining SomeEntity-specific properties/etc
builder.TestItemName.IsRequired();
}
}
You'll need to inform the model builder to use your configuration classes. For example, if your config classes are in the same assembly as your DbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(
typeof(YourDbContext).Assembly);
}

Avoid 'Discriminator' with AspNetUsers, AspNetRoles, & AspNetUserRoles

I am extending IdentityUser, IdentityUserRole, and IdentityRole like this:
public class ApplicationUser : IdentityUser
{
public string FullName { get; set; }
public virtual ICollection<ApplicationIdentityUserRole> Roles { get; } = new List<ApplicationIdentityUserRole>();
}
public class ApplicationIdentityUserRole : IdentityUserRole<string>
{
public virtual ApplicationUser User { get; set; }
public virtual ApplicationRole Role { get; set; }
}
public class ApplicationRole : IdentityRole
{
public virtual ICollection<ApplicationIdentityUserRole> Roles { get; } = new List<ApplicationIdentityUserRole>();
}
and configured like:
public class SmartAccountingSetUpContext : IdentityDbContext<ApplicationUser>
{
public SmartAccountingSetUpContext(DbContextOptions<SmartAccountingSetUpContext> options)
: base(options)
{
}
public DbSet<ApplicationUser> Users { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Ignore<RegistrationViewModel>();
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
builder.Entity<ApplicationUser>().ToTable("AspNetUsers");
builder.Entity<ApplicationIdentityUserRole>().ToTable("AspNetUserRoles");
builder.Entity<ApplicationRole>().ToTable("AspNetRoles");
builder.Entity<ApplicationIdentityUserRole>()
.HasOne(p => p.User)
.WithMany(b => b.Roles)
.HasForeignKey(p => p.UserId);
builder.Entity<ApplicationIdentityUserRole>()
.HasOne(x => x.Role)
.WithMany(x => x.Roles)
.HasForeignKey(p => p.RoleId);
}
}
I keep getting this:
"Invalid column name 'Discriminator'.\r\nInvalid column name 'Discriminator'.\r\nInvalid column name 'Discriminator'.\r\nInvalid column name 'Discriminator'."
I understand if you have derived class, then you have to specify the HasDiscriminitor in OnModelCreating method. However IdentityUser, IdentityUserRole, and IdentityRole are no abstract classes.
How can I get past this?
Your context is inheriting IdentityDbContext<TUser> which in turn inherits IdentityDbContext<TUser, IdentityRole, string>. TUser in this case is your ApplicationUser, but the role type is IdentityRole.
Thus the base class fluent configuration registers IdentityRole as entity. When you register the derived ApplicationRole as entity, EF Core treats that as TPH (Table Per Hierarchy) Inheritance Strategy which is implemented with single table having Discriminator column.
To fix the issue, simply use the proper base generic IdentityDbContext. Since you also have a custom IdentityUserRole derived type, you should use the one with all generic type arguments - IdentityDbContext<TUser,TRole,TKey,TUserClaim,TUserRole,TUserLogin,TRoleClaim,TUserToken>:
public class SmartAccountingSetUpContext : IdentityDbContext
<
ApplicationUser, // TUser
ApplicationRole, // TRole
string, // TKey
IdentityUserClaim<string>, // TUserClaim
ApplicationIdentityUserRole, // TUserRole,
IdentityUserLogin<string>, // TUserLogin
IdentityRoleClaim<string>, // TRoleClaim
IdentityUserToken<string> // TUserToken
>
{
// ...
}

EntityFramework how to not map a class but do map it's inherited properties

We use EntityFramework 6.1 with CodeFirst in our web mvc application (StdWebApp). Now we want to make a new custom version of this application (CustomWebApp) .
The CustomWebApp will use most of the code of the standard one, in it's domain model it will extend the Person class.
In CustomDomain we make implement a new DbContext that must connect with the database of the custom app (CustomSqlDb).
In (C#) code there is no problem that there is a Person in Domain and in CustomDomain. However we have not been able to devise a mapping for Person in the Custom DbContext that will:
Create a single "Person" table.
Contains fields form "CustomDomain.Person" AND those from "Domain.Person".
We tried some variants like this:
modelBuilder.Entity<Person>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Person");
}
);
using this document as our inspiration msdn mapping types
But EF complains about the simple name beeing equal.
Obviously we could rename the "Person" in "CustomDomain" to "PersonCustom" but that could lead to a lot of silly names if we have to do this again in the future like "PersonCustomExtraSpecial" etc.
Thoughts anyone?
UPDATE
we tried the solution suggested by mr100, here is the complete code:
namespace Domain
{
public class Person
{
public int Id { get; set; }
public string Stuff { get; set; }
}
}
namespace CustomDomain
{
public class Person : Domain.Person
{
public string ExtraStuff { get; set; }
}
}
namespace CustomDomain
{
public class DbModel : DbContext
{
DbSet<CustomDomain.Person> Persons { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<CustomDomain.Person>().Map(m => m.ToTable("Person"));
}
}
}
This still result in the error
The type 'CustomDomain.Person' and the type 'Domain.Person' both have the same simple name of 'Person' and so cannot be used in the same model. All types in a given model must have unique simple names. Use 'NotMappedAttribute' or call Ignore in the Code First fluent API to explicitly exclude a property or type from the model.
So we added the following code:
namespace CustomDomain
{
public class DbModel : DbContext
{
DbSet<CustomDomain.Person> Persons { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Ignore<Domain.Person>();
modelBuilder.Entity<CustomDomain.Person>().Map(m => m.ToTable("Person"));
}
}
}
Still same result.
To achieve this your DbContext class in CustomWebApps should have property People defined like this:
public DbSet<CustomDomain.Person> People {get; set;}
and no property:
public DbSet<Domain.Person> People {get; set;}
even if it comes from StdWebApp DbContext class from which CustomWebApp DbContext class may derive (if that is the case for you). Additionally you may set properly table name:
modelBuilder.Entity<Person>().ToTable("Person");

Entity Framework 6 change datacolumn name at runtime

I'm working on a existing SQL database. I created a complex type "Address" for one of my entities and I use the attribute "ColumnAttribute" to define the mapping in my table.
It works fine.
Now, I want to reuse my complex type "Address" for a another table.
But, for existing reasons, the names of column are differents.
How do I change the column names for this specific table without the use of inheritance ? Or having to create two classes of type Address for that.
public sealed class class1
{
public Adresse Adresse{get;set;}
}
public sealed class class2
{
// Need to change all ColumnAttributes for this specific class
public Adresse Adresse{get;set;}
}
public sealed class Adresse : IAdresse
{
#region IAdresse Membres
[Column( "ADRESSE" )]
public string Adresse1 { get; set; }
[Column( "CODE_POSTAL" )]
public string CodePostal { get; set; }
[Column( "VILLE" )]
public string Ville { get; set; }
#endregion
}
Thanks for your help.
I dont have a suitable test ready.
Assuming Complex Type is working correctly as you say.
I created a complex type "Address" .... It works fine.
Try Fluent API you might be able to override.
public class YourDbContext : DbContext {
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
// attempt override standard name of complex property
modelBuilder.Entity<Class2>().Property(t=>t.Adresse.Adresse1).HasColumnName("BetterName");
}
}

Interface inheritance issues in one-to-many relationship in Entity Framework

I have a domain model specifying the interfaces or my domain and I'm using DI to hook it up to an entity framework 4 repository implementation. In my domain I have the following:
public interface IInboundGateway : IGateway
{
ICollection<IInboundNumber> InboundNumbers { get; set; }
}
I then have my entity framework model that has generated the InboundGateway class:
public partial class InboundGateway : EntityObject
{
public EntityCollection<InboundNumber> InboundNumbers { get; set; }
}
In order to implement the IInboundGateway inteface I created a partial InboundGateway class.
public partial class InboundGateway : IInboundGateway
{
}
Eventhough EntityCollection<> implements ICollection<> and InboundNumber implements IInboundNumber I am getting an error reporting that InboundGateway does not implement interface IInboundGateway.InboundNumbers because InboundGateway.InboundNumbers does not have the matching return type ICollection<IInboundNumber>
Im pretty certain this is mental as EntityCollection does implement ICollection and InboundNumber does implement IInboundNumber.
Any help would be massively appreciated thanks.
You have to be aware that EntityCollection<InboundNumber> is a sub type of ICollection<InboundNumber> but is NOT a subtype of ICollection<IInboundNumber>. These are 2 different types and are not related.
so in the entity object class you have:
public EntityCollection<InboundNumber> InboundNumbers { get; set; }
While the comopiler expects you to have:
public ICollection<IInboundNumber> InboundNumbers { get; set; }
If you could turn your EntityObjects to POCO, part of the problem would be solved since POCO classes using ICollection for their navigation properties by default. Also, you need to change your interface like this:
public interface IInboundGateway : IGateway {
ICollection InboundNumbers { get; set; }
}