I've been trying to get EF4 CTP5 to play nice with an existing database, but struggling with some basic mapping issues.
I have two model classes so far:
public class Job
{
[Key, Column(Order = 0)]
public int JobNumber { get; set; }
[Key, Column(Order = 1)]
public int VersionNumber { get; set; }
public virtual User OwnedBy { get; set; }
}
and
[Table("Usernames")]
public class User
{
[Key]
public string Username { get; set; }
public string EmailAddress { get; set; }
public bool IsAdministrator { get; set; }
}
And I have my DbContext class exposing those as IDbSet
I can query my users, but as soon as I added the OwnedBy field to the Job class I began getting this error in all my tests for the Jobs:
Invalid column name 'UserUsername'.
I want this to behave like NHibernate's many-to-one, whereas I think EF4 is treating it as a complex type. How should this be done?
UserUsername is the default name that EF Code First chooses for the foreign key in the Jobs table. Obviously, it's not the same name that your existing database has for the FK column in Jobs table. You need to override this conventions and change the FK's default name so that it matches your database. Here is how it's done with fluent API:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Job>()
.HasOptional(j => j.OwnedBy)
.WithMany()
.IsIndependent()
.Map(m => m.MapKey(u => u.Username, "TheFKNameInYourDB"));
}
Try let it create new database from your schema and look what columname it expect.
I think its foreing key for OwnedBy. It tries to name it according to its internal convention, that is incompatible with how you named your foreing key column. And no, its really treating it as many-to-one.
Related
I have the two entities below:
[Table("TimeZone")]
public class TimeZone
{
[Required]
[Column("Name"), Index("IX_Name", IsUnique = true)]
[StringLength(50)]
public string Name { get; set; }
[Column("Description")]
[StringLength(100)]
public string Description { get; set; }
[Column("IANAID"), Index("IX_IANAID", IsUnique = true)]
[StringLength(50)]
public string IANAId { get; set; }
public ICollection<ApplicationUser> Users { get; set; }
public TimeZone()
{
Users = new HashSet<ApplicationUser>();
}
}
[Table("User")]
public class ApplicationUser : IdentityUser<string, IdentityUserLogin, ApplicationUserRole, IdentityUserClaim>
{
// [...]
[Column("TimeZoneID"), ForeignKey("TimeZone")]
public string TimeZoneId { get; set; }
[Column("RegionID"), ForeignKey("Region")]
public string RegionId { get; set; }
[Column("ManagerID"), ForeignKey("Manager")]
public string ManagerId { get; set; }
// One-to-One
public virtual TimeZone TimeZone { get; set; }
// One-to-One
public virtual Region Region { get; set; }
// One-to-Many
public virtual ApplicationUser Manager { get; set; }
// One-to-many
public virtual ICollection<Appointment> Appointments { get; set; }
// Many-to-many
public virtual ICollection<OnCallGroup> OnCallGroups { get; set; }
// One-to-many
public virtual ICollection<ApplicationUser> Subordinates { get; set; }
}
When I delete a record from TimeZone table which is used in the User table, the related records should be deleted according to the Cascadde Delete convention.
However since there are other foreign keys, I get the following exception with Entity Framework:
The DELETE statement conflicted with the REFERENCE constraint
"FK_dbo.User_dbo.TimeZone_TimeZoneID". The conflict occurred in
database "APPLICATIONDB_753c3d2ad2634cbf8cb62b098cdc6043", table
"dbo.User", column 'TimeZoneID'. The statement has been terminated.
Is it normal that even though there is the cascade delete enabled by default on EF6 Code First, deleting a record from TimeZone does not delete all the related users?
Note:
In my ApplicationDbContext I overrided the OnModelCreating method:
protected override void OnModelCreating(DbModelBuilder dbModelBuilder)
{
base.OnModelCreating(dbModelBuilder);
dbModelBuilder.Conventions.Add<OneToManyCascadeDeleteConvention>();
dbModelBuilder.Conventions.Add<ManyToManyCascadeDeleteConvention>();
dbModelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
// [...]
}
The FK property (TimeZoneId) CLR type is string, which allows null, and no [Required] attribute has been applied to the FK or navigation property, thus making the relationship optional, i.e. ApplicationUser may exist without TimeZone. And by default convention EF does not turn cascade delete on optional relationships.
To turn the cascade delete on, you need either to make the relationship required by applying the [Required] attribute on either TimeZoneId or TimeZone property, or use fluent API in case you need to keep it optional by adding the following inside OnModelCreating override:
modelBuilder.Entity<TimeZone>()
.HasMany(e => e.Users)
.WithOptional(e => e.TimeZone)
.HasForeignKey(e => e.TimeZoneId)
.WillCascadeOnDelete(); // <--
The same applies to other relationships using string FK properties.
Update: Actually the second option is not really an option. Even if it sets cascade delete on, EF treats cascade delete on optional relationships differently - when principal entity is deleted, it disassociates the related records by setting FKs to null rather than deleting them. So you really have to make the relationship required by using the aforementioned attribute or changing the fluent API WithOptional to WithRequired.
I'm having a bit of a problem with configuring composite keys in Entity framework between 3 tables, code first approach. I have a base class that has the Id, which all of my classes inherit from. The first table has a collection of the second table items, while it has a collection of the third table. I need composite keys for cascade on delete when removing an element from either table. I am also using the aggregate root pattern.
public abstract class BaseClass
{
[Key, Column(Order = 0)]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
}
public class Table1 : BaseClass
{
public virtual ICollection<Table2> Table2Collection { get; set; }
public string Name { get; set; }
}
public class Table2 : BaseClass
{
public Table1 Table1 {get; set;}
[Key, ForeignKey("Table1"), Column(Order=1)]
public long Table1ID { get; set; }
public virtual ICollection<Table3> Table3Collection { get; set; }
public string Name { get; set; }
}
public class Table3 : BaseClass
{
[Key, ForeignKey("Table2Id,Table1Id"), Column(Order = 1)]
public Table2 Table2 { get; set; }
public long Table2Id{ get; set; }
public long Table1Id{ get; set; }
public string Name { get; set; }
}
The code above works fine for when I delete an element of type either Table 1 or Table2, but it won't allow me to delete an element from Table3 giving me the following exception:
"The relationship could not be changed because one or more of the foreign-key properties is non-nullable.When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted."
Bellow is my model builder:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Table2>()
.HasRequired(x=>x.Table1)
.WithMany(x =>x.Table2Collection)
.WillCascadeOnDelete(true);
modelBuilder.Entity<Table3>()
.HasRequired(x=>x.Table2)
.WithMany(x =>x.Table3Collection)
.WillCascadeOnDelete(true);
}
I suspect that I may have not configured the model builder properly, but I can't seem to figure out how to configure it to allow for deleting an element of type Table3. Any help would be appreciated.
Figured out what I was missing. I'm putting this answer here for anyone that might bump into the same problem as I have. I needed to make all FK into PK FK (since they don't allow null). It's a bit annoying since if you have an even more complex tree, the number of keys you'd have to be manage of would grow the deeper you go.
modelBuilder.Entity<Table3>().HasKey(m => new {m.Id, m.Table2Id, m.Table1Id});
If anyone has an idea on how to shorten the number of keys to manage please leave an answer. Since this might not be the best solution.
I'm attempting to create a many-to-many mapping between User and Group models. Here are my classes:
public abstract class Entity
{
public Guid Id { get; set; }
public DateTime Created { get; set; }
public DateTime? Modified { get; set; }
}
public class User : Entity
{
public string Email { get; set; }
public string Name { get; set; }
public virtual ICollection<Group> Groups { get; set; }
}
public class Group : Entity
{
public string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
}
As you can see, I'm using the Entity abstract class to implement common properties in the classes that inherit from it. In this case, Id will be the key property for all of my EF classes.
Here is my configuration file where I map the many-to-many relationship:
public GroupConfiguration()
{
Property(x => x.Id).IsRequired().HasDatabaseGeneratedOption(databaseGeneratedOption: DatabaseGeneratedOption.Identity);
Property(x => x.Name).HasMaxLength(50).IsRequired();
HasMany(g => g.Users)
.WithMany(u => u.Groups)
.Map(m =>
{
m.MapLeftKey("Id");
m.MapRightKey("Id");
m.ToTable("UserGroups");
});
}
When I attempt to add a migration, I get the following error: Id: Name: Each property name in a type must be unique. Property name 'Id' is already defined. It seemingly doesn't like the fact that the property being mapped on both sides has the same identifier. When I don't inherit from Entity for one of the classes, eg. User, and make the mapping property UserId it is able to successfully create a migration.
Is there any way around this? It would be to be able to use an Id property for all of my entities defined in an abstract class.
Thanks in advance.
You can't have 2 keys with the same name, it will represent the columns for your relationship table (named "UserGroups"). When you call "MapLeftKey" or "MapRightKey", you define the columns name.
So I suggest you to rename your Ids (UserId and GroupId for example) and your mapping should be alright. I think you cannot have another solution on using fluent API.
I'm defining a many-to-many relationship as follows:
modelBuilder.Entity<GameSessionEntry>().
HasMany(c => c.Users).
WithMany(p => p.GameSessionEntries).
Map(
m =>
{
m.MapLeftKey("SessionId");
m.MapRightKey("UserId");
m.ToTable("UserSessions");
});
However, I keep getting:
The Foreign Key on table 'UserSessions' with columns 'UserId' could
not be created because the principal key columns could not be
determined. Use the AddForeignKey fluent API to fully specify the
Foreign Key.
I'm new to database work and the EntityFramework in general - what is it asking me to do?
It's the recurring confusion with left and right, see this explanation by Slauma. So you just have to turn around the key names:
m.MapLeftKey("UserId"); // Property in the HasMany call
m.MapRightKey("SessionId"); // Property in the WithMany call
This is how I usually go about creating a many to many table (note this requires no fluent api configuration)
public class User
{
public int Id { get; set; }
public virtual ICollection<UserSession> UserSessions { get; set; }
}
public class Session
{
public int Id { get; set; }
public virtual ICollection<UserSession> UserSessions { get; set; }
}
public class UserSession
{
[Key]
[Column(Order = 1)]
public int UserId { get; set; }
[Key]
[Column(Order = 2)]
public int SessionId{ get; set; }
public virtual User User { get; set; }
public virtual Session Session { get; set; }
}
Instead of fiddling around with a many-many relationship you should rewrite it to a weak entity set.
If you have for instance this relationship:
You can redesign it to a weak entity set:
By doing this you get rid of the many-many relationship and don't have to store the same data in multiple tables.
For more information: http://fileadmin.cs.lth.se/cs/Education/EDA216/lectures/dbtoh4.pdf
Read the lecture slides about "The Relational Data Model" starting on slide 87/360.
I'm having trouble with reverse navigation on one of my entities.
I have the following two objects:
public class Candidate
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long CandidateId { get; set; }
....
// Reverse navigation
public virtual CandidateData Data { get; set; }
...
// Foreign keys
....
}
public class CandidateData
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long CandidateDataId { get; set; }
[Required]
public long CandidateId { get; set; }
// Foreign keys
[ForeignKey("CandidateId")]
public virtual Candidate Candidate { get; set; }
}
Now my foreign key navigation on the CandidateData object works fine. I am having trouble getting the reverse navigation for the candidate object to work (if that's even possible).
This is my OnModelCreating function:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Candidate>()
.HasOptional(obj => obj.Data)
.WithOptionalPrincipal();
base.OnModelCreating(modelBuilder);
}
It's close to working except in the database I get two columns that link to the CandidateId. I get the one I from the POCO object the I get another column Candidate_CandidateId I assume was created by the modelBuilder.
I am quiet lost at the moment. Can someone please shed some light on what's going on?
The One to One problem....
The issue is EF and CODE First, when 1:1 , for the dependent to have a Primary key that refers to the principal. ALthough you can define a DB otherwise and indeed with a DB you can even have OPTIONAL FK on the Primary. EF makes this restriction in Code first. Fair Enough I think...
TRy this instead: IS have added a few opinions on the way which you may ignore if you disagree:-)
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
namespace EF_DEMO
{
class FK121
{
public static void ENTRYfk121(string[] args)
{
var ctx = new Context121();
ctx.Database.Create();
System.Console.ReadKey();
}
}
public class Candidate
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]// best in Fluent API, In my opinion..
public long CandidateId { get; set; }
// public long CandidateDataId { get; set; }// DONT TRY THIS... Although DB will support EF cant deal with 1:1 and both as FKs
public virtual CandidateData Data { get; set; } // Reverse navigation
}
public class CandidateData
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] // best in Fluent API as it is EF/DB related
public long CandidateDataId { get; set; } // is also a Foreign with EF and 1:1 when this is dependent
// [Required]
// public long CandidateId { get; set; } // dont need this... PK is the FK to Principal in 1:1
public virtual Candidate Candidate { get; set; } // yes we need this
}
public class Context121 : DbContext
{
static Context121()
{
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<Context121>());
}
public Context121()
: base("Name=Demo") { }
public DbSet<Candidate> Candidates { get; set; }
public DbSet<CandidateData> CandidateDatas { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Candidate>();
modelBuilder.Entity<CandidateData>()
.HasRequired(q => q.Candidate)
.WithOptional(p=>p.Data) // this would be blank if reverse validation wasnt used, but here it is used
.Map(t => t.MapKey("CandidateId")); // Only use MAP when the Foreign Key Attributes NOT annotated as attributes
}
}
}
I think that the foreign key should be created as:
.Map(t => t.MapKey("CandidateDataId")) because thsi foreign key will be placed in Candidate table...
Waht do you think?