I am using Entity Framework 5.0.0.0 in a .net 4.5 console application and I have to access a database with two tables in it with a foreign key relationship between them like so:
The odd thing about it is that the foreign key is between B(Almost1, Almost2)
and A(Almost1, Almost2)
not from B(AId)
to A(AId)
. This is allowed by SQL server as Almost1
and Almost2
combined are unique and neither are nullable (on table A
at least - on B
they are as it is an optional relationship but that is by the by).
Here's some SQL for creating this situation:
CREATE TABLE [dbo].[A](
[AId] [int] IDENTITY(1,1) NOT NULL,
[Almost1] [int] NOT NULL,
[Almost2] [int] NOT NULL,
CONSTRAINT [PK_A] PRIMARY KEY CLUSTERED
(
[AId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
CONSTRAINT [A_Constraint] UNIQUE NONCLUSTERED
(
[Almost1] ASC,
[Almost2] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
CREATE TABLE [dbo].[B](
[BId] [int] IDENTITY(1,1) NOT NULL,
[Almost1] [int] NULL,
[Almost2] [int] NULL,
CONSTRAINT [PK_B] PRIMARY KEY CLUSTERED
(
[BId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
ALTER TABLE [dbo].[B] ADD CONSTRAINT [FK_A_B] FOREIGN KEY([Almost1], [Almost2])
REFERENCES [dbo].[A] ([Almost1], [Almost2])
The thing is, it seems that it is not allowed by Entity Framework - is this the case or am I just not defining my model correctly?
Here is my c#:
public class MyContext : DbContext
{
public MyContext(string connectionString) : base(connectionString)
{
MyAs = Set<A>();
MyBs = Set<B>();
}
public DbSet<A> MyAs { get; private set; }
public DbSet<B> MyBs { get; private set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
var aEntity = modelBuilder.Entity<A>();
aEntity.ToTable("A");
aEntity.HasKey(a => a.AId);
var bEntity = modelBuilder.Entity<B>();
bEntity.ToTable("B");
bEntity.HasKey(a => a.BId);
bEntity
.HasOptional(b => b.A)
.WithMany(a => a.Bs)
.Map(m => m.MapKey("Almost1", "Almost2"));
}
}
public class A
{
public int AId { get; set; }
public int Almost1 { get; set; }
public int Almost2 { get; set; }
public virtual ICollection<B> Bs { get; private set; }
public void AddB(B b)
{
if (b == null) throw new ArgumentNullException("b");
if (Bs == null) Bs = new List<B>();
if (!Bs.Contains(b)) Bs.Add(b);
b.A = this;
}
}
public class B
{
public int BId { get; set; }
public virtual A A { get; set; }
}
class Program
{
static void Main()
{
using (var ctx = new MyContext(@"connection string"))
{
ctx.MyAs.Add(new A { Almost1 = 1, Almost2 = 1 });
ctx.SaveChanges();
}
}
}
It throws an InvalidOperationException
saying:
The specified association foreign key columns 'Almost1, Almost2' are invalid. The number of columns specified must match the number of primary key columns.
If I ignore the AId
column and instead make Almost1
and Almost2
a composite primary key, so my OnModelCreating
method now looks like this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
var aEntity = modelBuilder.Entity<A>();
aEntity.ToTable("A");
aEntity.HasKey(a => new { a.Almost1, a.Almost2 });
aEntity.Ignore(a => a.AId);
var bEntity = modelBuilder.Entity<B>();
bEntity.ToTable("B");
bEntity.HasKey(a => a.BId);
bEntity
.HasOptional(b => b.A)
.WithMany(a => a.Bs)
.Map(m => m.MapKey("Almost1", "Almost2"));
}
It works, but I don't really want to do this as there is also a table (let's call it C
) that relates to A
in the traditional way by having an AId
column and the foreign key is going from C.AId
to A.AId
.
Yeah, it's a bit weird I know - but is it possible to deal with this in Entity Framework?
As mentioned, since EF doesn't support unique keys and other SQL objects. I ended up creating some scripts as embedded resources and have them execute as part of the seed process of the drop/create initializer.
Not sure if this will allow you to navigate between the objects in code, but it works pretty nicely to have the database get updated in one process.