Ok - first off apologies - I'm a front end developer (HTML, CSS and JS) trying to do stuff with data - never pretty!
I have a 'Page', that can can have one or many 'Series'. These 'Series' can hold one or many 'Collections' and these 'Collections' can be related to more than one 'Series'. The 'Collection's can hold one or more 'Titles'. This is how I've structured my db:
CREATE TABLE [dbo].[Pages] (
PageId INT NOT NULL PRIMARY KEY,
[Title] NCHAR(50) NOT NULL
)
CREATE TABLE [dbo].[Series] (
[SeriesId] INT NOT NULL,
[Title] NCHAR (50) NOT NULL,
[PageId] INT NOT NULL,
PRIMARY KEY CLUSTERED ([SeriesId] ASC),
CONSTRAINT [FK_Series_Pages] FOREIGN KEY ([PageId]) REFERENCES [Pages]([PageId])
);
CREATE TABLE [dbo].[Collections] (
[CollectionId] INT NOT NULL,
[Title] NCHAR (50) NOT NULL,
PRIMARY KEY CLUSTERED ([CollectionId] ASC)
);
CREATE TABLE [dbo].[SeriesCollections] (
[SeriesCollectionId] INT NOT NULL,
[SeriesId] INT NOT NULL,
[CollectionId] INT NOT NULL,
PRIMARY KEY CLUSTERED ([SeriesCollectionId] ASC),
CONSTRAINT [FK_SeriesCollections_Series] FOREIGN KEY ([SeriesId]) REFERENCES [Series]([SeriesId]),
CONSTRAINT [FK_SeriesCollections_Collections] FOREIGN KEY ([CollectionId]) REFERENCES [Collections]([CollectionId])
);
CREATE TABLE [dbo].[Titles] (
[TitleId] INT NOT NULL,
[Title] NCHAR (100) NOT NULL,
[SeriesCollectionId] INT NOT NULL,
PRIMARY KEY CLUSTERED ([TitleId] ASC),
CONSTRAINT [FK_Titles_SeriesCollections] FOREIGN KEY ([SeriesCollectionId]) REFERENCES [SeriesCollections]([SeriesCollectionId])
Using Entity Framework I have the following:
public DbSet<Page> Pages { get; set; }
public DbSet<Series> Series { get; set; }
public DbSet<Collection> Collections { get; set; }
public DbSet<SeriesCollection> SeriesCollections { get; set; }
public DbSet<Title> Titles { get; set; }
In the view I want to get the following.
For a given 'Page' (id), I want all the 'Series' and within each of those 'Series', be able to list each of the 'Titles' and its associated 'Collection'.
First off - is my db set up correctly? Secondly, I'm struggling with the db call and viewmodels that would return this.
If anyone can help that'd be great
Thanks in advance
The 'Collection's can hold one or more 'Titles'.
Because of this I would modify your DB table schema:
In table Titles replace [SeriesCollectionId] by [CollectionId], directly refering to the Collections table.
In table SeriesCollections remove your PK [SeriesCollectionId] and make instead the remaining two fields [SeriesId] and [CollectionId] to a composite primary key.
Now, you can model a many-to-many relationship between Series and Collections with EF. Then the join table SeriesCollections isn't part of your model anymore. It's just a hidden table in the DB which is managed by EF. Therefore you can remove public DbSet<SeriesCollection> SeriesCollections { get; set; }.
The model classes could then look like this:
public class Page
{
public int PageId { get; set; }
[Required]
[MaxLength(50)]
pubic string Title { get; set; }
public ICollection<Series> Series { get; set; }
}
public class Series
{
public int SeriesId { get; set; }
[Required]
[MaxLength(50)]
pubic string Title { get; set; }
public int SeriesId { get; set; }
public int PageId { get; set; } // FK property, helpful but not required
public Page Page { get; set; }
public ICollection<Collection> Collections { get; set; }
}
public class Collection
{
public int CollectionId { get; set; }
[Required]
[MaxLength(50)]
pubic string Title { get; set; }
public ICollection<Series> Series { get; set; }
public ICollection<Title> Titles { get; set; }
}
public class Title
{
public int TitleId { get; set; }
[Required]
[MaxLength(100)]
pubic string TTitle { get; set; } // must be other name then class
public int CollectionId { get; set; } // FK property
public Collection Collection { get; set; }
}
For many-to-many mapping you need Fluent API:
public class MyContext : DbContext
{
public DbSet<Page> Pages { get; set; }
public DbSet<Series> Series { get; set; }
public DbSet<Collection> Collections { get; set; }
public DbSet<Title> Titles { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Series>()
.HasMany(s => s.Collections)
.WithMany(c => c.Series)
.Map(a =>
{
a.MapLeftKey("SeriesId");
a.MapRightKey("CollectionId");
a.ToTable("SeriesCollections");
});
}
}
EF will figure out all other relationships by convention, I believe.
For a given 'Page' (id), I want all the 'Series' and within each of
those 'Series', be able to list each of the 'Titles' and its
associated 'Collection'.
With the model above you could then try:
var page = context.Pages.Where(p => p.PageId == id)
.Include(p => p.Series.Select(s => s.Collections.Select(c => c.Titles)))
.SingleOrDefault();
It would select the page which contains a list of series with a list of collections with a list of titles.
Not sure if this is exactly what you want, just an untested starting point.
(BTW: You can write your classes first (Code-First) and let EF create your database tables. It's easier during design phase when you want to try some mappings, imo.)
Edit
One thing I forgot: If you really want non-variable fixed length string fields (NCHAR(50)) you must define this explicitely in Fluent API. By default EF would assume NVARCHAR(50) fields with the mapping above. Setting to fixed length columns would look like this:
modelBuilder.Entity<Page>().Property(p => p.Title).IsFixedLength();
Related
I have a pretty simple need, but I can't figure out how to do it with EF core 2.1.1 in code first.
I have a table Right and a table Role:
Role
public int RoleId { get; set; }
public string Name { get; set; }
Right
public int RightId { get; set; }
public string Name { get; set; }
Usually, in a standard database, I would simply make an intersection table Named:
RoleRights(RoleId int, RightId int)
But it seems in ef core 2.1.1, you instead add navigation properties.
Role
public int RoleId { get; set; }
public string Name { get; set; }
public IEnumerable<Right> Rights { get; set; }
Right
public int RightId { get; set; }
public string Name { get; set; }
public IEnumerable<Role> Roles { get; set; }
A Role can contain any number of Right and a Right can be contained in any number of Role.
By doing:
modelBuilder.Entity<Role>().HasMany(r => r.Rights);
modelBuilder.Entity<Right>().HasMany(r => r.Roles);
It flattens my Role table and add a RightId instead of making an intersection table. Same thing for the Right table. It adds a RoleId.
In the Migration script:
migrationBuilder.AddColumn<int>(
name: "RightId",
table: "Roles",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "RoleId",
table: "Rights",
nullable: true);
migrationBuilder.AddForeignKey(
name: "FK_Rights_Roles_RoleId",
table: "Rights",
column: "RoleId",
principalTable: "Roles",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
migrationBuilder.AddForeignKey(
name: "FK_Roles_Rights_RightId",
table: "Roles",
column: "RightId",
principalTable: "Rights",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
How can I configure my model to have an intersection table instead? In this case, it is generating a wrong schema. I cannot insert and empty Role or a Right in no Role. Thinking of it, I should probably never do that anyway, but it feels wierd to me.
Thanks for your time!
If anything is not clear, tell me what needs more detail and I'll clarify!
So I had followed something outdated. The solution is to explicitly make the join table.
public class RoleRight : IEntity
{
public int RoleId { get; set; }
public Role Role { get; set; }
public int RightId { get; set; }
public Right Right { get; set; }
}
With both Right and Role looking like this.
public class Right : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List<RoleRight> RoleRights { get; set; }
}
With this configuration on the OnModelCreating
modelBuilder.Entity<RoleRight>().HasKey(rr=> new { rr.RightId, rr.RoleId });
modelBuilder.Entity<RoleRight>().HasOne(rr => rr.Right)
.WithMany(r => r.RoleRights)
.HasForeignKey(rr => rr.RightId);
modelBuilder.Entity<RoleRight>().HasOne(rr => rr.Role)
.WithMany(r => r.RoleRights)
.HasForeignKey(rr => rr.RoleId);
Which is basically the last section in the link I provided in the comment earlier.
I have no clue how I had missed it when I read the page the first time!
I have a table which has the Id as primary key, I want to have a composite unique key on Code and Value column. I'm trying in this way.
[Table("Test")]
public class Test: FullAuditedEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public virtual int Code { get; set; }
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public virtual int Value { get; set; }
But it's making the composite primary key not unique key.
Try this:
public class SomeClass : Entity<int>
{
[Column("Code")]
public override int Id {get; set;}
public virtual string Name { get; set; }
}
[DatabaseGenerated(DatabaseGeneratedOption.None)] will make the Database to NOT create the Code column. What you need in reality is to override the Id and rename it to Code.
And to have a composite key, simply add
[Key]
public virtual int Value { get; set; }
field.
Adding solution for others.
Don't modify anything just add the below line
modelBuilder.Entity<Test>(b =>
{
b.HasIndex(e => new { e.Code, e.Value}).IsUnique();
});
I have two entities
public class Datatype
{
[Key]
public int Id { get; set; }
[StringLength(96)]
public string DataTypeName { get; set; }
}
public class Attribute
{
[Key]
public int Id { get; set; }
[StringLength(96)]
public string Attribute_Name { get; set; }
[ForeignKey("Datatype")]
public int? DatatypeId { get; set; }
public virtual Datatype Datatype { get; set; }
}
In DataBase Initialize I have this code
Datatype dt = new Datatype();
dt.DataTypeName = "text";
context.datatypes.Add(dt);
//Above code is working fine. And After execution I can see
//in records a row.. with id=1 and datatype=text
Attribute at = new Attribute();
at.Attribute_Name = "Description";
//at.DatatypeId = 1; But if I uncomment this line
context.attributes.Add(at); // Then This Gives Following Error
The INSERT statement conflicted with the FOREIGN KEY constraint
"FK_dbo.Attributes_dbo.Datatypes_DatatypeId". The conflict occurred in database
"dyescan", table "dbo.Datatypes", column 'Id'
Assuming that the above code executes before either of the two objects have been saved to the database then it will not work simply because your object 'dt' will not have an ID of 1 before it's been saved to the database and therefore you cannot associate with attribute on '1' YET!
Instead you should not set the 'DatatypeId' but simply set the 'Datatype' like so:
at.Datatype = dt;
This will leave entity framework to figure out what the actual foreign key associated should/would be when savechanges is called.
I have read the articles about Independent association from Ladislav. I have checked the information he provided in this link.
The information is quite helpful and it shed some light.
However I want to model my existing database which I was given access to. It has three tables Users, Certificates and Quiz .
At first I thought of modeling as independent association. Why? My Users table has primary key UserID and Certificates table has PK CertID and a column UserID which I can say is a foreign key. Just when I thought its a one to many relationship, I realized that some UserID in Users table are not found in Certificates table. However all UserID in Certificates can be found in Users table.
My question is should I use independent association and if so how to achieve this that my Users table become the principal class and the Certificates one the dependent. This is so that I can show or have values from my Users table then read values from my Certificate table in my asp.net mvc 3 application.
Please correct this code below which shows what I intend to achieve what I stated above:
public class Certificates
{
[Key]
public Users CertID { get; set; }
public int ID { get; set; }
public DateTime recvd { get; set; }
public int QuizID { get; set; }
}
public class Users
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string email { get; set; }
public ICollection<Certificates> Certificates { get; set; }
}
public class Quiz
{
public int QuizID { get; set; }
public string QuuizName { get; set; }
public int VolumeNo { get; set; }
public int mark { get; set; }
public ICollection<Certificates> Certificates { get; set; }
}
public class cpdContext : DbContext
{
public DbSet<Certificates> Certificates { get; set; }
public DbSet<Users> Users { get; set; }
public DbSet<Users> Quiz { get; set; }
Finally how do I show details view with information from these three classes with ability to add Users mark and tests. The relationship I want to model is 1 to many between Quiz and Certificates.
My Users table has primary key UserID and Certificates table has PK
CertID and a column UserID which I can say is a foreign key. Just when
I thought its a one to many relationship, I realized that some UserID
in Users table are not found in Certificates table. However all UserID
in Certificates can be found in Users table.
That's pretty normal for a one-to-many relationship where User is the principal and Certificate the dependent and you have a constraint enforced for the relationship.
I don't see this as an argument to decide for independent or foreign key associations. As far as I can tell you can map a database schema with both association types. The database schema shouldn't be the driving factor for the decision. Ladislav's posts you have linked explained it in all details. There are other points than the database schema that will guide the decision:
Architecture: Strict separation of object and relational world which might lead to the decision that you don't want a "foreign key" property as a relational artifact in your object model. This goes in favor of independent associations.
Ease of use: The additional foreign key property makes it easier to work with relationships, especially updating them, in some scenarios. This point is for foreign key associations.
Performance: EF is faster with foreign key associations in some situations with larger models.
Personally point 2 above tips the scales for me in most cases, but as said both is possible.
Mapping with foreign key associations (I omit all properties except PK, FK and navigation properties to keep it short):
public class Certificates
{
[Key]
public int CertID { get; set; }
[ForeignKey("User")]
public int UserID { get; set; }
[ForeignKey("Quiz")]
public int QuizID { get; set; }
public Users User { get; set; }
public Quiz Quiz { get; set; }
}
public class Users
{
public int ID { get; set; }
public ICollection<Certificates> Certificates { get; set; }
}
public class Quiz
{
public int QuizID { get; set; }
public ICollection<Certificates> Certificates { get; set; }
}
This assumes that both relationships are required, i.e. FKs in the database are not nullable. If they are you need to make the FK properties nullable as well (int?). Instead of data annotations you can use Fluent API, similar (but not exactly identical!) to the following example.
Mapping with independent associations:
public class Certificates
{
[Key]
public int CertID { get; set; }
public Users User { get; set; }
public Quiz Quiz { get; set; }
}
public class Users
{
public int ID { get; set; }
public ICollection<Certificates> Certificates { get; set; }
}
public class Quiz
{
public int QuizID { get; set; }
public ICollection<Certificates> Certificates { get; set; }
}
public class cpdContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entitiy<Users>()
.HasMany(u => u.Certificates)
.WithRequired(c => c.User) // or WithOptional
.Map(m => m.MapKey("UserID")); //<- DB FK column name
modelBuilder.Entitiy<Quiz>()
.HasMany(u => u.Certificates)
.WithRequired(c => c.Quiz) // or WithOptional
.Map(m => m.MapKey("QuizID")); //<- DB FK column name
}
}
The navigation properties in Certificates are not required (neither in the first nor the second example), you can remove them, if you don't need them, just use the parameterless WithRequired() then, and use Fluent API in the first example. (Possibly a [ForeignKey("UserID")] annotation on the Certificates collection in the Users class will work as well.)
Setup
Using MVC 3 + Code First
Here are my classes
public class Member
{
[Key]
public Guid ID { get; set; }
[Required]
public String Email { get; set; }
[Required]
public String FirstName { get; set; }
[Required]
public String LastName { get; set; }
public String Sex { get; set; }
public String Password { get; set; }
public String PasswordSalt { get; set; }
public DateTime RegisterDate { get; set; }
public DateTime LastOnline { get; set; }
public String SecurityQuestion { get; set; }
public String SecurityAnswer { get; set; }
public virtual ICollection<FamilyMember> Families { get; set; }
public virtual ICollection<Relationship> Relationships { get; set; }
}
public class Relationship
{
[Key]
public Guid ID { get; set; }
[ForeignKey("Member1")]
public Guid Member1ID { get; set; }
[ForeignKey("Member2")]
public Guid Member2ID { get; set; }
public Guid RelationshipTypeID { get; set; }
public virtual RelationshipType RelationshipType { get; set; }
public virtual Member Member1 { get; set; }
public virtual Member Member2 { get; set; }
}
Here is the problem
The database table "Relationship" is being created with the following columns:
ID, Member1ID, Member2ID, RelationshipTypeID, Member_ID
Why is it creating the Member_ID column?
I've seen this post in which the user has the same type of setup, but I am unsure of how to define the InverseProperty correctly. I tried using fluent API calls but from what I can tell they will not work here since I have two foreign keys referring to the same table.
Any help would be appreciated!
Member_ID is the foreign key column which EF created for the navigation property Member.Relationships. It belongs to a third association from Member.Relationships refering to an end endpoint which is not exposed in your Relationship entity. This relationship has nothing to do with the other two relationships from Relationship.Member1 and Relationship.Member2 which also both have an endpoint not exposed in Member.
I guess, this is not what you want. You need always pairs of endpoints in two entities to create an association. One endpoint is always a navigation property. The second endpoint can also be a navigation property but it is not required, you can omit the second navigation property.
Now, what is not possible, is to associate two navigation properties (Member1 and Member2) in one entity with one navigation property (Relationships) in the other entity. That is what you are trying to do apparently.
I assume that your Member.Relationships property is supposed to express that the member is either Member1 or Member2 in the relationship, or that it participates in the relationship, no matter if as Member1 or Member2.
Unfortunately you cannot express this in the model appropriately. You have to introduce something like RelationsshipsAsMember1 and RelationsshipsAsMember2 and for these two collection you can use the InverseProperty attribute as shown in the other question. In addition you can add a helper property which concats the two collections. But this is not a mapped property but readonly:
public class Member
{
// ...
[InverseProperty("Member1")]
public virtual ICollection<Relationship> RelationshipsAsMember1 { get; set; }
[InverseProperty("Member2")]
public virtual ICollection<Relationship> RelationshipsAsMember2 { get; set; }
public IEnumerable<Relationship> AllRelationships
{
get { return RelationshipsAsMember1.Concat(RelationshipsAsMember2); }
}
}
Accessing AllRelationships will cause two queries and roundtrips to the database (with lazy loading) to load both collections first before they get concatenated in memory.
With this mapping the Member_ID column will disappear and you will only get the two expected foreign key columns Member1ID, Member2ID because now you have only two associations and not three anymore.
You could also think about if you need the Relationships collection in the Member entity at all. As said, navigation properties on both sides are not required. If you rarely need to navigate from a member to its relationships you could fetch the relationships also with queries on the Relationship set, like so:
var relationships = context.Relationships
.Where(r => r.Member1ID == givenMemberID || r.Member2ID == givenMemberID)
.ToList();
...or...
var relationships = context.Relationships
.Where(r => r.Member1ID == givenMemberID)
.Concat(context.Relationships
.Where(r => r.Member2ID == givenMemberID)
.ToList();
This would give you all relationships the member with ID = givenMemberID participates in without the need of a navigation collection on the Member entity.