How to use composite Ids in one-to-many mappings in fluent nhibernate? - 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 :)

Related

How to set up relationship using entity framework core

I have Two Model
public class User: Entity
{
public string Gender { get; set; }
public string Name { get; set; }
}
And
public class CognitoUser : Entity
{
public string UserId { get; set; }
public User User{ get; set; }
public string CognitoName { get; set; }
}
I want to set Cognito.UserId as User.Id . I have written the following which is not working can you please correct me as i dont want to create a model CognitoUser into user model.
modelBuilder.Entity<CognitoUser>(e =>
{
e.ToTable("CognitoUser");
e.HasKey(p => p.UserId);
e.HasOne(x => x.User)
.HasForeignKey<User>(c => c.Id);
});
Primary keys are required in each Entity which is missing in your User Entity.
Using Fluent API is optional. If you set your classes right, Entity Framework will understand what you want to achieve.
Hints:
Use [Key] attribute to specify a property as primary key
Prefered primary key format would be {ClassName}{Id}
Use [DatabaseGenerated(DatabaseGeneratedOption.Identity)] to force database
to automatically generate primary key for you.
You can use Guid as primary key type, it is always unique and hassle-free
Additionally, check out the code below to see how to create a relation.
public class User: Entity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid UserId { get; set; }
public string Gender { get; set; }
public string Name { get; set; }
}
public class CognitoUser: Entity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid CognitoUserId { get; set; }
public string CognitoName { get; set; }
// relation
public Guid UserId { get; set; }
public User User { get; set; }
}
Visualization:

EF Two-to-Many relationship

I have a Section object that is in relationship with exactly two Node objects. Basically, the Section object is a route with a starting point and an ending point. I can't find a lot about Multiple-to-Many relationships and I don't even know if this is a thing.
public class Section
{
public Guid Id { get; set; }
public int Length { get; set; }
public Node StartNode { get; set; }
public Guid StartNodeId { get; set; }
public Node EndNode { get; set; }
public Guid EndNodeId { get; set; }
}
public class Node
{
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<Section> Sections { get; set; }
}
This should not work :
modelBuilder.Entity<Section>()
.HasOne(se => se.StartNode)
.WithMany(sn => en.Sections)
.HasForeignKey(se => se.StartNodeId);
modelBuilder.Entity<Section>()
.HasOne(se => se.EndNode)
.WithMany(en => en.Sections)
.HasForeignKey(se => se.EndNodeId);
Should I map this as a Many-to-Many relationship ? I would like to have access to the Sections from a Node, and the two points from my Section.
What you actually wanting is multiple one-to-many with the same entity.
So your Node class should be as follows:
public class Node
{
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<Section> StarNodeSections { get; set; }
public ICollection<Section> EndNodeSections { get; set; }
}
Then in the Fluent API configuration as follows:
modelBuilder.Entity<Section>()
.HasOne(se => se.StartNode)
.WithMany(sn => sn.StarNodeSections) // <-- Here it is
.HasForeignKey(se => se.StartNodeId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Section>()
.HasOne(se => se.EndNode)
.WithMany(en => en.EndNodeSections) // <-- Here it is
.HasForeignKey(se => se.EndNodeId)
.OnDelete(DeleteBehavior.Restrict);

How can I make a foreign key relationship between an identity server column and another custom table?

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

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

Fluent NHibernate: How to map two non-key columns

How would I be able to Map an object inside of another with two columns that arent keys?
public class Car
{
public virtual int Id { get; set; }
public virtual int AccountId { get; set; }
}
public class UserAccount
{
public virtual int Id { get; set; }
public virtual int UserId{ get; set; }
public virtual int AccountId { get; set; }
}
public class User
{
public virtual int Id { get; set; }
public virtual int Name { get; set; }
}
Lets say I wanted to get all Cars with a User.Name of "joe". How would I map / query these with fluent nhibernate?
public Car()
{
Table("Car");
Id(x => x.Id).Column("ID").GeneratedBy.Native();
Map(x => x.AccountId);
References(x => x.Account); // ?? needs to map accountid with the Account.Id...
}
If you want to map a reference, you need the type, not the key.
public class Car
{
public virtual int Id { get; set; }
public virtual UserAccount Account { get; set; }
}
Then you would map it like this
public Car()
{
Table("Car");
Id(x => x.Id).Column("ID").GeneratedBy.Native();
References(x => x.Account, "AccountId");
}