I am aware of this, which states that it is not possible to create a primary key with non clustered index via code first. Is this still the case?
Ideally, I would like to specify via EntityTypeConfiguration, that my primary key (Guid) has a non-clustered index and there is another column (int) with a clustered index.
AFAIK this is not possible with EntityTypeConfiguration. However you can do this with Code-First migrations. Working example:
public class Product
{
public Guid Id
{ get; set; }
public int Price
{ get; set; }
}
class AppDbContext : DbContext
{
public DbSet<Product> Products
{ get; set; }
}
public partial class InitialCreate : DbMigration
{
public override void Up()
{
CreateTable(
"dbo.Products",
c => new
{
Id = c.Guid(nullable: false),
Price = c.Int(nullable: false),
})
.PrimaryKey(t => t.Id, clustered: false)
.Index(t => t.Price, clustered: true);
}
public override void Down()
{
DropIndex("dbo.Products", new[] { "Price" });
DropTable("dbo.Products");
}
}
Result:
CREATE TABLE [dbo].[Products] (
[Id] UNIQUEIDENTIFIER NOT NULL,
[Price] INT NOT NULL,
CONSTRAINT [PK_dbo.Products] PRIMARY KEY NONCLUSTERED ([Id] ASC)
);
GO
CREATE CLUSTERED INDEX [IX_Price]
ON [dbo].[Products]([Price] ASC);
You can also do this with your OnModelCreating method like so:
modelBuilder.Entity(entityTypeName)
.HasKey(nameof(ClassName.Id))
.ForSqlServerIsClustered(false);
Related
I have the following classes generated from an edmx model:
public partial class A
{
public int Id { get; set; }
public virtual B B { get; set; }
}
public partial class B
{
public int Id { get; set; }
public virtual A A { get; set; }
}
The existing db doesn't use the EF default which expects A.Id to be the primary key of table B:
CREATE TABLE [dbo].[B] (
[Id] INT IDENTITY (1, 1) NOT NULL,
PRIMARY KEY CLUSTERED ([Id] ASC)
);
CREATE TABLE [dbo].[A] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[BId] INT NULL,
CONSTRAINT [fk] FOREIGN KEY ([BId]) REFERENCES [dbo].[B] ([Id])
);
With an edmx model, I can explicitly configure the multiplicity of each end, but I haven't found how to get the equivalent model using the fluent-api. When I do something like the following and generate a new db, the foreign key gets placed in table A instead of table B.
modelBuilder.Entity<A>().HasOptional(a => a.B).WithRequired(b => b.A);
I'm guessing I need to use a convention, but so far I've been unable to get the desired output.
UPDATE:
The closest solution I've found so far is to use the following which generates the correct SQL in the db:
modelBuilder.Entity<A>()
.HasOptional(a => a.B)
.WithOptionalDependent(b => b.A)
.Map(c => c.MapKey("BId"));
However, it's conceptually modeled as a 0..1:0..1 relationship and I haven't found how to set a CASCADE delete rule that deletes B when A is deleted.
I wasn't able to find a direct solution, but using the following code seems to meet my requirements of preserving the existing schema and creating a conceptual model that has the same multiplicities & delete behaviors as my original edmx model.
I'd still be interested in any solutions that don't require updating the conceptual model during the post-processing IStoreModelConvention.
{
var overridesConvention = new OverrideAssociationsConvention();
modelBuilder.Conventions.Add(overridesConvention);
modelBuilder.Conventions.Add(new OverrideMultiplictyConvention(overridesConvention));
}
private class OverrideAssociationsConvention : IConceptualModelConvention<AssociationType>
{
...
public List<AssociationEndMember> MultiplicityOverrides { get; } = new List<AssociationEndMember>();
public void Apply(AssociationType item, DbModel model)
{
if (multiplicityOverrides.Contains(item.Name))
{
// Defer actually updating the multiplicity until the store model is generated
// so that foreign keys are placed in the desired tables.
MultiplicityOverrides.Add(item.AssociationEndMembers.Last());
}
if (cascadeOverrides.Contains(item.Name))
{
item.AssociationEndMembers.Last().DeleteBehavior = OperationAction.Cascade;
}
}
}
private class OverrideMultiplictyConvention : IStoreModelConvention<EdmModel>
{
private readonly OverrideAssociationsConvention overrides;
public OverrideMultiplictyConvention(OverrideAssociationsConvention overrides)
{
this.overrides = overrides;
}
public void Apply(EdmModel item, DbModel model)
{
overrides.MultiplicityOverrides.ForEach(o => o.RelationshipMultiplicity = RelationshipMultiplicity.One);
}
}
I have an entity with a composite, primary key (Id and TreeVersion). I have another entity with values that reference this key.
First entity:
class ClassificationType
{
[Key]
[Column(Order = 1)
public int Id {get;set;}
[Key]
[Column(Order = 2, Type="varchar")]
[StringLength("100")]
public string TreeVersion {get;set}
}
Second entity:
class ClassConfig
{
public int Id {get;set;}
public string Name {get;set;}
public int? ClassificationTypeId { get; set; }
[Column(TypeName = "varchar")]
[StringLength(10)]
public string TreeVersion { get; set; }
}
With this in place, everything works fine - I can seed my tables with data and everything is peachy.
However, when I try to add a navigation property (and thereby foreign key from second entity to first entity), seeding fails.
I add this line to second entity:
public virtual ClassificationType ClassificationType {get;set;}
This causes this migration step:
public override void Up()
{
DropPrimaryKey("dbo.ClassConfig");
AlterColumn("dbo.ClassConfig", "Id", c => c.Int(nullable: false));
AddPrimaryKey("dbo.ClassConfig", "Id");
CreateIndex("dbo.ClassConfig", new[] { "Id", "TreeVersion" });
AddForeignKey("dbo.ClassConfig", new[] { "Id", "TreeVersion" }, "dbo.ClassificationTypes", new[] { "Id", "TreeVersion" });
}
Notice how it actually fails in deducting the foreign key columns, so I manually change this to
public override void Up()
{
DropPrimaryKey("dbo.ClassConfig");
AlterColumn("dbo.ClassConfig", "Id", c => c.Int(nullable: false));
AddPrimaryKey("dbo.ClassConfig", "Id");
CreateIndex("dbo.ClassConfig", new[] { "ClassificationTypeId", "TreeVersion" });
AddForeignKey("dbo.ClassConfig", new[] { "ClassificationTypeId", "TreeVersion" }, "dbo.ClassificationTypes", new[] { "Id", "TreeVersion" });
}
This creates the following SQL statement:
ALTER TABLE [dbo].[ClassConfig] DROP CONSTRAINT [PK_dbo.ClassConfig]
ALTER TABLE [dbo].[ClassConfig] ALTER COLUMN [Id] [int] NOT NULL
ALTER TABLE [dbo].[ClassConfig] ADD CONSTRAINT [PK_dbo.ClassConfig] PRIMARY KEY ([Id])
CREATE INDEX [IX_ClassificationTypeId_TreeVersion] ON [dbo].[ClassConfig]([ClassificationTypeId], [TreeVersion])
ALTER TABLE [dbo].[ClassConfigEntries] ADD CONSTRAINT [FK_dbo.ClassConfig_dbo.ClassificationTypes_ClassificationTypeId_TreeVersion] FOREIGN KEY ([ClassificationTypeId], [TreeVersion]) REFERENCES [dbo].[ClassificationTypes] ([Id], [TreeVersion])
Now, when I seed my tables, the script fails. The message is this:
Cannot insert explicit value for identity column in table
'ClassConfigEntries' when IDENTITY_INSERT is set to OFF.
Can anyone tell me why this is happening? The values I am trying to insert into the ClassConfig table are present in the ClassificationType table at point of insertion.
I have about 30 existing tables in out model, some of them with identity specified, and all of them work as they should when seeding and navigation properties are available when getting values from the database. It is only this corner of my model, that causes me head aches.
Table EMPLOYEE has MST_SQ (master-sequence) as both it's primary key, and as an FK to the primary key of table MASTER, which is also named MST_SQ. This table is used to join several other tables as well so that they all have the same PK. That is as far as my understanding goes.
I need to defined a 1 to 1 relationship in my model between class Employee and class Master, but I simply cannot find a way to do this. It seems only relationships with multiplicty allow an FK field to be speficied, and those that look like for 1 to 1, e.g. has optional(...)..WithRequiredPrincipal(....) has no FK space.
I could do some manual coding to link EMPLOYEE and MASTER when the are loaded, but how could I tell they were loaded. Is there any event that signals a POCO being populated from the DB? Or, the real question, how do I define this relationship in code?
From Relationships and Navigation Properties :
When working with 1-to-1 or 1-to-0..1 relationships, there is no
separate foreign key column, the primary key property acts as the
foreign key
From Configuring a Required-to-Optional Relationship (One-to–Zero-or-One) :
because the name of the property does not follow the convention the
HasKey method is used to configure the primary key
public class Master
{
public int MST_SQ { get; set; }
public virtual Employee Employee { get; set; }
}
public class Employee
{
public int MST_SQ { get; set; }
public virtual Master Master { get; set; }
}
The Employee has the MST_SQ property that is a primary key and a foreign key:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Master>().HasKey(m => m.MST_SQ);
modelBuilder.Entity<Employee>().HasKey(e => e.MST_SQ);
modelBuilder.Entity<Employee>()
.HasRequired(e => e.Master) //Employee is the Dependent and gets the FK
.WithOptional(m => m.Employee); //Master is the Principal
}
Generated migration code:
CreateTable(
"dbo.Employees",
c => new
{
MST_SQ = c.Int(nullable: false),
})
.PrimaryKey(t => t.MST_SQ)
.ForeignKey("dbo.Masters", t => t.MST_SQ)
.Index(t => t.MST_SQ);
CreateTable(
"dbo.Masters",
c => new
{
MST_SQ = c.Int(nullable: false, identity: true),
})
.PrimaryKey(t => t.MST_SQ);
So you don't need the "FK space" because EF makes it the foreign key without you having to specify it
I reimplementing database created automatically by SimpleMembershipProvider. Actually I have a question about 2 tables linking:
create table user_profiles
(
id int not null identity, /* PK */
username varchar(128) not null,
.........
);
create table membership
(
userid int not null, /* FK to user_profile. */
..............
);
I'd like to create relationship between initial POCO classes:
public class UserProfile : BaseType
{
public virtual Membership Membership { get; set; }
......
public string UserName { get; set; }
......
}
public class Membership
{
public virtual int UserId { get; set; }
public virtual UserProfile User { get; set; }
......
}
In Membership property UserId used as PK and in the same time as FK in database. I tried following configurations:
public class UserProfileConfiguration : EntityTypeConfiguration<UserProfile> {
public UserProfileConfiguration() {
HasKey(k => k.Id);
Map(m => m.ToTable("user_profiles"));
HasRequired(t => t.Membership)
.WithRequiredPrincipal(t1 => t1.User)
.Map(m => m.MapKey("userid"));
....
}
}
public class MembershipConfiguration : EntityTypeConfiguration<Membership> {
public MembershipConfiguration() {
HasKey(k => k.UserId);
Map(m => m.ToTable("webpages_Membership"));
//Property(x => x.UserId).HasColumnName("userid");
}
}
When line in MembershipConfiguration commented out (like in sample) command Add-Migration creates 2 records in migration command:
c => new {
UserId = c.Int(nullable: false, identity: true),
.............
userid = c.Int(nullable: false),
If I uncommenting it command failed with error message Each property name in a type must be unique. Property name 'userid' was already defined.
How could I claim required result, use column 'userid' as PK and FK in the same time?
(This looks like a long question, but it's not really, honest!)
I am trying to get a simple proof of concept working with Entity Framework 4 and the CTP 3 version of Code Only. It feels like I'm missing something really obvious and simple.
I have this following test which is failing:
[TestFixture]
public class ParentChildTests
{
[Test]
public void ChildRead_DatabaseContainsRelatedObjects_ParentIsNotNull()
{
var ctx = GetMyObjectContext();
var child = ctx.Children.Where(c => c.Id == 1).Single();
var parent = child.ParentTable;
Assert.That(parent, Is.Not.Null);
}
// GetMyObjectContext etc...
}
The read of child works fine and I get back a ChildTable whose ParentTableId value is '1' as I would expect, but the ParentTable property is NULL. I do not expect this because my POCOs have all virtual properties (see below) and EF4 has lazy loading enabled by default.
What am I missing?
Database
create table parent_table
(
parent_table_id int identity(1,1) primary key,
parent_table_name varchar(50) not null,
display_name varchar(50)
)
create table child_table
(
child_table_id int identity(1,1) primary key,
child_table_name varchar(50) not null,
parent_table_id int not null
)
alter table child_table add constraint FK_child_table__parent_table
foreign key (parent_table_id) references parent_table(parent_table_id)
POCO Entities
public class ParentTable
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string DisplayName { get; set; }
}
public class ChildTable
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual int ParentTableId { get; set; }
public virtual ParentTable ParentTable { get; set; }
}
Entity Configurations
public class ParentTableConfiguration : EntityConfiguration<ParentTable>
{
public ParentTableConfiguration()
{
MapSingleType(pt => new
{
parent_table_id = pt.Id,
parent_table_name = pt.Name,
display_name = pt.DisplayName,
})
.ToTable("dbo.parent_table");
Property( pt => pt.Id ).IsIdentity();
Property( pt => pt.Name ).IsRequired();
}
}
public class ChildTableConfiguration : EntityConfiguration<ChildTable>
{
public ChildTableConfiguration()
{
MapSingleType(ct => new
{
child_table_id = ct.Id,
child_table_name = ct.Name,
parent_table_id = ct.ParentTableId,
})
.ToTable("dbo.child_table");
Property( ct => ct.Id ).IsIdentity();
Property( ct => ct.Name ).IsRequired();
Relationship(ct => ct.ParentTable)
.HasConstraint((ct, pt) => ct.ParentTableId == pt.Id);
}
}
(Thanks for reading this far!)
As far as understand you just do not load this navigation property.
This will result in eager loading.
var child = ctx.Children.Include("ParentTable").Where(c => c.Id == 1).Single();
Or you could enable lazy loading by setting ctx.ContextOptions.LazyLoadingEnabled = true;