Defining many-to-many connection defining ICollection<> only in one class - c#

I have an .NET application with the validation logic outsourced to a .NET Core API. There are some models and logics that both of them use and I want them to use the same classes from a (.net standard) Nuget package. The main problem that I'm using Entity Framework code-first, and some of the common models are in database too but i don't want to include f.e. ApplicationUser in the nuget package.
There is a model with a many-to-many connection to ApplicationUser and I don't want to define the ICollection in it.
So my "local" class looks something like this:
public class ApplicationUser : IdentityUser
{
....
public ICollection<Institute> Institutes { get; set; }
....
}
And my "remote" class looks like this:
public class Institute
{
....
public ICollection<ApplicationUser> Users { get; set; }
....
}
But I don't want Institute to have this public ICollection<ApplicationUser> Users { get; set; } but I want the EF to map the many-to-many connection. If I delete it from the Institute class the next migration will delete the whole ApplicationUserInstitute connection table.
I have thought of some kind of inheritance solution but I think future development would be pain in the ass with it. Every possible solution is welcomed.

You need explicitly override OnModelCreating() in your context and specify Many-to-Many relation there:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ApplicationUser>()
.HasMany(s => s.Institues)
.WithMany()
.Map(cs =>
{
cs.MapLeftKey("UserRefId");
cs.MapRightKey("InstituteRefId");
cs.ToTable("UserInsitute");
});
}
I used entities from this article to generate schema. The only difference is that I removed public virtual ICollection<Student> Students { get; set; } from Course.

Related

Inheriting properties from model - Navigation properties can only participate in a single relationship

I'm trying to share common properties with multiple entities by using multiple levels of inheritance, but I'm running into an error.
Cannot create a relationship between 'User.SupersCreated' and 'Super.CreatedBy' because a relationship already exists between 'User.BasicsCreated' and 'Basic.CreatedBy'. Navigation properties can only participate in a single relationship. If you want to override an existing relationship call 'Ignore' on the navigation 'Super.CreatedBy' first in 'OnModelCreating'.
The structure of my models is as follows.
public class EntityBase
{
public Guid Id { get; set; }
public Guid CreatedById { get; set; }
public User CreatedBy { get; set; }
}
public class Basic: EntityBase
{
public string BasicProperty { get; set; }
}
public class Super : Basic
{
public string SuperProperty { get; set; }
}
public class User : IdentityUser<Guid>
{
public ICollection<Basic> BasicsCreated { get; set; }
public ICollection<Super> SupersCreated { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>()
.HasMany(x => x.BasicsCreated)
.WithOne(x => x.CreatedBy);
modelBuilder.Entity<User>()
.HasMany(x => x.SupersCreated)
.WithOne(x => x.CreatedBy);
}
The problem seems to be a result of Super Inheriting from Basic, or at least, the problem goes away when I remove this level of inheritance and make Super inherit from EntityBase (however than I'll lose the properties that exist in Basic).
Can anyone please help me understand why I'm getting this error and what should be done to fix it?
Edit
After considering this some more, I think I'm trying to abuse inheritance to do what it's not intended to do.
The database structure I was hoping to end up with, is:
Even though my Basic and Super tables share the same properties, with Super having it's own additional properties, there's no relationship between Basic data and Super data.
From having a look at Microsoft's tutorial on implementing inheritance, there's two options:
Table per type
Table per hierarchy
Neither of these are what I'm trying to achieve.
Perhaps I should be using interfaces to define the common properties that exist between unrelated entities. It seems like I need to back and re-evaluate my design anyway.
If some of the base classes of the entity is identified as entity (as with your Super and Basic), by default EF Core will try to use one of the database inheritance strategies.
If you don't want that (want to treat is just like non entity base class), then you have to configure that explicitly at the very beginning of the OnModelCreating, e.g. for your sample
modelBuilder.Entity<Super>().HasBaseType((Type)null);
or more generally using a loop similar to this
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
entityType.BaseType = null;
and then define explicitly the entity hierarchy if and where needed.

No method to map entity to table in entity framework core

I am using entity framework core in a normal .net project, version 4.7. I know I can do this. The problem is that I can't seem to map an entity to a table because the "ToTable" method doesn't exist. I can't edit the poco or entity classes because they are predefined and generated. So I can't use the attribute. I looked on the internet and everyone seems to use this method to map an entity to a table.
Here is my code:
public class FactsDbContext : DbContext
{
public DbSet<TblIncident> TblIncidents { get; set; }
public DbSet<TblAction> TblActions { get; set; }
public DbSet<TblAddressTypeAlias> TblAddressTypeAliases { get; set; }
public DbSet<TblCountry> TblCountries { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//these methods don't exist in my case
modelBuilder.Entity<TblIncident>(entity => entity.ToTable("Incident"));
modelBuilder.Entity<TblIncident>().ToTable("Incident");
}
}
I also tried to use IEntityTypeConfiguration with a EntityTypeBuilder but it still don't have access to the map to table method:
public class IncidentConfig : IEntityTypeConfiguration<TblIncident>
{
public void Configure(EntityTypeBuilder<TblIncident> builder)
{
builder.ToTable("Incident");
}
}
I looked into the Entity Framework Core repository on GitHub and searched for the method "Totable" inside the repository. It turns out it is defined as an extension method but it is in separate nuget package and library called Microsoft.EntityFrameworkCore.SqlServer
After I downloaded the package I got the Totable method that I need. Still it doesn't make sense to add that method in a separate package for sql server when you already have the "Table" attribute that you can add on entities directly in the entity framework core package.
You can use the below approach. You have to use Table data annotation.
DBContext:
public virtual DbSet<Article> Article { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Article>(b =>
{
b.Property(e => e.Property1).HasDefaultValue(true);
... //Other properties
}
Model class:
[Table("Article")]
public class Article
{
You can also use to ToTable in DBContext, but you have to make sure that you have included using Microsoft.EntityFrameworkCore;.
Line modelBuilder.Entity<TblIncident>().ToTable("Incident"); looks correct according to the documentation.
https://learn.microsoft.com/en-us/ef/core/modeling/relational/tables#fluent-api
It's very old thread but I got the same issue and I solved it by placing base.OnModelCreating(builder) as a first line of OnModelCreating method.
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Rest of the code
}

Multiples contexts ef-core

I'm working with EF Core and I decided split the DbContexts I'm using.
Everything is right but I notice that when I create the migrations for each context, EF tries to create the tables again even if the other context had created that table previously.
I think EF Core must be trying to update the tables instead of trying to create them but I don't know why EF Core would do that.
AccountsContext :
public class AccountsContext : BaseContext<AccountContext>
{
public AccountContext(DbContextOptions<AccountsContext> options): base (options){}
public DbSet<Account> Accounts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Account>().HasKey(a => a.id);
modelBuilder.Entity<Account>().Property(a => a.Name).HasField("_name");
modelBuilder.Entity<Account>().Property(a => a.Role).HasField("_role");
modelBuilder.Entity<Account>().OwnsOne(a => a.Password);
modelBuilder.Entity<Account>().OwnsOne(a => a.Email);
}
}
CampaignContext :
public class CampaignContext: BaseContext<CampaignContext>
{
public CampaignContext(DbContextOptions<CampaignContext> options): base (options){}
public DbSet<Campaign> Campaing { get; set; }
private DbSet<Account> Accounts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Campaing>().ToTable("Campaings");
modelBuilder.Entity<Campaing>().HasKey(a => a.Id);
modelBuilder.Entity<Campaing>().HasOne(c => c.Owner);
modelBuilder.Entity<Account>().ToTable("Accounts");
}
}
Here is the scaffold of the migration in the campaign context. EF Core looks like its trying to create the accounts table even if it already exists in the database.
you should specify the Context you want to use by
dotnet ef Update Database -Context DbContext
each context in Ef core considered as a separated database so you are putting that table in both of the databases I don't know why you are trying to do that but it is normal that you see the table in both migrations

Data Access Layer DatabaseContext Do I have to map all relationships for my classes?

In my OnModelCreating method do I have to map every table's relationship like i did with my Tickets and Administrator class?
I read this article and the writer only did one of the relationships but not all of it. If you scroll down in the link I've provided to "Customize the Data Model by adding Code to the Database Context" you will see what I'm talking about.
Source: Click Here
IssueContext.cs
public class IssueContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Ticket> Tickets { get; set; }
public DbSet<Category> Categories { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Administrator> Administrators { get; set; }
public DbSet<Depot> Depots { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Ticket>()
.HasMany(c => c.Administrators).WithMany(i => i.Tickets)
.Map(t => t.MapLeftKey("TicketID")
.MapRightKey("AdministratorID")
.ToTable("Adminstrators on Tickets"));
}
}
My Entity Diagram
In my OnModelCreating method do I have to map every table's relationship like i did with my Tickets and Administrator class?
EF Code First came with some default conventions like the one you already disable by calling this line of code:
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
Those conventions help EF Code First to know:
which entity belong to which table,
who is the primary key,
what is the relationship between your entities and create those relationship into your database tables
....
If you follow the conventions, you will not need to write a connfiguration by using fluent API or data annotation attributes.
The following configuration you worte:
modelBuilder.Entity<Ticket>()
.HasMany(c => c.Administrators).WithMany(i => i.Tickets)
.Map(t => t.MapLeftKey("TicketID")
.MapRightKey("AdministratorID")
.ToTable("Adminstrators on Tickets"));
is useful because if you let EF to generate the join table between Ticket and Administrator you will have some issue to know which is the left key and which is the right key. You also tell EF to generate TicketID and AdministratorID as column name instead of Ticket_ID and Administrator_ID which are the default names it will use on the join table.
To answer to your question. No you don't need to add mappings for all entities. Use conventions as much as possible and then use fluent configuration or data annotation attributes to tweak and refine some columns, tables etc like you do with your join table.
Side note: Adminstrators on Tickets is not a good table name for a table just use AdministratorsTickets

Code-first: Mapping entities to existing database tables

I am using Entity Framework 6 code-first with an existing database, but having problems mapping my entities to the database tables.
Normally, I would use database-first approach and have my entity and context code generated, but using the designer has become a huge pain.
I have set Database.SetInitializer(null) as I do not want EF to change my schema.
Database schema:
Code-first:
public class Project
{
public int ProjectId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
public class ReleaseControlContext : DbContext
{
public ReleaseControlContext()
: base(ConfigurationManager.ConnectionStrings["ReleaseControl"].ConnectionString)
{
Database.SetInitializer<ReleaseControlContext>(null);
}
public DbSet<Project> Projects { get; set; }
}
Calling code:
using(var context = new ReleaseControlContext())
{
var projects = context.Projects.ToList();
}
The following exception is thrown:
SqlException: Invalid object name 'dbo.Projects'.
This is because my database table is Project and not Projects. I don't want to rename my context's DbSet<Project> to "Project" because that would be semantically incorrect.
Question:
Do I have to use the fluent API/data annotations to map between the Project database table and the DbSet<Project> Projects collection?
You can use the
[Table("Project")]
public class Project {
....
}
annotation against the Project entity, or in the OnModelCreating(DbModelBuilder modelBuilder) you can call modelBuilder.Entity<Project>().ToTable("Project");.
Both would do the same thing.
You should define a class (ie:ProjectMap) that inherits from the generic class EntityTypeConfiguration(T) where T is here your Project class.
In this ProjectMap class, you can define explicitly a table mapping :
this.ToTable("Project", "dbo");
The class ProjectMap should be called in the following method of your DbContext class
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new ProjectMap());
}

Categories