I am trying to build a datawarehouse (DWH), using the code-first approach (star-schema):
Fact-/dimension classes:
[Table("FactBase")]
public class FactBase
{
[Key]
public Guid Id { get; set; }
[ForeignKey("DimTest1")]
public string DimDigitalesBuchISBN { get; set; }
public virtual DimTest1 DimTest1 { get; set; }
}
[Table("DimTest1")]
public class DimTest1
{
[Key]
public string ISBN { get; set; }
public string Bla { get; set; }
}
Context:
public class XDBContextDWH : DbContext
{
public DbSet<FactBase> FactBase { get; set; }
public DbSet<DimTest1> DimTest1 { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(new string("connection string"));
}
}
After using migration the schema looks like this:
Based on that star schema, shouldn't be the relationship (here: SQL) like that?
When you specify the attribute [ForeignKey("DimTest1")], you're telling EF to use DimTest1 as the navigation property of the FactBase class, not pointing to the DimTest1 class.
But since that property does not exist, it does not create the relationship.
Change your class to:
[Table("FactBase")]
public class FactBase
{
[Key]
public Guid Id { get; set; }
[ForeignKey("DimTest1")]
public string DimDigitalesBuchISBN { get; set; }
public virtual DimTest1 DimTest1 { get; set; } // navigation property
}
This should make it work as intended.
As you imply is your question, the star schema Fact table should use a composite key made up of the foreign keys it's referencing.
So I would say there are a couple issues with your situation that should be addressed.
First, a fact table probably shouldn't have a a column called Id, though it's not really going to hurt anything, it probably wouldn't ever be used to query by, so you are just adding extra data taking up disk space.
Second, and probably the answer you are looking for is, if you want a composite primary key on your fact table, you need to specify that in the Database Context.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<FactBase>()
.HasKey(x => new { x.Id, x.DimDigitalesBuchISBN });
}
As I mentioned, you probably don't want to include the Fact.Id column in your PK but instead you would refer to more than one dimension like this:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<FactBase>()
.HasKey(x => new { x.Dim1Id, x.Dim2Id, x.Dim3Id});
}
Where Dim1Id, Dim2Id and Dim3Id are the primary keys of your dimensions.
I should also mention that you need to remove the [Key] attribute from the Id field of your FactBase class.
refer to: https://learn.microsoft.com/en-us/ef/core/modeling/keys?tabs=data-annotations
Related
Let's start with one-to-many relationship:
public sealed class MyContext : DbContext
{
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Slave>()
.HasOne(typeof(Master))
.WithMany() // *
.HasForeignKey(nameof(Slave.ForeignField));
}
}
So I declare that per one record in Master table I can have multiple records in Slave table. When I run EF tools to build migration this is accepted and works fine.
But when I change the line marked with asterisk to:
.WithOne()
in order to build one-to-one relationship building migration fails with error:
You are configuring a relationship between 'Slave' and 'Master' but
have specified a foreign key on 'ForeignField'. The foreign key must
be defined on a type that is part of the relationship.
I don't get it, just a second ago the given field (property in C# terms) was OK, and now EF claims it cannot find it?
Whad do I miss? How to make EF happy?
Record types are as follows -- please note there are no navigational properties.
internal sealed class Slave
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public Guid InstanceId { get; set; }
public Guid ForeignField { get; set; }
}
internal sealed class Master
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public Guid Id { get; set; }
}
For the time being I solved this using raw SQL, it works, but I am still curious what is wrong here.
Thanks to #AminGolmahalle answer, my curiosity was triggered why and can I use HasForeignKey in generic form. This lead me to finding out that I cannot, but what more is that WithOne and WithMany are not 1:1 replacements of each other. Both lead to different overloads.
So the first version one-to-many worked because I was hitting the right overload, the second didn't, because I was passing incorrect arguments. The correct version is:
builder.Entity<Slave>()
.HasOne(typeof(Master))
.WithOne()
.HasForeignKey(nameof(Slave), nameof(Slave.ForeignField)); // changed
the first argument has to be name of the slave table (again).
But is is even better to switch to generic version (see last comment under accepted answer) and avoid possibility of such "stupid" mistake in the first place.
Below code just sample for relation one to one:
public class Author
{
public int AuthorId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public AuthorBiography Biography { get; set; }
}
public class AuthorBiography
{
public int AuthorBiographyId { get; set; }
public string Biography { get; set; }
public DateTime DateOfBirth { get; set; }
public string PlaceOfBirth { get; set; }
public string Nationality { get; set; }
public int AuthorRef { get; set; }
public Author Author { get; set; }
}
You Can Use FluentApi For Relation In EntityFramework:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Author>()
.HasOne(a => a.Biography)
.WithOne(b => b.Author)
.HasForeignKey<AuthorBiography>(b => b.AuthorRef);
}
Using FluentApi is much better than DataAnnotaion.
FluentApi In Asp Core
I Suggest To You Read About FluentValidation
Refer this link, i think this will do
https://www.learnentityframeworkcore.com/configuration/one-to-one-relationship-configuration
Here im trying to get data from database using code-first but if I have a tabel Like Country, like this:
public class Country
{
public int id { get; set; }
public string Name { get; set; }
}
contextdb
public class ContextDb: DbContext
{
public ContextDb() { }
public DbSet<Country> Country { get; set; }
...
}
when I implements as Countrys its throwing an error:
Countries not have dbo
Please try to make it explicit which specific table you want for this particular type
public class ContextDb : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Country>().ToTable("Country");
}
public ContextDb() { }
public DbSet<Country> Country { get; set; }
}
As an alternative, you can turn off the pluralizing convention
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
Another option, as mentioned in Stefan's answer, is to have an attribute over your model class (which makes the class "not-that-POCO-anymore" but technically is perfectly valid).
Usually we use plurals in the DbContext like this:
public DbSet<Country> Countries { get; set; }
EF uses an auto plural convention, so your entity
public class Country
will translate to a datatable [dbo].[Countries] in your database.
If you want to explicitly override the name of the table in the database you can use the following attribute.
[Table("YourNameHere")]
public class Country
{
public int id { get; set; }
public string Name { get; set; }
}
I think there is a mixup in this logic somewhere in your code.
Alternativly you can also put your table in a differnt schema if you like:
[Table("YourNameHere", Schema = "YourSchemaNameHere")]
public class Country
{
public int id { get; set; }
public string Name { get; set; }
}
Beware with renaming the schema though, I have experienced that some migrations will break because to database index names are not always handled correctly.
note
See #Wiktor Zychla 's solution to disable the plural conventions on the whole context.
Courses have many prerequisites, and simultaneously a particular course can be a prerequisite for many courses. I've tried to establish the many-to-many relationship (in OnModelBCreating) using EF code-first with the following:
modelBuilder.Entity<Course>()
.HasMany(e => e.Prerequisites)
.WithMany(e => e.Postrequisites)
.Map(m => m.ToTable("CourseRequisiteMappings")
.MapLeftKey("CourseId").MapRightKey("CourseId")); // EDIT: THIS LINE IS THE PROBLEM. SEE MARKED ANSWER AND MY COMMENT ON IT.
Also, here is the Course class:
public class Course
{
public int CourseId { get; set; }
public string Name { get; set; }
public string InstitutionCode { get; set; }
public string Description { get; set; }
public bool IsElective { get; set; }
public virtual ICollection<Instructor> Instructors { get; set; }
public virtual ICollection<Student> Students { get; set; }
public virtual ICollection<Module> Modules { get; set; }
public virtual ICollection<Course> Prerequisites { get; set; }
public virtual ICollection<Course> Postrequisites { get; set; }
}
When I implemented this and went to update the database, it gave me the following errors:
CourseId: Name: Each property name in a type must be unique. Property
name 'CourseId' is already defined.
ModuleId: Name: Each property name in a type must be unique. Property
name 'ModuleId' is already defined.
CourseCourse: EntityType: EntitySet 'CourseCourse' is based on type
'CourseCourse' that has no keys defined.
ModuleModule: EntityType: EntitySet 'ModuleModule' is based on type
'ModuleModule' that has no keys defined.
I could not find an example of doing this which leads me to believe one of the following three are true:
There's a different way of accomplishing this that I don't see
I'm on the right track but overlooking something due to my lack of knowledge with EF
I'm the first one to try and EF doesn't support this (very unlikely)
First, does anyone know how I can set up this relationship, i.e., what do these errors mean (responding to #2)? For bonus points, is there another way of doing this that might be better or worse (kinda #1)? Thanks in advance.
Your mapping is nearly correct. But you have to understand that under the hood the Entity Framework wil create a so callled junction table that stores the many to many relationship.
This junction table wil just have two fields, containing the foreign keys which together make up the primary key. Obviously these foreign keys cannot have the same name.EF is smart enough to all figure it out by itself and no maping is necessary. Below a working example:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.Entity;
namespace ManyToManyUnderTheHoodSpike
{
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new DropCreateDatabaseAlways<CourseContext>());
using (CourseContext context=new CourseContext())
{
context.Courses.Add(new Course("Top of the bill")
{
PrerequisiteCourses = new List<Course>()
{
new Course("My two cents"),
new Course("Counting to two")
}
});
context.SaveChanges();
}
}
}
public class CourseContext : DbContext
{
public DbSet<Course> Courses { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
public class Course
{
public Course() { }
public Course(string name)
{
Name = name;
}
public string Name {get;set;}
public int CourseId{get;set;}
public ICollection<Course> PrerequisiteCourses{get;set;}
public ICollection<Course> FollowUpCourses{get;set;}
}
}
If you run this code you get a database with two tables: Courses and CourseCourses with as the only fields Course_Id and Course_Id1.
But that is not very readable, so let's make the mapping to make it more readable:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Course>().HasMany(course => course.PrerequisiteCourses)
.WithMany(course => course.FollowUpCourses)
.Map(data => data.ToTable("Prerequisites")
.MapLeftKey("FollowUpId")
.MapRightKey("PrerequisiteId"));
}
Presto!
I would model like this. I know you wanted only 1 table. But Ef will create the many to many table if you dont. Not sure what you didnt get right without testing. So anyway, here is another option.
public class Course
{
public int CourseId { get; set; }
public string Name { get; set; }
public string InstitutionCode { get; set; }
public string Description { get; set; }
public bool IsElective { get; set; }
//nav elements
public virtual ICollection<Instructor> Instructors { get; set; }
public virtual ICollection<Student> Students { get; set; }
public virtual ICollection<Module> Modules { get; set; }
public virtual ICollection<PreReqCourse> Prerequisites { get; set; }
// You can Find follow on courses, by accessing PreReqCourse table, but if you felt this navigation offered enough value, create a post req table too. Using same approach.
// public virtual ICollection<Course> Postrequisites { get; set; }
}
public class PreReqCourse
{
public virtual int Id {get; set;}
public virtual int CourseId { get; set; }
public virtual Course PreReqForCourse { get; set; } //Nav prop
}
modelBuilder.Entity<Course>()
.HasMany(e => e.Prerequisites)
.WithMany();
// Leave WithMany empty. You can define in PreReqCourse Table model, you dont need to model from both directions.
modelBuilder.Entity<PreReqCourse>()
.HasRequired(e => e.PreReqForCourse)
.HasForeignKey(f => f.CourseId)
.WithMany(p=>p.PreRequisites);
In my AspNet MVC 3 project when I try to scaffold an entity which has a One to Zero or One relationship with another entity I get "An item with the same index has already been added" error.
Essentially this happens when the Primary Key of the related table is also a Foreign Key.
At the moment my workaround is
Add an Id column to the related table and make it the primary key
Add Unique Key to the Foreign Key Column.
The problem with this is that EF will generate an ICollection navigation property for the related entity instead of just a property of the related entity type (which I can set to null in case of zero related entities)
Is this a know bug?
Am I doing something wrong?
Is there a better work around to get rid of the ICollection navigation property?
See my answer on this question:
How do I code an optional one-to-one relationship in EF 4.1 code first with lazy loading and the same primary key on both tables?
That's the example code with the correct configuration.
public class ZoneMedia
{
public int ZoneMediaID { get; set; }
public string MediaName { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public virtual ZoneMediaText MediaText { get; set; }
}
public class ZoneMediaText
{
public int ZoneMediaID { get; set; }
public string Text { get; set; }
public int Color { get; set; }
public virtual ZoneMedia ZoneMedia { get; set; }
}
public class TestEFDbContext : DbContext
{
public DbSet<ZoneMedia> ZoneMedia { get; set; }
public DbSet<ZoneMediaText> ZoneMediaText { get; set; }
protected override void OnModelCreating (DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ZoneMedia>()
.HasOptional(zm => zm.MediaText);
modelBuilder.Entity<ZoneMediaText>()
.HasKey(zmt => zmt.ZoneMediaID);
modelBuilder.Entity<ZoneMediaText>()
.HasRequired(zmt => zmt.ZoneMedia)
.WithRequiredDependent(zm => zm.MediaText);
base.OnModelCreating(modelBuilder);
}
}
class Program
{
static void Main (string[] args)
{
var dbcontext = new TestEFDbContext();
var medias = dbcontext.ZoneMedia.ToList();
}
}
You can also achieve this with DataAnnotations, but I generally prefer to keep my entity models as POCOs.
Try to use the [Key] attribute to the intended primary key. You may need to import the namespace System.ComponentModel.DataAnnotations
Also check the documentation about the full implementation of this namespace.
http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.aspx
I am trying to setup a navigation property that will hold zero or more elements of another table. The problem Entity Framework seems to be having is the other table has a composite primary key.
public class Alpha
{
public int Id { get; set; }
public int? BetaId { get; set; }
public virtual ICollection<Beta> Beta { get; set; }
public string Name { get; set; }
}
public class Beta
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int SequenceNumber { get; set; }
public string Name { get; set; }
}
public class ABContext : DbContext
{
public DbSet<Alpha> Alpha { get; set; }
public DbSet<Beta> Beta { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Beta>()
.HasKey(b => new { b.Id, b.SequenceNumber });
}
}
I am not sure how to properly setup the relationship. I've tried several different things. Entity Framework either complains about not using both keys from the Beta class to define the navigation property, or creates a pair of extra columns in the Alphas table that doesn't properly link the tables.
The goal is Alpha should hold a set of zero or more Betas based on Beta.Id. A Beta may belong to zero or more Alphas. However, I'm not really interested in the Beta to Alpha relationship.
Any ideas?
So let's have a look at your requirements:
Alpha should hold set of zero or more Betas .... A Beta may belong to zero or more Alphas
It is many-to-many relation so you have to map it.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Beta>()
.HasKey(b => new { b.Id, b.SequenceNumber });
modelBuilder.Entity<Alpha>()
.HasMany(a => a.Beta)
.WithMany();
}
This will create additional table in database with trhee columns (probably called Alpha_Id, Beta_Id and Beta_SequenceNumber).
I still don't underestand what do you mean by based on Beta.Id. If alpha can hold only records with the same Beta.Id you will probably have to control this in the application logic. It is something that would need additional complicated constructs to be enforced by mapping.