Entity Framework - TPC - Override Primary Key Column Name - entity-framework

Here's my model:
public abstract class Entity
{
public long Id { get; set; }
}
public abstract class Audit : Entity
{}
public class UserAudit : Audit
{
public virtual User User { get; set; }
}
public class User : Entity
{}
Here's my DbContext:
public class TestDbContext : DbContext
{
static TestDbContext()
{
Database.DefaultConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0");
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new AuditConfiguration());
modelBuilder.Configurations.Add(new UserAuditConfiguration());
modelBuilder.Configurations.Add(new UserConfiguration());
}
}
And here's my mappings:
public abstract class EntityConfiguration<T> : EntityTypeConfiguration<T>
where T : Entity
{
protected EntityConfiguration()
{
HasKey(t => t.Id);
Property(t => t.Id)
.HasColumnName("Key");
}
}
public class AuditConfiguration : EntityConfiguration<Audit>
{}
public class UserAuditConfiguration : EntityTypeConfiguration<UserAudit>
{
public UserAuditConfiguration()
{
Map(m =>
{
m.MapInheritedProperties();
m.ToTable("UserAudits");
});
HasRequired(u => u.User)
.WithMany()
.Map(m => m.MapKey("UserKey"));
}
}
public class UserConfiguration : EntityConfiguration<User>
{}
When I try to generate a migration for this model, I get the following error:
error 2010: The Column 'Id' specified as part of this MSL does not exist in MetadataWorkspace.
If I comment out the ".HasColumnName" call in the constructor of EntityConfiguration, the migration generates correctly (except of course that the column name is Id and not Key).
Is TPC mappings supposed to support primary key columns that don't use the default column name?

The problem appears to be that Entity Framework does not expect me to map Audit. If I remove AuditConfiguration, this works as expected.

Related

Entity Framework Core (6.0.8) indirect Many to Many relationship with 2 db contexts

I am unable to generate a migration for a many to many relationship that spans 2 different db contexts.
I have 3 entities:
AccountApp, SubscriptionDetail, SubscribedAccountApp
AccountApp and SubscribedAccountApp are the AccountDbContext while the SubscriptionDetail is in a SubscriptionsDbContext.
I want to set up an indirect many to many relationship as described here
Based on the documentation, I have created an EntityTypeConfig for the SubscribedAccountApp like this
public class SubscribedAccountAppConfig : IEntityTypeConfiguration<SubscribedAccountApp>
{
public void Configure(EntityTypeBuilder<SubscribedAccountApp> builder)
{
builder.ToTable("subscribed_account_apps");
builder.HasKey(m => new { m.SubscriptionDetailId, m.AccountAppId });
builder.HasOne(x => x.SubscriptionDetail)
.WithMany(x => x.SubscribedAccountApps)
.HasForeignKey(x => x.SubscriptionDetailId);
builder.HasOne(x => x.AccountApp)
.WithMany(x => x.SubscribedAccountApps)
.HasForeignKey(x => x.AccountAppId);
}
}
This config is then applied to the AccountDbContext like so
public class AccountDbContext : DbContext
{
public IQueryable<AccountApp> AccountApps { get; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new AccountAppConfig());
modelBuilder.ApplyConfiguration(new SubscribedAccountAppConfig());
}
}
The data model for SubsriptionsDetails looks like:
public class SubscriptionDetail
{
private readonly List<SubscribedAccountApp> _subscribedAccountApps;
private readonly List<ConsumptionComponent> _consumptionComponents;
public SubscriptionDetail(
Guid id,
IEnumerable<ConsumptionComponent> consumptionComponents = null,
IEnumerable<SubscribedAccountApp> subscribedAccountApps = null)
{
Id = id;
_consumptionComponents = consumptionComponents ?? new List<ConsumptionComponent>();
_subscribedAccountApps = subscribedAccountApps ?? new List<SubscribedAccountApp>();
}
public Guid Id { get; set; }
public IReadOnlyCollection<ConsumptionComponent> ConsumptionComponents => _consumptionComponents;
public IReadOnlyCollection<SubscribedAccountApp> SubscribedAccountApps => _subscribedAccountApps;
}
The data model for AccountApp looks like:
public class AccountApp
{
public readonly List<SubscribedAccountApp> _subscribedAccountApps;
public AccountApp(Guid id,
Guid accountId,
IEnumerable<SubscribedAccountApp> subscribedAccountApps = null)
{
Id = id;
_subscribedAccountApps = subscribedAccountApps.ToNavigationProperty();
}
public Guid Id { get; private set; }
public IReadOnlyCollection<SubscribedAccountApp> SubscribedAccountApps => _subscribedAccountApps;
}
This leaves me an error when I try to create a migration for the AccountDbContext:
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
System.InvalidOperationException: No suitable constructor was found for entity type 'ConsumptionComponent'.
This error describes a ConsumptionComponent constructor for some reason, although this model has already existed and nothing has changed on that model. This model does appear as an IEnumerable in the SubscriptionDetail constructor but I am not sure why it is showing up as an error. All I want this migration to do is add support for the indirect-many-to-many-relationship which has nothing to do with ConsumptionComponent.

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
>
{
// ...
}

InvalidOperationException: The entity type 'Enrollments' requires a primary key to be defined

I am new to Asp.Net Core (Even to Asp.Net and web). I am using Asp.Net Core 2 with MySQL, using Pomelo.EntityFrameWorkCore.MySql (2.0.1) driver. I just created a custom dbcontext with Courses and Enrollments table, along with the default created ApplicationDbContext. The Primary Key for Enrollments is a composite key, comprising of UserId and CourseId. Below is the code :
public class CustomDbContext : DbContext
{
public virtual DbSet<Courses> Courses { get; set; }
public virtual DbSet<Enrollments> Enrollments { get; set; }
public CustomDbContext(DbContextOptions<CustomDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Courses>(entity =>
{
entity.ToTable("courses");
entity.HasIndex(e => e.Name)
.HasName("Coursescol_UNIQUE")
.IsUnique();
entity.Property(e => e.Id).HasColumnType("int(11)");
entity.Property(e => e.Duration).HasColumnType("time");
entity.Property(e => e.Name).HasMaxLength(45);
});
modelBuilder.Entity<Enrollments>(entity =>
{
entity.HasKey(e => new { e.UserId, e.CourseId });
entity.ToTable("enrollments");
entity.HasIndex(e => e.CourseId)
.HasName("fk_Courses_Enrollments_CourseId_idx");
entity.HasIndex(e => e.UserId)
.HasName("fk_Users_Enrollments_CourseId_idx");
entity.HasIndex(e => new { e.UserId, e.CourseId })
.HasName("UniqueEnrollment")
.IsUnique();
entity.Property(e => e.CourseId).HasColumnType("int(11)");
entity.HasOne(d => d.Course)
.WithMany(p => p.Enrollments)
.HasForeignKey(d => d.CourseId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("fk_Courses_Enrollments_CourseId");
entity.HasOne(d => d.User)
.WithMany(p => p.Enrollments)
.HasForeignKey(d => d.UserId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("fk_Users_Enrollments_UserId");
});
}
}
The Program.cs goes like :
public static void Main(string[] args)
{
var host = BuildWebHost(args);
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<CustomDbContext>();
context.Database.EnsureCreated();
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred while seeding the database.");
}
}
host.Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
The configure services method in Startup.cs goes like :
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseMySql(Configuration.GetConnectionString("DefaultConnection")));
services.AddDbContext<CustomDbContext>(options =>
options.UseMySql(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// Add application services.
services.AddTransient<IEmailSender, EmailSender>();
services.AddMvc();
}
The Courses Model goes like :
public partial class Courses
{
public Courses()
{
Enrollments = new HashSet<Enrollments>();
}
public int Id { get; set; }
public string Name { get; set; }
public TimeSpan? Duration { get; set; }
public ICollection<Enrollments> Enrollments { get; set; }
}
The Enrollments Model goes like :
public partial class Enrollments
{
public string UserId { get; set; }
public int CourseId { get; set; }
public Courses Course { get; set; }
public ApplicationUser User { get; set; }
}
The applicationUser model goes like :
public ApplicationUser()
{
Enrollments = new HashSet<Enrollments>();
}
public ICollection<Enrollments> Enrollments { get; set; }
Now, here's what I've tried so far :
If i add Course and Enrollment model to the ApplicationDBContext, then everything goes fine.
If in CustomDBContext i have a non-composite primary Key, even then it works fine. (I just tried another example)
Can somebody please throw some light on why is this error ? Is this the intended way to handle such a case ?
Thanks in advance.
It's because the Enrollments entity has been discovered by ApplicationDbContext through ApplicationUser.Enrollments navigation property. This is explained in the Including & Excluding Types - Conventions section of the EF Core documentation:
By convention, types that are exposed in DbSet properties on your context are included in your model. In addition, types that are mentioned in the OnModelCreating method are also included. Finally, any types that are found by recursively exploring the navigation properties of discovered types are also included in the model.
I guess now you see the problem. The Enrollments is discovered and included in the ApplicationDbContext, but there is no fluent configuration for that entity there, so EF uses only the default conventions and data annotations. And of course composite PK requires fluent configuration. And even there wasn't a composite PK, it's still incorrect to ignore the existing fluent configuration. Note that Courses is also included in the ApplicationDbContext by the aforementioned recursive process (through Enrollments.Courses navigation property). Etc. for other referenced classes.
Note that the same applies in the other direction. ApplicationUser and all referenced from it are discovered and included in the CustomDbContext w/o their fluent configuration.
The conclusion - don't use separate contexts containing interrelated entities. In your case, put all the entities in the ApplicationDBContext.

EF Migration Clear Cache

public class A
{
public int Id { get; set; }
public virtual ICollection<AHistory> AHistorys { get; set; }
}
public class AHistory
{
public int AId { get; set; }
public virtual A A {get; set; }
}
I renammed AHistories to AHistory.
add-migration HistoMig
AHistories: EntityType: EntitySet 'AHistories' is based on type 'AHistory' that has no keys defined.
So the error mention an old name that no longer exists in the solution.
What should I do ?
I've already clean Visual Studio Solution with no effects.
I also tried to comment out navigation property, add migration, rollback migration, uncomment then add migration ; I still get this erros.
I've done a search through VS solution on String "AHistories" with 0 occurence found.
Based on ESG comment, I've added :
public class DefaultContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new AHistoryMapping());
public DbSet<AHistory> AHistorys { get; set; }
}
}
public class AHistoryMapping : EntityTypeConfiguration<AHistory>
{
public AHistoryMapping()
{
HasKey(ah => ah.AId);
}
}
It works now !
Edit
Actually it doesn't "work"... It compiles but the result is not what I was expecting.
This code creates a table with a unique identifier named FundId and a foreign key named Fund_Id.
I've change my code to
public class AHistoryMapping : EntityTypeConfiguration<AHistory>
{
public AHistoryMapping()
{
HasRequired(ah => ah.A)
.WithMany(a => a.AHistorys)
.HasForeignKey(ah => ah.AId);
}
}
It doesn't compile anymore. I get the message again.
AHistory: : EntityType 'AHistory' has no key defined. Define the key for this EntityType.
AHistorys: EntityType: EntitySet 'AHistorys' is based on type 'AHistory' that has no keys defined.
Edit 2
It turns out that EF require a Primary Key. Here is the solution :
public class AHistoryMapping : EntityTypeConfiguration<AHistory>
{
public AHistoryMapping()
{
HasKey(ah => new { ah.AId, Ah.Date });
HasRequired(ah => ah.A)
.WithMany(a => a.AHistorys)
.HasForeignKey(ah => ah.AId);
}
}

EF5 Code first TPH Mapping error using DBSet.Find()

When using Entity Framework 5 Code First, with Table Per Hierarchy.
This combined with a Repository and Unit of Work (tried several implementations).
I'm having the following error:
(34,10) : error 3032: Problem in mapping fragments starting at lines 19, 34:EntityTypes T, T are being mapped to the same rows in table T. Mapping conditions can be used to distinguish the rows that these types are mapped to.
I have resolved this issue using the following guide:
Entity Framework 4.3 - TPH mapping and migration error
This works when using a general look-up of all records, then no errors.
When using the DBSet<T>.Find(id), I receive the above error message.
When using DBSet<T>.Where(t => t.id == id) all works fine.
Please does anyone have the solution for this problem?
public class TDataContext : DbContext
{
// Models
public abstract class BaseTrackable
{
public DateTime DateModified { get; set; }
}
public abstract class ParentClass : BaseTrackable
{
public int ParentId { get; set; }
public string ParentString { get; set; }
}
public class Foo : ParentClass
{
public string FooString { get; set; }
}
public class Bar : ParentClass
{
public string BarString { get; set; }
}
// Configuration
public class ParentConfiguration : EntityTypeConfiguration<ParentClass>
{
public ParentConfiguration()
{
ToTable("Parent");
}
}
public class FooConfiguration : EntityTypeConfiguration<Foo>
{
public FooConfiguration()
{
Map(m => m.Requires("FooIndicator").HasValue(true));
}
}
public class BarConfiguration : EntityTypeConfiguration<Bar>
{
public BarConfiguration()
{
Map(m => m.Requires("BarIndicator").HasValue(true));
}
}
public DbSet<ParentClass> Parent { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations
.Add(new ParentConfiguration())
.Add(new FooConfiguration())
.Add(new BarConfiguration());
}
}
public class Controller
{
TDataContext _context = new TDataContext();
// Repository function
public T GetById<T>(object id) where T : class
{
var dbset = _context.Set<T>();
return dbset.Find(id);
}
public IQueryable<TDataContext.Foo> GetFiltered(Expression<Func<TDataContext.Foo, bool>> filter)
{
var dbset = _context.Set<TDataContext.Foo>();
return dbset.Where(filter);
}
// Final call
// Which fails..
public TDataContext.Foo Get(int id)
{
return this.GetById<TDataContext.Foo>(id);
}
// This works...
public TDataContext.Foo GetWhere(int id)
{
return this.GetFiltered(f => f.ParentId == id).FirstOrDefault();
}
}
Found something that solves my problem partially...
When adding another indicator to the tables, there is no more error, example:
public class FooConfiguration : EntityTypeConfiguration<Foo>
{
public FooConfiguration()
{
Map(m => {
m.Requires("FooIndicator").HasValue(true);
m.Requires("BarIndicator").HasValue<short>(1);
});
}
}
public class BarConfiguration : EntityTypeConfiguration<Bar>
{
public BarConfiguration()
{
Map(m => {
m.Requires("BarIndicator").HasValue(true);
m.Requires("FooIndicator").HasValue<short>(0);
});
}
}
Wouldn't be better
public class FooConfiguration : EntityTypeConfiguration<Foo>
{
public FooConfiguration()
{
Map(m => m.Requires("Type").HasValue("Foo"));
}
}
public class BarConfiguration : EntityTypeConfiguration<Bar>
{
public BarConfiguration()
{
Map(m => m.Requires("Type").HasValue("Bar");
}
}
In this way FooConfiguration doesn't need to know anything about BarConfiguration and visa versa. I had this issue when migrating from EF 4.3 to 5.0 and I think what has changed was the discriminator database columns are not nullable in EF 5.0. I think it makes much more sense for them to be not nullable and in general it might be better to have only one discrimanotor column for each derived type as opposed to one column per type (as it was in EF 4.3)
-Stan