One-to-one relationship in CF Entity Framework using mapping - c#

I'm using Entity Framework 5, Code-First.
I've two domain objects (or tables). 1st is User, and 2nd is UserProfile. One user can have only one profile, and one profile belongs to only one user. That is 1-1 relationship.
Here are the classes.... (I simplified the code to make it understandably, It is actually more complex)
User
public class User {
public virtual Int64 UserId { get; set; }
public virtual UserProfile UserProfile { get; set; }
public virtual String Username{ get; set; }
public virtual String Email { get; set; }
public virtual String Password { get; set; }
}
UserProfile
public class UserProfile {
public virtual Int64 UserId { get; set; }
public virtual User User { get; set; }
public virtual Int64 Reputation { get; set; }
public virtual String WebsiteUrl { get; set; }
}
Here are the Maps....
UserMap
public UserMap() {
this.Property(t => t.Email)
.IsRequired()
.HasMaxLength(100);
this.Property(t => t.Password)
.IsRequired()
.HasMaxLength(15);
this.Property(t => t.Username)
.IsRequired()
.HasMaxLength(15);
}
UserProfileMap
public UserProfileMap()
{
this.HasKey(t => t.UserId);
}
Here is the Context....
public class TcContext : DbContext {
static TcContext () {
Database.SetInitializer(new TcContextInitializer());
}
public DbSet<User> Users { get; set; }
public DbSet<UserProfile> UserProfiles { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Configurations.Add(new UserMap());
modelBuilder.Configurations.Add(new UserProfileMap());
}
}
And here is my error message....
Unable to determine the principal end of an association between the types 'Tc.Domain.UserProfile' and 'Tc.Domain.User'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
I think in this way EF should determine the relationship automatically. But it gives me the above error message. I've researched this problem for a while but can't find a good illustration of the problem in my case.
Where is my mistake? Or, should I define some sort of additional relations in maps?

I had to modify the UserMap class as follows
public UserMap() {
this.Property(t => t.Email)
.IsRequired()
.HasMaxLength(100);
this.Property(t => t.Password)
.IsRequired()
.HasMaxLength(15);
this.Property(t => t.Username)
.IsRequired()
.HasMaxLength(15);
this.HasOptional(t => t.UserProfile)
.WithRequired(t => t.User);
}
It essentially says:
"User has an optional entity UserProfile which in turn has a required User entity"
The definition of the key in UserProfile is a must here.

Just looked at it again, do you need to add HasKey to UserMap so it knows which one is the key otherwise you could do the following :-
Are you able to change UserProfile to inherit from User and then add this to your OnModelCreating method
modelBuilder.Entity().ToTable("UserProfile");
this will create your one to one relationship on the database. I had to do this with my current model to get one to one otherwise if you do not specify the ToTable part it will lump them into one table with a Discriminator column
cheers Mark

Related

Can you help me Many to One Relation for it?

modelBuilder.Entity<Food>(food =>
{
food.ToTable("foods");
food.Property(e => e.FoodCategoryId).HasColumnName("food_category_id").IsRequired();
food.HasOne(e => e.FoodCategory).WithMany().HasForeignKey(e =>
e.FoodCategoryId).HasConstraintName("fk_foods_food_categories_id");
});
I wrote one to many relation.But i need many to one
One to many and many to one are the same definition but this article Configuring One To Many Relationships in Entity Framework Core can help you to do this.
The following model represents companies and employees with an inverse navigation property defined in the dependent entity (Employee) but no matching foreign key property in the dependent
note: you can access the Foreign key from navigation property
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Employee> Employees { get; set; }
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public Company Company { get; set; }
}
A company has many employees, each with one company. That relationship is represented as follows:
// This method belongs to your context class which is inherited from DbContext
protected override void OnModelCreating(Modelbuilder modelBuilder)
{
modelBuilder.Entity<Company>()
.HasMany(c => c.Employees)
.WithOne(e => e.Company);
}
It can also be configured by starting with the other end of the relationship:
// This method belongs to your context class which is inherited from DbContext
protected override void OnModelCreating(Modelbuilder modelBuilder)
{
modelBuilder.Entity<Employee>()
.HasOne(e => e.Company)
.WithMany(c => c.Employees);
}

Entity Framework 6 Lazy Loading not working

I am having some trouble with EF6 lazy loading, code first to an existing database.
Here are the Entities that are giving me the issue, I have no idea why it is not working, everything I find online says it should be working.
public class User
{
public long UserId { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<Token> Tokens { get; set; }
public virtual ICollection<Business> Businesses { get; set; }
public virtual ICollection<Candidate> Candidates { get; set; }
}
Here is the configuration mappings:
public class Token
{
public long TokenId { get; set; }
public long UserId { get; set; }
public Guid TokenValue { get; set; }
public DateTime ExpirationDate { get; set; }
public virtual User User { get; set; }
}
public TokenMap()
{
this.HasKey(t => t.TokenId);
this.Property(t => t.TokenValue)
.IsRequired();
this.Property(t => t.ExpirationDate)
.IsRequired();
this.ToTable("Tokens");
this.Property(t => t.TokenId).HasColumnName("TokenId");
this.Property(t => t.UserId).HasColumnName("UserId");
this.Property(t => t.TokenValue).HasColumnName("TokenValue");
this.Property(t => t.ExpirationDate).HasColumnName("ExpirationDate");
this.HasRequired(s => s.User)
.WithMany(s=>s.Tokens)
.HasForeignKey(s=>s.UserId);
}
public UserMap()
{
this.ToTable("Users");
this.HasKey(t => t.UserId);
this.Property(t => t.Email)
.IsRequired();
this.Property(t => t.FirstName)
.IsRequired();
this.Property(t => t.LastName)
.IsRequired();
this.HasMany(t => t.Businesses)
.WithMany(set => set.Users)
.Map(m =>
{
m.ToTable("BusinessUser");
m.MapLeftKey("UserId");
m.MapRightKey("BusinessId");
});
this.HasMany(s => s.Tokens)
.WithRequired(s => s.User)
.HasForeignKey(s => s.UserId);
this.HasMany(s => s.Candidates)
.WithOptional(s => s.User)
.HasForeignKey(s => s.UserId);
}
And here is a few snippets from the context:
public DbSet<Token> Token { get; set; }
public DbSet<User> User { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new TokenMap());
modelBuilder.Configurations.Add(new UserMap());
}
Whenever I do a SingleOrDefault on the Tokens entity the result' user is null.
Any idea what I am doing wrong? All the data in the database is right, and UserId does have a value.
Here is a more elaborate example of calling:
I am implementing the repository patern.
In the class doing the call' constructor I have:
context = new Consolid8ContextProvider();
uow = new UnitOfWork(context);
And then uow.Tokens.First(u => u.ExpirationDate > DateTime.Now && u.TokenValue == token);
Tokens is my TokenRepository that exposes the Tokens entity, and First is a wrapper for FirstOrDefault.
This results in a token object with all of the properties set except for the User navigation property
So I was using BreezeJS and it overrides your context with it's own settings, part of which is to set LazyLoading and EnableProxiesCreation to false.
So if you want to do queries outside of breeze you either have to implement a different constructor for your breeze provider or setting it per query as Slauma has suggested in the comments of the question.

Disable cascade deletion with EF code first doesn't work?

I have the following classes and I've created the database tables using EF Code-first. However, I found the deletion cascade is on and tried to remove it.
public class Category
{
[Key, DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[MaxLength(100)]
public string Title { get; set; }
public virtual ICollection<Event> Events { get; set; }
}
public class Event
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
[ForeignKey("Category")]
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
}
And I have added the following code in the DbContext class.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Use singular table names
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
Database.SetInitializer<MyContext>(null);
modelBuilder.Entity<Category>()
.HasMany(c => c.Events)
.WithRequired()
.HasForeignKey(e => e.CategoryId)
.WillCascadeOnDelete(false);
}
However, the generated migration code generate two AddForeignKey states with one with cascadeDeletion and another one without it.
AddForeignKey("dbo.Event", "CategoryId", "dbo.Category", "Id");
AddForeignKey("dbo.Event", "CategoryId", "dbo.Category", "Id", cascadeDelete: true);
CreateIndex("dbo.Event", "CategoryId");
CreateIndex("dbo.Event", "CategoryId");
The .WithRequired() call you use configures the relationship to be optional:required without a navigation property on the other side of the relationship so that results in one AddForeignKey call.
Then you have a Category navigation property that hasn't been configured in the FluentAPI so EF generates another AddForeignKey call that uses the default configuration.
Try using the override of WithRequired that configures the relationship to be optional:required with a navigation property on the other side of the relationship
modelBuilder.Entity<Category>()
.HasMany(c => c.Events)
.WithRequired(e => e.Category)
.HasForeignKey(e => e.CategoryId)
.WillCascadeOnDelete(false);

Entity Framework 5 code first cannot get model to work

Tearing my hair out again on this one...using EF 5 Code First, building the model in code - compiles, so it is syntactically correct, but I get an exception when the code builds the model. Here is the entity class (I also have vaidation attributes, but have removed them here for readability):
[Table("USERS")]
public class User : IPEntity
{
#region Constructor (needs to initialize list objects for related entities)
public User()
{
this.Profiles = new List<Profile>();
this.ProfileDivs = new List<ProfileDiv>();
this.ProfileDepts = new List<ProfileDept>();
}
#endregion
#region Entity properties and validation attributes
[Key]
public long UserId { get; set; }
public long PclientId { get; set; }
public string UserName { get; set; }
public string UserDescription { get; set; }
public long? EmpId { get; set; }
public string MustChangePassword { get; set; }
public long? FailedLogins { get; set; }
public DateTime? LastLogin { get; set; }
public long? SequenceNumber { get; set; }
public string AllDivs { get; set; }
public string AllDepts { get; set; }
public string UserRole { get; set; }
public DateTime? BeginSupport { get; set; }
public DateTime? EndSupport { get; set; }
public string OneTimeAccess { get; set; }
public long? ClonedFromUser { get; set; }
public string Email { get; set; }
public string ResetEmail { get; set; }
public DateTime? ResetTimeout { get; set; }
public long? ChallengeFailures { get; set; }
public string PermUserRole { get; set; }
public DateTime? PasswordChangedDate { get; set; }
public virtual ICollection<Profile> Profiles { get; set; }
public virtual ICollection<ProfileDiv> ProfileDivs { get; set; }
public virtual ICollection<ProfileDept> ProfileDepts { get; set; }
public virtual WorkSession WorkSession { get; set; }
}
The model builder class is:
public class User_Map : EntityTypeConfiguration<User>
{
public User_Map()
{
this.ToTable("USERS");
this.HasKey(t => new { t.UserId });
this.Property(t => t.UserId)
.HasColumnName("USER_ID")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None)
.IsRequired();
this.Property(t => t.UserName)
.HasColumnName("USERNAME")
.IsRequired()
.HasMaxLength(25);
this.Property(t => t.PclientId)
.HasColumnName("PCLIENT_ID");
this.Property(t => t.EmpId)
.HasColumnName("EMP_ID");
this.Property(t => t.MustChangePassword)
.HasColumnName("MUST_CHANGE_PASSWORD")
.HasMaxLength(1);
this.Property(t => t.UserDescription)
.HasColumnName("USER_DESCRIPTION")
.HasMaxLength(80);
this.Property(t => t.FailedLogins)
.HasColumnName("FAILED_LOGINS");
this.Property(t => t.LastLogin)
.HasColumnName("LAST_LOGIN");
this.Property(t => t.SequenceNumber)
.HasColumnName("SEQUENCE_NUMBER");
this.Property(t => t.AllDivs)
.HasColumnName("ALL_DIVS")
.HasMaxLength(1);
this.Property(t => t.AllDepts)
.HasColumnName("ALL_DEPTS")
.HasMaxLength(1);
this.Property(t => t.UserRole)
.HasColumnName("USER_ROLE")
.HasMaxLength(2);
this.Property(t => t.BeginSupport)
.HasColumnName("BEGIN_SUPPORT");
this.Property(t => t.EndSupport)
.HasColumnName("END_SUPPORT");
this.Property(t => t.OneTimeAccess)
.HasColumnName("ONE_TIME_ACCESS")
.HasMaxLength(1);
this.Property(t => t.ClonedFromUser)
.HasColumnName("CLONED_FROM_USER");
this.Property(t => t.Email)
.HasColumnName("EMAIL")
.HasMaxLength(60);
this.Property(t => t.ResetEmail)
.HasColumnName("RESET_EMAIL")
.HasMaxLength(60);
this.Property(t => t.ResetTimeout)
.HasColumnName("RESET_TIMEOUT");
this.Property(t => t.ChallengeFailures)
.HasColumnName("CHALLENGE_FAILURES");
this.Property(t => t.PermUserRole)
.HasColumnName("PERM_USER_ROLE")
.HasMaxLength(2);
this.Property(t => t.PasswordChangedDate)
.HasColumnName("PASSWORD_CHANGED_DATE");
this.HasOptional(t => t.WorkSession)
.WithRequired(t => t.User);
// TODO: This is syntactically correct but model blows up!
this.HasMany(t => t.Profiles)
.WithRequired(t => t.User)
.HasForeignKey(t => t.UserId);
}
}
When the model builder class' constructor executes, I get the following exception thrown on the line above (after the comment):
The expression 't => t.User' is not a valid property expression.
The expression should represent a property: C#: 't => t.MyProperty'
VB.Net: 'Function(t) t.MyProperty'.
The Profiles entity is very simple:
[Table("PROFILE")]
public class Profile : IPEntity
{
[Key, Column(Order = 0)]
[ForeignKey("UserId")]
public long UserId { get; set; }
[Key, Column(Order = 1)]
public string FunctionalArea { get; set; }
public int RightsId { get; set; }
public User User;
}
I have been beating on this for two or three days, if anyone can spot my mistake, I would be MOST appreciative!
Thanks,
Peter
UPDATE: I found one bone-headed mistake by reading this post, since I had declared something as a field and not a property...but now I get a different exception which I do not understand:
The navigation property 'UserId' is not a declared property on type 'Profile'.
Verify that it has not been explicitly excluded from the model and that
it is a valid navigation property.
This has me confused as UserId IS a declared property on Profile...?
SECOND UPDATE:
I understand the exception but (since there is no inner exception detail) cannot determine where it is coming from. I changed the User_map class to include:
this.HasMany(t => t.Profiles)
.WithRequired(t => t.User)
.HasForeignKey(t => t.UserId)
.WillCascadeOnDelete(false);
and the Profile_map class to include:
this.HasRequired(t => t.User)
.WithMany()
.HasForeignKey(t => t.UserId)
.WillCascadeOnDelete(false);
Since I have to work with the existing database, I am stuck with the property "UserId" being a foreign key in both tables (in User table it is foreign key for Profiles table, in Profiles table it is foreign key for User table.) I added the .WillCascadeOnDelete(false) to both in case there was some kind of circularity problem. Like I said, I cannot determine which map is blowing things up, the calls to both constructors pass without exception, it is only when the override for OnModelCreating in the context exits that the exception is thrown. Here is the override:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
var mbProfile = new Profile_map();
modelBuilder.Configurations.Add(mbProfile);
var mbUser = new User_Map();
modelBuilder.Configurations.Add(mbUser);
base.OnModelCreating(modelBuilder);
}
I know I'm still an EF newbie, but I cannot find the problem (well, haven't yet anyway...)
Thanks again.
public User User;
should be a property instead of a field - like all the others :)
public User User { get; set; }
With virtual preferably (to allow lazy loading), because you have marked all your navigation properties as virtual.
Edit about the UPDATE
The exception talks about a navigation property UserId and complains that it is not a "valid navigation property". Indeed it isn't a valid navigation property. The exception indicates that you possibly have used UserId (instead of User) in the WithRequired method. You should take a closer look at that.
Peter, with regard to your issue with the two "UserId" columns, you said that they were both foreign keys. I don't think you are correct. The User.UserId is marked with [Key] and it is the only one in the User class marked that way. It must be the primary id for the User table. On the Profile class, you have both UserId and FunctionalArea marked with [Key]. This means that the Profile table has a joint primary key, one of which is the foreign key to the User table's UserId column.
That design is perfectly fine. However, in order to have a relationship between User and Profile records where the Profile record is the parent, you would have to have TWO columns on your User table that reference the Profile table's two primary keys, and NEITHER of those columns could be the UserId column of the User table, otherwise you would have a circular dependency with no way of creating either record.
Bottom line is, you don't have two relationships between the User and Profile tables, just one. So delete the following block from your Profile_map class:
this.HasRequired(t => t.User)
.WithMany()
.HasForeignKey(t => t.UserId)
.WillCascadeOnDelete(false);
That should fix it right up! ;)

Entity Framework 4.1 Code First Foreign Key Id's

I have two entities referenced one to many. When entity framework created the table it creates two foreign keys, one for the key I have specified with the fluent interface and the other for the ICollection. How do I get rid of the duplicate foreign key?
public class Person
{
public long RecordId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Username { get; set; }
public long DepartmentId { get; set; }
public virtual Department Department { get; set; }
}
public class Department
{
public long RecordId { get; set; }
public string Name { get; set; }
public virtual ICollection<Person> People { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>()
.HasRequired(p => p.Department)
.WithMany()
.HasForeignKey(p => p.DepartmentId)
.WillCascadeOnDelete(false);
}
Thanks!
You must specify the many-end of the association explicitely:
modelBuilder.Entity<Person>()
.HasRequired(p => p.Department)
.WithMany(d => d.People)
.HasForeignKey(p => p.DepartmentId)
.WillCascadeOnDelete(false);
Otherwise EF will assume that there are two associations: One which is not exposed in Department with the foreign key DepartmentId and navigation property Department in the Person class as you have defined in the Fluent code - and another association which belongs to the exposed navigation property People but with another not exposed end in Person and a foreign key automatically created by EF. That's the other key you see in the database.
The default Code First conventions detect your DepartmentId foreign key, since it is, well, conventional. I think you should remove the Fluent definition:
modelBuilder.Entity<Person>()
.HasRequired(p => p.Department)
.WithMany()
.WillCascadeOnDelete(false);
best thing is to remove departmentid property from Person class and add the following statement. MapKey will create foreign key column with the name you specify
modelBuilder.Entity<Person>().HasRequired(p => p.Department)
.WithMany().Map(x=>x.MapKey("DepartmentId"))
.WillCascadeOnDelete(false);

Categories