How can I make a foreign key relationship between an identity server column and another custom table? - asp.net-core

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);
}
}

Related

Link two tables with a one-to-one relationship, using the same unique key

I have two tables that need to be linked one to one by the key field email.
When I try to do this, I get an error like this:
Cannot use table 'UserSettings' for entity type 'UserSettings' since it is being used for entity type 'UserSettings' and potentially other entity types, but there is no linking relationship. Add a foreign key to 'UserSettings' on the primary key properties and pointing to the primary key on another entity type mapped to 'UserSettings'.
how I tried to implement it:
public class UserSettingsConfiguration : IEntityTypeConfiguration<UserSettings>
{
public void Configure(EntityTypeBuilder<UserSettings> builder)
{
builder.HasKey(n => n.Email);
builder.HasOne(n => n.User)
.WithOne(u => u.UserSettings)
.HasForeignKey<UserSettings>(k => k.Email)
.HasPrincipalKey<UserSettings>(k => k.Email);
}
}
UserSettings and User entities:
public class User
{
public string Email { get; set; }
public DateTime RegistrationDate { get; set; }
public string Image { get; set; }
public UserSettings UserSettings { get; set; }
}
public class UserSettings
{
public string Email { get; set; }
public int LanguageId { get; set; }
public User User { get; set; }
}

EF Core 2.2, owned entities generated as another table when multiple in hierarchy

I have a model with a class Address marked [Owned] and a hierarchy of people (person, customer or employee, then even more subtypes etc). There are addresses at different stages of this hierarchy and all of it ends up in one table as EF Core is limited to table per hierarchy. I expected all the attributes from address to appear multiple times in that person table (once per mention in any of the subtypes) however it doesn't appear at all! Instead i see FK for each of them and a separate Address table.
Does EF Core not support multiple owned members of the same type? If not is there anything i should do? I don't have any fluent API / specific configuration that could interfere with the defaults (new empty console project, only config line is .UseSQLServer(connectionstring)
Sample code bellow :
public class SampleContext : DbContext
{
public virtual DbSet<Address> Addresses { get; set; }
public virtual DbSet<Customer> Customers { get; set; }
public virtual DbSet<Employee> Employees { get; set; }
public virtual DbSet<Person> Persons { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer("my connection string here");
}
base.OnConfiguring(optionsBuilder);
}
}
[Owned]
public class Address
{
public int Id { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string AddressLine3 { get; set; }
public string City { get; set; }
}
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
}
public class Employee : Person
{
public Address Address { get; set; }
}
public class Customer : Person
{
public Address DeliveryAddress { get; set; }
public Address InvoicingAddress { get; set; }
}
Expected Person table :
DeliveryAddressAddressLine1
DeliveryAddressAddressLine2
DeliveryAddressAddressLine3
DeliveryAddressAddressCity
InvoicingAddressAddressLine1
InvoicingAddressAddressLine2
InvoicingAddressAddressLine3
InvoicingAddressAddressCity
EmployeeAddressAddressLine1
EmployeeAddressAddressLine2
EmployeeAddressAddressLine3
EmployeeAddressAddressCity
Generated Person table (+ an unexpected Address table):
EmployeeAddressAddressId
DeliveryAddressAddressId
InvoicingAddressAddressId
Edit : updated the question, added the context definition and noticed i had Addresses as a DbSet so i assume this may be the cause, removing it gives me the following error :
Cannot use table 'Person' for entity type 'Customer.DeliveryAddress#Address' since it is being used for entity type 'Employee.Address#Address' and there is no relationship between their primary keys.`
According to EF Core Owned Entity Types documentation:
Inheritance hierarchies that include owned entity types are not supported
You can overcome this problem by moving public Address Address { get; set; }, public Address DeliveryAddress { get; set; } and public Address InvoicingAddress { get; set; } navigation properties from Employee and Customer to the base class Person as follows:
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
public Address Address { get; set; }
public Address DeliveryAddress { get; set; }
public Address InvoicingAddress { get; set; }
}
Then configure with fluent API to override the Navigation_OwnedEntityProperty rule for owned entity column name as follows:
modelBuilder.Entity<Person>().OwnsOne(p => p.Address,
a =>
{
a.Property(p => p.AddressLine1).HasColumnName("EmployeeAddressLine1");
a.Property(p => p.AddressLine2).HasColumnName("EmployeeAddressLine2");
a.Property(p => p.AddressLine2).HasColumnName("EmployeeAddressLine3");
a.Property(p => p.City).HasColumnName("EmployeeAddressCity");
}).OwnsOne(p => p.DeliveryAddress,
a =>
{
a.Property(p => p.AddressLine1).HasColumnName("DeliveryAddressLine1");
a.Property(p => p.AddressLine2).HasColumnName("DeliveryAddressLine2");
a.Property(p => p.AddressLine2).HasColumnName("DeliveryAddressLine3");
a.Property(p => p.City).HasColumnName("DeliveryAddressCity");
}).OwnsOne(p => p.InvoicingAddress,
a =>
{
a.Property(p => p.AddressLine1).HasColumnName("InvoicingAddressLine1");
a.Property(p => p.AddressLine2).HasColumnName("InvoicingAddressLine2");
a.Property(p => p.AddressLine2).HasColumnName("InvoicingAddressLine3");
a.Property(p => p.City).HasColumnName("InvoicingAddressCity");
});
Now you if you don't want to move public Address Address { get; set; }, public Address DeliveryAddress { get; set; } and public Address InvoicingAddress { get; set; } navigation properties from Employee and Customer to the base class Person then you have to create separate tables from each address types as follows:
modelBuilder.Entity<Employee>().OwnsOne(p => p.Address,
a =>
{
a.ToTable("EmployeeAddresses");
});
modelBuilder.Entity<Customer>().OwnsOne(p => p.DeliveryAddress,
a =>
{
a.ToTable("DeliveryAddresses");
}).OwnsOne(p => p.InvoicingAddress,
a =>
{
a.ToTable("InvoicingAddresses");
});

EF Core Many to many with same class

I have City class representing City with its postal code. I have also a Route class representing bus route between two cities. So, i want in my Route class to have cityFrom and cityTwo properties, both with type City - many to many relationship with same class involved.
How can I achieve this with EF Core code first?
Thx!
UPDATE:
My models looks something like this:
public class Route
{
public int Id { get; set; }
public City CityFrom { get; set; }
public City CityTo { get; set; }
}
public class City
{
public int Id { get; set; }
public int PostCode { get; set; }
public string Name { get; set; }
}
Would this solve my problem:
modelBuilder.Entity<Route>()
.HasOne(f => f.CityFrom)
.WithMany()
.HasForeignKey(f => f.CityFromId);
modelBuilder.Entity<Route>()
.HasOne(f => f.CityTo)
.WithMany()
.HasForeignKey(f => f.CityToId);
And also to add int properties CityFromId and CityToId in Route model?
You can achieve many-to-many relationships in ASP.NET Core using Data Annotations
In your requirements, we can introduce a new class named CityRoute that represent many-to-many between the City and the Route
public class Route
{
public int RouteId { get; set; } // change to make it more specific id
public string CityFrom { get; set; }
public string CityTo { get; set; }
public ICollection<CityRoute> CityRoutes { get; set; }
}
public class City
{
public int CityId { get; set; } // change to make it more specific id
public int PostCode { get; set; }
public string Name { get; set; }
public ICollection<CityRoute> CityRoutes { get; set; }
}
// new class
public class CityRoute
{
public City City { get; set; }
public int CityId { get; set; }
public Route Route { get; set; }
public int RouteId { get; set; }
}
Your DataAnnotations can be like this
modelBuilder.Entity<CityRoute>()
.HasKey(cr => new {cr.CityId, cr.RouteId});
I hope this helps you
I wouldn't design this is a many-to-many relation.
Every Route has exactly one FromCity, defined by foreignkey FromCityId.
Every Route has exactly one ToCity, defined by foreignkey ToCityId.
public class Route
{
public int Id { get; set; }
// Every Route has one starting City defined by foreign key:
public int FromCityId { get; set; }
public virtual City FromCity { get; set; }
// Every Route has one termination city, defined by foreign key
public virtual int ToCityId { get; set; }
public virtual City ToCity { get; set; }
...
}
And the DbContext:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Proper name for table of Cities
ModelBuilder.Entity<City>().ToTable("Cities");
// Every Route has one required starting point defined by foreign key
// Several routes may use the same City Id
modelBuilder.Entity<Route>()
.HasRequired(route => route.FromCity)
.WithMany()
.HasForeignKey(route => route.FromCityId)
.WillCascadeOnDelete(false);
// Every Route has one required termination point defined by foreign key
// Several routes may use the same City Id
modelBuilder.Entity<Route>()
.HasRequired(route => route.ToCity)
.WithMany()
.HasForeignKey(route => route.ToCityId)
.WillCascadeOnDelete(false);
base.OnModelCreating(modelBuilder);
If you delete a Route, then you don't want their cities to be deleted, as they might be used by other Routes: hence we don't want cascading on delete CascadeOnDelete(false)
EFCore 3.
Needs its own many to many class.
My problem was: Person to Person relations (n->m)
My solution.
Create a relationship class that implements the many to many.
I chose for the records in the class to have their own ID.
So the relationship class has 1 PK and 2 FKs (both to the Person class).
public class Person
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public List<Relationship> MyRelationships { get; set; }
public List<Relationship> TheirRelationships { get; set; }
}
//MyRelationships for people I added as a relation.
//TheirRelationships for people that added me as a relation.
public class Relationship {
public int RelationshipID { get; set; }
public DateTime Since { get; set; }
//ref to person myrelationships
public int MyID { get; set; }
public Person Me { get; set; }
//ref to person theirrelationships
public int TheirID { get; set; }
public Person They { get; set; }
}
Using add-migration and update-database I learned that in this particular case EFCore cannot resolve which FK to use for which relation.
I solved that using the fluent API.
public class ApplicationDbContext : IdentityDbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//Write Fluent API configurations here
modelBuilder.Entity<Person>()
.HasMany<Relationship>(mr => mr.MyRelationships)
.WithOne(p => p.Me)
.HasForeignKey(m => m.MyID)
.OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<Person>()
.HasMany<Relationship>(tr => tr.TheirRelationships)
.WithOne(p => p.They)
.HasForeignKey(t => t.TheirID)
.OnDelete(DeleteBehavior.NoAction);
}
public DbSet<Person> People { get; set; }
public DbSet<Relationship> Relationships { get; set; }
}
Now add-migration xxx will work:
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "People",
columns: table => new
{
ID = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
FirstName = table.Column<string>(nullable: true),
MiddleName = table.Column<string>(nullable: true),
LastName = table.Column<string>(nullable: true),
Email = table.Column<string>(nullable: true),
UserID = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_People", x => x.ID);
table.ForeignKey(
name: "FK_People_AspNetUsers_UserID",
column: x => x.UserID,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_People_UserID",
table: "People",
column: "UserID");
migrationBuilder.CreateTable(
name: "Relationships",
columns: table => new
{
RelationshipID = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Since = table.Column<DateTime>(nullable: false),
Kind = table.Column<int>(nullable: false),
MyID = table.Column<int>(nullable: false),
TheirID = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Relationships", x => x.RelationshipID);
table.ForeignKey(
name: "FK_Relationships_People_MyID",
column: x => x.MyID,
principalTable: "People",
principalColumn: "ID");
table.ForeignKey(
name: "FK_Relationships_People_TheirID",
column: x => x.TheirID,
principalTable: "People",
principalColumn: "ID");
});
migrationBuilder.CreateIndex(
name: "IX_Relationships_MyID",
table: "Relationships",
column: "MyID");
migrationBuilder.CreateIndex(
name: "IX_Relationships_TheirID",
table: "Relationships",
column: "TheirID");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Relationships");
migrationBuilder.DropTable(
name: "People");
}
}

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")
);

How to use composite Ids in one-to-many mappings in fluent nhibernate?

I got a scenario where a composite Id uniquely identifies an entity. I defined the MSSQL to have a multiple primary key on those fields. In addition I would like an auto-incremented id to be used for referencing a one-to-many relationship. Here's the schema:
public class Character
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Region Region { get; set; }
public virtual string Realm { get; set; }
public virtual IList<CharProgression> Progression { get; set; }
}
public class CharProgression
{
public virtual int Id { get; set; }
public virtual Character Character { get; set; }
public virtual Stage Stage { get; set; }
public virtual int ProgressionPoints { get; set; }
public virtual int NumOfSaves { get; set; }
}
public class Stage
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
}
The mappings look like this:
class CharacterMap : ClassMap<Character>
{
public CharacterMap()
{
Table("characters");
Id(ch => ch.Id, "id").GeneratedBy.Identity().Not.Nullable();
CompositeId().KeyProperty(ch => ch.Region, "region")
.KeyProperty(ch => ch.Realm, "realm")
.KeyProperty(ch => ch.Name, "name");
HasMany<CharProgression>(ch => ch.Progression).Inverse().Cascade.All();
}
}
class CharProgressionMap : ClassMap<CharProgression>
{
public CharProgressionMap()
{
Table("char_progression");
CompositeId().KeyReference(cprog => cprog.Character, "char_id",
.KeyReference(cprog => cprog.Stage, "stage_id");
Id(cprog => cprog.Id, "id").GeneratedBy.Identity().Not.Nullable();
Map(cprog => cprog.ProgressionPoints, "progression_points");
Map(cprog => cprog.NumOfSaves, "num_of_saves");
}
}
public class StageMap : ClassMap<Stage>
{
public StageMap()
{
Table("stages");
Id(st => st.Id, "id").GeneratedBy.Identity().Not.Nullable();
Map(st => st.Name, "name");
Map(st => st.Description, "description");
}
}
Now, the thing is that I would like to use SaveOrUpdate() on a character and use the composite id for the update, since the character uniqueness is defined by those 3 fields - region, realm, name.
However, when I am referencing the Character from CharProgression, I don't want to use the composite Id as I don't want the char_progression table to hold 3 fields for identifying a character, a simple Id is enough... which is why I also defined an IDENTITY id on the Character entity.
Is what i'm trying possible? or is there another way to achieve this?
Thanks :)