EF and existing database and table, with a bunch of new tables.
Context:
An old database has a Table User that the new application will use.
I used the entity DataModel Wizard to Map it this .
What I have now :
A bunch of new entities and their configuration:
public class Bundle
{
public Bundle() { Produits = new HashSet<Produit>(); }
public int BundleID { get; set; }
public string Designation { get; set; }
public ICollection<Produit> Produits { get; set; }
}
public class BundleConfiguration : EntityTypeConfiguration<Bundle>
{
public BundleConfiguration()
{
ToTable("PL_Bundle");
HasKey(e => e.BundleID);
Property(e => e.Designation).HasMaxLength(200);
}
}
A DbContext with an extension using reflection to map all available configuration.
public partial class FooBarDbContext : DbContext
{
public FooBarDbContext() : base("name=Model1")
=> Database.SetInitializer<FooBarDbContext>(null);
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.ApplyAllConfigurations();
base.OnModelCreating(modelBuilder);
}
public DbSet<User> User { get; set; }
public DbSet<Bundle> Bundles { get; set; }
// [...]
}
public static class ModelBuilderExtensions
{
public static void ApplyAllConfigurations(this DbModelBuilder modelBuilder)
{
IEnumerable<Type> typesToRegister
= AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(t => t.GetTypes())
.Where(t => t.IsClass)
.Where(type => !string.IsNullOrEmpty(type.Namespace) &&
type.GetTypeInfo().BaseType != null &&
type.GetTypeInfo().BaseType.GetTypeInfo().IsGenericType &&
type.GetTypeInfo().BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>))
.ToArray();
foreach (var type in typesToRegister)
{
dynamic configurationInstance = Activator.CreateInstance(type);
modelBuilder.Configurations.Add(configurationInstance);
}
}
}
In the calling application I am trying to test the Database and table to check everything works..
static void Main(string[] args)
{
var db = new FooBarCore.DbContext.FooBarDbContext();
var all = db.User.ToList(); // Work
var bundles1 = db.Bundles.ToList();
The previous line give me error either :
Nom d'objet 'dbo.PL_Bundle' non valide. => Invalid object name 'dbo.PL_Bundle'
What I tried:
Finding if ModelBuilderExtensions was an issue. But using classical modelBuilder.Configurations.Add(new BundleConfiguration());.
Or using fluent API directly in the OnModelCreating throw the same error.
Check the database => Table are not here.
Search for EF not create table, but CreateDatabaseIfNotExist, DropCreateDatabaseWhenModelChanges, DropCreateDatabaseAlways are scary, they all have this Drop Database in the name.
so, as stated in comment:
Nom d'objet 'dbo.PL_Bundle' non valide. => Invalid object name
'dbo.PL_Bundle'
this means, that EF thinks that there should be a table PL_Bundle, but there isn't. It isn't there, because you added new class into your model.
For EF to make proper change to the database, you should mechanism called Migrations
You have to enable migrations, generate migration and apply it to the database.
Good practice is to store the migrations also as SQL scripts (by using UpdateDatabase -script) and make system admin apply them manually to prevent any secret datalosses if you remove a column or a table for example.
You ship the scripts with new versions of software and it should crash until the migration is applied, stating in logs which one is needed.
Update your DbContext class to this:
public partial class FooBarDbContext : DbContext
{
public FooBarDbContext() : base("name=Model1")
=> Database.SetInitializer<FooBarDbContext>(null);
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//Configurations for Bundle class
modelBuilder.Entity<Bundle>()
.ToTable("PL_Bundle")
.HasKey(e => e.BundleID);
.Property(e => e.Designation).HasMaxLength(200);
modelBuilder.ApplyAllConfigurations();
base.OnModelCreating(modelBuilder);
}
public DbSet<User> User { get; set; }
public DbSet<Bundle> Bundles { get; set; }
// [...]
}
Related
Does Entity Framework support generic relations?
E.g.
public class Comment<T> : Entity
where T : Entity
{
public string Text { get; set; }
public long EntityId { get; set; }
public T Entity { get; set; }
}
public class DocumentComment : Comment<Document> {
}
public class DeliveryComment : Comment<Delivery> {
}
UPDATE: I'll expand on my process as I tried this.
I tried creating configurations to make it work as I figured I needed to specify the types of variables but got an error when trying to add the migration.
public class CommentConfiguration<TU,T> : IEntityTypeConfiguration<TU>
where TU : Comment<T>
where T : Entity
{
public Configure(EntityTypeBuilder<TU> builder)
{
builder.ToTable(nameof(Comment));
builder.Property(x => x.Id);
builder.HasKey(x => x.Id);
builder.HasOne(x => x.Entity)
.WithMany()
.HasForeignKey(x => x.EntityId)
.OnDelete(DeleteBehavior.ClientCascade)
}
}
public class DocumentCommentConfiguration : CommentConfiguration<DocumentComment,Document>
{
public Configure(EntityTypeBuilder<DocumentComment> builder)
{
}
}
public class DeliveryCommentConfiguration : CommentConfiguration<DeliveryComment,Delivery>
{
public Configure(EntityTypeBuilder<DeliveryComment> builder)
{
}
}
Actually using conventions based setup worked just fine for me, so (at least based on provided info) you don't need to provide IEntityTypeConfiguration's.
If you still want to then (apart from the compilation issues, cause currently provided code is not compliable) you need to fix the table name in CommentConfiguration<TU,T> (by default you can't map different entity types to the same table). For example:
public class CommentConfiguration<TU,T> : IEntityTypeConfiguration<TU>
where TU : Comment<T>
where T : Entity
{
public Configure(EntityTypeBuilder<TU> builder)
{
builder.ToTable(typeof(TU).Name);
// ... rest of the config
}
}
Full code sample used for testing.
Also can be useful - inheritance in EF Core.
I think I am just doing something silly here.
I have used code first entity framework with asp.net identity and I set up a custom user like this:
public class User : IdentityUser, IKey<string>
{
[MaxLength(100)] public string JobTitle { get; set; }
[MaxLength(100)] public string Image { get; set; }
[MaxLength(100)] public string FirstName { get; set; }
[MaxLength(100)] public string LastName { get; set; }
}
then I updated my DbContext to match:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// 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<User>(m => m.ToTable("Users"));
builder.Entity<IdentityRole>(m => m.ToTable("Roles"));
builder.Entity<IdentityRoleClaim<string>>(m => m.ToTable("RoleClaims"));
builder.Entity<IdentityUserClaim<string>>(m => m.ToTable("UserClaims"));
builder.Entity<IdentityUserLogin<string>>(m => m.ToTable("UserLogins"));
builder.Entity<IdentityUserRole<string>>(m => m.ToTable("UserRoles"));
builder.Entity<IdentityUserToken<string>>(m => m.ToTable("UserTokens"));
}
All works, and the tables were created successfully.
Now I want to do the same for roles, except this time I don't need extra columns (it's the interface that is important here) so I create a new Role class:
public class Role: IdentityRole, IKey<string>
{
}
I then changed my OnModelCreating method to this:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// 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<User>(m => m.ToTable("Users"));
builder.Entity<Role>(m => m.ToTable("Roles"));
builder.Entity<IdentityRoleClaim<string>>(m => m.ToTable("RoleClaims"));
builder.Entity<IdentityUserClaim<string>>(m => m.ToTable("UserClaims"));
builder.Entity<IdentityUserLogin<string>>(m => m.ToTable("UserLogins"));
builder.Entity<IdentityUserRole<string>>(m => m.ToTable("UserRoles"));
builder.Entity<IdentityUserToken<string>>(m => m.ToTable("UserTokens"));
}
The only line that changed was builder.Entity<Role>(m => m.ToTable("Roles"));.
When I run add-migration RoleChange I expected nothing to have changed since my last migration, but instead I get this error:
The entity type 'Role' cannot be mapped to a table because it is derived from 'IdentityRole'. Only base entity types can be mapped to a table.
Does anyone know why?
I don't understand why User works, but Role won't....
Here is the full context:
public class DatabaseContext : IdentityDbContext<User>
{
public DbSet<Claim> Claims { get; set; }
/// <summary>
/// For testing only
/// </summary>
public DatabaseContext()
{
}
// ReSharper disable once SuggestBaseTypeForParameter
public DatabaseContext(DbContextOptions<DatabaseContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// 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<User>(m => m.ToTable("Users"));
builder.Entity<Role>(m => m.ToTable("Roles"));
builder.Entity<IdentityRoleClaim<string>>(m => m.ToTable("RoleClaims"));
builder.Entity<IdentityUserClaim<string>>(m => m.ToTable("UserClaims"));
builder.Entity<IdentityUserLogin<string>>(m => m.ToTable("UserLogins"));
builder.Entity<IdentityUserRole<string>>(m => m.ToTable("UserRoles"));
builder.Entity<IdentityUserToken<string>>(m => m.ToTable("UserTokens"));
}
}
What you have done is correct, you just need to update this line
public class DatabaseContext : IdentityDbContext<User>
as below :
public class DatabaseContext : IdentityDbContext<User, Role, string>
This may seem like a duplicate question EF Core One-to-Many relationship list returns null, but the answer to that question didn't help me. My situation:
public class Section
{
public int Id { get; set; }
// ...
public IEnumerable<Topic> Topics { get; set; }
}
public class Topic
{
public int Id { get; set; }
// ...
public int SectionId { get; set; }
public Section Section { get; set; }
}
But I have not implemented the OnModelCreating method in DbContext because in that case, errors occurs with users identity. There are topics in the database with the specified SectionId. But no matter how I try to get the section, I get null in the Topics property. For example:
var section = _dbContext.Sections.Include(s => s.Topics).FirstOrDefault(s => s.Id == id);
What is the reason for this problem? Have I declared something wrong? Or maybe there is a problem in creating a topic?
UPDATE
I tried to override the OnModelCreating method this way:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Section>()
.HasMany(s => s.Topics)
.WithOne(t => t.Section);
base.OnModelCreating(modelBuilder);
}
And this way:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Section>()
.HasMany(s => s.Topics)
.WithOne(t => t.Section)
.HasForeignKey(prop => prop.SectionId);
base.OnModelCreating(modelBuilder);
}
I also added the virtual attribute to the dependencies again. It did not help. Added a test migration (thought there might be something wrong with the database structure), but the migration was empty.
SOLUTION
As a result, I solved the problem with a crutch:
var section = _dbContext.Sections.Include(s => s.Topics).FirstOrDefault(s => s.Id == id);
if (section == null)
{
return Error();
}
section.Topics = _dbContext.Topics.Where(t => t.SectionId == section.Id).Include(t => t.Author).ToList();
foreach(var topic in section.Topics)
{
topic.Author = _dbContext.Users.FirstOrDefault(u => u.Id == topic.AuthorId);
topic.Posts = _dbContext.Posts.Where(t => t.TopicId == topic.Id).ToList();
}
As you can see, I had to explicitly get data from the dbContext and assign them to the appropriate properties. Include method calls can be deleted because they do not perform the desired action.
Several suggestions:
Try to make your Section define Topics as ICollection rather than IEnumerable and virtual so that they can be lazy loaded if necessary
public class Section
{
public int Id { get; set; }
// ...
public virtual ICollection<Topic> Topics { get; set; }
}
In your solution sample, you should be able to leverage EF Core's ThenInclude method to get the Sections, Topics, and Authors in one fell swoop:
var section = _dbContext.Sections
.Include(s => s.Topics)
.ThenInclude(t => t.Authors)
.FirstOrDefault(s => s.Id == id);
However to get the collection of authors and posts in the same child collection, you may want to consider a custom projection into a new type. EF Core 3.1 should piece all of this into a single query. Earlier versions of Core would break it apart into multiple database queries depending on the version and exact syntax you used. Something like:
var items =
from section in _dbContext.Sections
from topic in section.Topics
select new {
section.Name,
topic.Description,
Authors = topic.Authors.ToList(),
Posts = topic.Posts.ToList()
};
Following the guide on this link https://www.learnentityframeworkcore.com/lazy-loading
Install the Microsoft.EntityFrameworkCore.Abstractions package into the project containing your model classes:
[Package Manager Console]
install-package Microsoft.EntityFrameworkCore.Abstractions
[Dotnet CLI]
add package Microsoft.EntityFrameworkCore.Abstractions
Alter the principal entity to include
a using directive for Microsoft.EntityFrameworkCore.Infrastructure
a field for the ILazyLoader instance
an empty constructor, and one that takes an ILazyLoader as a parameter (which can be private, if you prefer)
a field for the collection navigation property
a getter in the public property that uses the ILazyLoader.Load method
using Microsoft.EntityFrameworkCore.Infrastructure;
public class Author
{
private readonly ILazyLoader _lazyLoader;
public Author()
{
}
public Author(ILazyLoader lazyLoader)
{
_lazyLoader = lazyLoader;
}
private List<Book> _books;
public int AuthorId { get; set; }
public List<Book> Books
{
get => _lazyLoader.Load(this, ref _books);
set => _books = value;
}
}
The solution in the answer will not work in case of many-to-many relationship.
Seniors,
I'm using ASP.NET Identity on my ASP.Net web application and I Add my custom Tables(with their relations) to Identity on my CodeFirst ASP.Net MVC Project.when I Run Project for the first time,the databace is created automatically with the custom tables and relations between them in SqlServer.
Custom Tables :
MvcControllers
ActionsTbls
GroupsTbls
AspNetUser_Action
AspNetUser_Group
Action_Group
This is The Diagram of my Database Image :
Click Here
For Creating Custom tables,I Add Some Codes to IdentityModels.cs.
IdentityModels.cs :
namespace Admin_Base_SN.Models
{
// You can add profile data for the user by adding more properties to your ApplicationUser class, please visit https://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
public virtual AspNetUser_Action AspNetUser_Action { get; set; }
public virtual AspNetUser_Group AspNetUser_Group { get; set; }
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public DbSet<MvcController> MvcController { get; set; }
public DbSet<ActionsTbls> ActionsTbls { get; set; }
public DbSet<AspNetUser_Action> AspNetUser_Actions { get; set; }
public DbSet<GroupsTbl> GroupsTbls { get; set; }
public DbSet<Action_Group> Action_Groups { get; set; }
public DbSet<AspNetUser_Group> AspNetUser_Groups { get; set; }
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// one-to-zero or one relationship between ApplicationUser and Customer
// UserId column in Customers table will be foreign key
modelBuilder.Entity<ApplicationUser>()
.HasOptional(m => m.AspNetUser_Action)
.WithRequired(m => m.ApplicationUser)
.Map(p => p.MapKey("AspNetUser_Id"));
modelBuilder.Entity<ApplicationUser>()
.HasOptional(m => m.AspNetUser_Group)
.WithRequired(m => m.ApplicationUser)
.Map(p => p.MapKey("AspNetUser_Id"));
}
}
}
What I want
At first,I want to save All the Controllers Names and Actions Names of Project into separated Lists,then insert them into MvcController Table & ActionsTbl Table.This process should be done automatically when I Run Project for the first time.I mean When the Database is Created, the Lists inert to their tables automatically.
I think it's better to Add a New Custom Function to the Identity for inserting Lists to the Tables.
I appreciate your efforts in reaching a solution for my problem.
If desired types are Controller and ActionResult (without web api), then
you may get controllers and actions by using reflection
var controllers = Assembly.GetAssembly(typeof(*any type in assembly*))
.GetTypes()
.Where(x => x.IsSubclassOf(typeof(Controller)))
.ToList();
var result = controllers
.Select(type => new
{
ControllerType = type,
Actions = type.GetMethods()
.Where(m => m.ReturnType == typeof(ActionResult) || m.ReturnType.IsSubclassOf(typeof(ActionResult))).ToList()
})
.ToList();
Since you're using EF you can put all necessary logic into Seed Method
and enable Automatic Migration
I am trying to implement a history table for an entity in EF6, code first.
I figured there would be a way to do this with inheritance. The history table, which is a derived type of the actual table entity, just containing straight copies of all the properties. Along with an edit to the key.
My code first table entity config for Booking.
public class BookingEntityConfiguration
: EntityTypeConfiguration<Booking>
{
public BookingEntityConfiguration()
{
Property(b => b.BookingId).HasColumnOrder(0);
HasKey(b => new { b.BookingId });
HasOptional(b => b.BookingType)
.WithMany()
.HasForeignKey(c => c.BookingTypeId);
}
}
My code first table entity config for BookingHistory.
public class BookingHistoryTypeEntityConfiguration
: EntityTypeConfiguration<BookingHistory>
{
public BookingHistoryTypeEntityConfiguration()
{
Property(b => b.BookingId).HasColumnOrder(0);
Property(b => b.BookingVersion).HasColumnOrder(0);
HasKey(b => new { b.BookingId, b.BookingVersion });
}
}
Where
public class BookingHistory : Booking { }
My BookingHistory table never gets generated in the contexts associated database, which includes these references to the table entities:
public DbSet<Booking> Bookings { get; set; }
public DbSet<BookingHistory> BookingHistories { get; set; }
Is there any simple way to achieve what I want? Which is the derived entity (history table) generates a table that contains the same column fields as the base class entity, but with a change of key.
I appreciate my code above is pretty naive, but I can't seem to find a blog post of similar to help.
The best way is to have a base type from which both the entity and its history entity inherit:
public class BookingsContext : DbContext
{
public DbSet<Booking> Bookings { get; set; }
public DbSet<BookingHistory> BookingHistories { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<BookingBase>()
.HasKey(p => p.BookingId)
.Property(p => p.BookingId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<Booking>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Booking");
});
modelBuilder.Entity<BookingHistory>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("BookingHistory");
});
}
}
By ToTable you specify that both entities should be mapped to different tables. On top of that, MapInheritedProperties tells EF to mapp all properties from the base type to this table as well. the result is two completely independent tables that can be addressed by two separate DbSet properties.