EF Code first Multi level inheritence issues - entity-framework

I have a multi level inheriance heriarchy consisting of the following classes:
public abstract class BasePoco
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
}
public class Activity : BasePoco
{
public ActivityType ActivityType { get; set; }
[MaxLength(1000)]
public string Description { get; set; }
}
Now there is a special type of Activity which is called a data capture activity.
Which is of two types: DataCaptureActivity and MasterDataCaptureActivity
public class DataCaptureActivityBase : Activity
{
[MaxLength(100)]
public string Title { get; set; }
}
Derived Classes :
[Table("DataCaptureActivities")]
public class DataCaptureActivity : DataCaptureActivityBase
{
public virtual DataCaptureActivityType DataCaptureActivityType { get; set; }
}
[Table("MasterDataCaptureActivities")]
public class MasterDataCaptureActivity : DataCaptureActivityBase
{
public virtual string SomeOtherField{ get; set; }
}
The problem is upon creating a migration, The column Title which should be a part of DataCaptureActivity is actually coming up as a part of the Activity table.
Please note that DataCaptureActivityBase should not be a table in my schema. It is just for holding the common parameters DataCaptureActivities table and its inherited types.
The Schema I am targeting is :
Activity
-------------------------------
Id | ActivityType | Description
DataCaptureActivity
--------------------------------
Id | Title
MasterDataCaptureActivity
--------------------------------
Id | Title | SomeOtherField

Your context is similar to this
public DbSet<Activity> Activity { get; set; }
public DbSet<DataCaptureActivity> DataCaptureActivities { get; set; }
public DbSet<MasterDataCaptureActivity> MasterDataCaptureActivities { get; set; }
What you are conceptually saying to EF is that DataCaptureActivity is an Activity and that MasterDataCaptureActivity is an Activity. On the database you are creating all the three entities.
With this model, the statement
context.Activities.ToList();
retrieve all the Activities (the union of the three sets).
To do it with your model, EF on the database will create the Activity table with a Discriminator. The table structures will be this (look at 1-1 relationships):
ExecuteNonQuery==========
CREATE TABLE [Activity] (
[Id] int not null identity(1,1)
, [ActivityType] int not null
, [Description] text null
, [Title] varchar(100) null
, [Discriminator] varchar(128) not null
);
ALTER TABLE [Activity] ADD CONSTRAINT [PK_Activity_7ea65be8] PRIMARY KEY ([Id])
ExecuteNonQuery==========
CREATE TABLE [DataCaptureActivities] (
[Id] int not null
, [DataCaptureActivityType] int not null
);
ALTER TABLE [DataCaptureActivities] ADD CONSTRAINT [PK_DataCaptureActivities_7ea65be8] PRIMARY KEY ([Id])
ExecuteNonQuery==========
CREATE TABLE [MasterDataCaptureActivities] (
[Id] int not null
, [SomeOtherField] text null
);
ALTER TABLE [MasterDataCaptureActivities] ADD CONSTRAINT [PK_MasterDataCaptureActivities_7ea65be8] PRIMARY KEY ([Id])
ExecuteNonQuery==========
CREATE INDEX [IX_Id] ON [DataCaptureActivities] ([Id])
ExecuteNonQuery==========
CREATE INDEX [IX_Id] ON [MasterDataCaptureActivities] ([Id])
ExecuteNonQuery==========
ALTER TABLE [DataCaptureActivities] ADD CONSTRAINT [FK_DataCaptureActivities_Activity_Id] FOREIGN KEY ([Id]) REFERENCES [Activity] ([Id])
ExecuteNonQuery==========
ALTER TABLE [MasterDataCaptureActivities] ADD CONSTRAINT [FK_MasterDataCaptureActivities_Activity_Id] FOREIGN KEY ([Id]) REFERENCES [Activity] ([Id])
Also, this statement
using (var context = new Context(GetConnection()))
{
context.DataCaptureActivities.Add(new DataCaptureActivity() {Description = "Description"});
context.SaveChanges();
}
Will generate this DML statements (2 insert statements!!!)
ExecuteDbDataReader==========
insert into [Activity]([ActivityType], [Description], [Title], [Discriminator])
values (#p0, #p1, null, #p2);
select [Id]
from [Activity]
where [Id] = ##identity
#p0 = 0
#p1 = Description
#p2 = DataCaptureActivity
ExecuteNonQuery==========
insert into [DataCaptureActivities]([Id], [DataCaptureActivityType])
values (#p0, #p1);
#p0 = 1
#p1 = 0
The solution to target your tables
You need to change your model saying that DataCaptureActivities and MasterDataCaptureActivities are not Activities.
For example:
public abstract class BasePoco
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
}
public class BaseActivity : BasePoco
{
public ActivityType ActivityType { get; set; }
[MaxLength(1000)]
public string Description { get; set; }
}
[Table("Activity")]
public class Activity : BaseActivity
{
}
public class DataCaptureActivityBase : BaseActivity
{
[MaxLength(100)]
public string Title { get; set; }
}
[Table("DataCaptureActivities")]
public class DataCaptureActivity : DataCaptureActivityBase
{
public virtual DataCaptureActivityType DataCaptureActivityType { get; set; }
}
[Table("MasterDataCaptureActivities")]
public class MasterDataCaptureActivity : DataCaptureActivityBase
{
public virtual string SomeOtherField { get; set; }
}
In this case, the tables structures will be this
ExecuteNonQuery==========
CREATE TABLE [Activity] (
[Id] int not null identity(1,1)
, [ActivityType] int not null
, [Description] text null
);
ALTER TABLE [Activity] ADD CONSTRAINT [PK_Activity_2b28bd47] PRIMARY KEY ([Id])
ExecuteNonQuery==========
CREATE TABLE [DataCaptureActivities] (
[Id] int not null identity(1,1)
, [DataCaptureActivityType] int not null
, [Title] varchar(100) null
, [ActivityType] int not null
, [Description] text null
);
ALTER TABLE [DataCaptureActivities] ADD CONSTRAINT [PK_DataCaptureActivities_2b28bd47] PRIMARY KEY ([Id])
ExecuteNonQuery==========
CREATE TABLE [MasterDataCaptureActivities] (
[Id] int not null identity(1,1)
, [SomeOtherField] text null
, [Title] varchar(100) null
, [ActivityType] int not null
, [Description] text null
);
ALTER TABLE [MasterDataCaptureActivities] ADD CONSTRAINT [PK_MasterDataCaptureActivities_2b28bd47] PRIMARY KEY ([Id])
But in this case, if you want to enum all the ActivityBase you need to join them (Union) on the client.

Try doing the below steps.
Mark Activity class with a Table Attribute
Mark DataCaptureActivityBase as abstract
In OnModelCreating Map Derived Entities with MapInheritedProperties();
Hope this helps

Related

TPH, Is possible such a data structure like this

I'm trying to create a tables using Code-First in EF.
I created many versions of my code but I put here only one. It almost works, it means this code creates sql but doesn't work properly.
What I want to get:
I have 2 simple tables : Order and Payer.
Also, I want to create a third table based on foreign keys of these 2.
It wont create it that meet the following rules:
Each Order has exactly one Base Payer. (So I don't need complex key in this case. OrderId can exist only one in this table if record is type of (Base)OrderPayer
Each Order can have 0,1 or many (Extra)OrderPayer. ( So in this case i need put into table complex Key (OrderId + PayerId)
here is my last C# code
public class Order
{
[Key]
public int OrderId { get; set; }
public string OrderName { get; set; }
public virtual BaseOrderPayer BaseOrderPayer { get; set; }
public virtual ICollection<ExtraOrderPayer> ExtraOrderPayers { get; set; }
}
public class Payer
{
[Key]
public int PayerId { get; set; }
public string PayerName { get; set; }
}
public abstract class OrderPayer
{
[Column(Order = 0), Key, ForeignKey("Order")]
public int OrderId { get; set; }
public virtual Order Order { get; set; }
}
public class BaseOrderPayer : OrderPayer
{
[ForeignKey("BasePayer")]
public virtual int? BasePayerId { get; set; }
public virtual Payer BasePayer { get; set; }
}
public class ExtraOrderPayer : OrderPayer
{
[Column(Order = 1), Key, ForeignKey("ExtraPayer")]
public virtual int? ExtraPayerId { get; set; }
public virtual Payer ExtraPayer { get; set; }
}
and SQL that was generated by Update-Database -Script
CREATE TABLE [dbo].[OrderPayers] (
[OrderId] [int] NOT NULL,
[ExtraPayerId] [int],
[BasePayerId] [int],
[Discriminator] [nvarchar](128) NOT NULL,
[Order_OrderId] [int],
CONSTRAINT [PK_dbo.OrderPayers] PRIMARY KEY ([OrderId])
)
CREATE TABLE [dbo].[Payers] (
[PayerId] [int] NOT NULL IDENTITY,
[PayerName] [nvarchar](30),
CONSTRAINT [PK_dbo.Payers] PRIMARY KEY ([PayerId])
)
CREATE TABLE [dbo].[Orders] (
[OrderId] [int] NOT NULL IDENTITY,
[OrderName] [nvarchar](30),
[BaseOrderPayer_OrderId] [int],
CONSTRAINT [PK_dbo.Orders] PRIMARY KEY ([OrderId])
)
CREATE INDEX [IX_OrderId] ON [dbo].[OrderPayers]([OrderId])
CREATE INDEX [IX_ExtraPayerId] ON [dbo].[OrderPayers]([ExtraPayerId])
CREATE INDEX [IX_BasePayerId] ON [dbo].[OrderPayers]([BasePayerId])
CREATE INDEX [IX_Order_OrderId] ON [dbo].[OrderPayers]([Order_OrderId])
CREATE INDEX [IX_BaseOrderPayer_OrderId] ON [dbo].[Orders]([BaseOrderPayer_OrderId])
ALTER TABLE [dbo].[OrderPayers] ADD CONSTRAINT [FK_dbo.OrderPayers_dbo.Payers_BasePayerId] FOREIGN KEY ([BasePayerId]) REFERENCES [dbo].[Payers] ([PayerId])
ALTER TABLE [dbo].[OrderPayers] ADD CONSTRAINT [FK_dbo.OrderPayers_dbo.Payers_ExtraPayerId] FOREIGN KEY ([ExtraPayerId]) REFERENCES [dbo].[Payers] ([PayerId])
ALTER TABLE [dbo].[OrderPayers] ADD CONSTRAINT [FK_dbo.OrderPayers_dbo.Orders_Order_OrderId] FOREIGN KEY ([Order_OrderId]) REFERENCES [dbo].[Orders] ([OrderId])
ALTER TABLE [dbo].[OrderPayers] ADD CONSTRAINT [FK_dbo.OrderPayers_dbo.Orders_OrderId] FOREIGN KEY ([OrderId]) REFERENCES [dbo].[Orders] ([OrderId])
ALTER TABLE [dbo].[Orders] ADD CONSTRAINT [FK_dbo.Orders_dbo.OrderPayers_BaseOrderPayer_OrderId] FOREIGN KEY ([BaseOrderPayer_OrderId]) REFERENCES [dbo].[OrderPayers] ([OrderId])
but his SQL it doesn't even work
I can't insert Order because it need OrderPayer and vice versa :)
Is it possible to fix it according to my expectations ?
p.s.
the tables are very simplified, in fact they contain many other fields

How to many-to-many relation with the same class with Entity-framework

I would like to create many-to-many relation with .NET MVC EF. I have Client model (it could be employer or employee or colleague etc., company or person). So any of the client can have employees or employer. I am not sure how to set this relation in EF.
My models (parts of db context):
public partial class Client
{
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<ClientRelation> TargetClientRelations { get; set; }
public virtual ICollection<ClientRelation> SourceClientRelations { get; set; }
}
public enum ClientRelationType
{
Employer_Employee
}
public class ClientRelation
{
public long Id { get; set; }
public virtual Client Client { get; set; }
public virtual Client TargetClient { get; set; }
public ClientRelationType ClientRelationType { get; set; }
}
The problem is EF creates this relation table. I gues I should create some mapping..
CREATE TABLE [dbo].[ClientRelations](
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[Client_Id] [bigint] NULL,
[TargetClient_Id] [bigint] NULL,
[Client_Id1] [bigint] NULL,
[Client_Id2] [bigint] NULL,
[ClientRelationType] [int] NOT NULL,
CONSTRAINT [PK_dbo.ClientRelations] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[ClientRelations] ADD DEFAULT ((0)) FOR [ClientRelationType]
GO
ALTER TABLE [dbo].[ClientRelations] WITH CHECK ADD CONSTRAINT [FK_dbo.ClientRelations_dbo.Clients_Client_Id] FOREIGN KEY([Client_Id])
REFERENCES [dbo].[Clients] ([Id])
GO
ALTER TABLE [dbo].[ClientRelations] CHECK CONSTRAINT [FK_dbo.ClientRelations_dbo.Clients_Client_Id]
GO
ALTER TABLE [dbo].[ClientRelations] WITH CHECK ADD CONSTRAINT [FK_dbo.ClientRelations_dbo.Clients_Client_Id1] FOREIGN KEY([Client_Id1])
REFERENCES [dbo].[Clients] ([Id])
GO
ALTER TABLE [dbo].[ClientRelations] CHECK CONSTRAINT [FK_dbo.ClientRelations_dbo.Clients_Client_Id1]
GO
ALTER TABLE [dbo].[ClientRelations] WITH CHECK ADD CONSTRAINT [FK_dbo.ClientRelations_dbo.Clients_Client_Id2] FOREIGN KEY([Client_Id2])
REFERENCES [dbo].[Clients] ([Id])
GO
ALTER TABLE [dbo].[ClientRelations] CHECK CONSTRAINT [FK_dbo.ClientRelations_dbo.Clients_Client_Id2]
GO
ALTER TABLE [dbo].[ClientRelations] WITH CHECK ADD CONSTRAINT [FK_dbo.ClientRelations_dbo.Clients_TargetClient_Id] FOREIGN KEY([TargetClient_Id])
REFERENCES [dbo].[Clients] ([Id])
GO
ALTER TABLE [dbo].[ClientRelations] CHECK CONSTRAINT [FK_dbo.ClientRelations_dbo.Clients_TargetClient_Id]
GO
You need to add some additional configuration to your model to specify which property is related with.
Using Data Anotations you will need to use InverseProperty attribute:
public class ClientRelation
{
public long Id { get; set; }
[InverseProperty("SourceClientRelations")]
public virtual Client Client { get; set; }
[InverseProperty("TargetClientRelations")]
public virtual Client TargetClient { get; set; }
public ClientRelationType ClientRelationType { get; set; }
}
If you decide go for Fluent Api then your configurations would be:
modelBuilder.Entity<ClientRelation>()
.HasOptional(l => l.TargetClient)
.WithMany(p => p.TargetClientRelations);
modelBuilder.Entity< ClientRelation>()
.HasOptional(l => l.Client)
.WithMany(p => p.SourceClientRelations);

ef7 sqlite, configure identity column

i try to create sqlite db using EF7.
the class:
public class Item
{
//primaery key, not identity
public string id { get; set; }
//not primery key, but needed autoincerment
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int numberInc { get; set; }
}
but in the table genereted numberInc its a normal column:
CREATE TABLE "VideoItem" (
"id" TEXT NOT NULL CONSTRAINT "PK_VideoItem" PRIMARY KEY,
"numberInc" INTEGER NOT NULL,
)
i also tried setting the column in OnModelCreating:
modelBuilder.Entity<VideoItem>().Property(x => x.numberInc).UseSqlServerIdentityColumn();
but any effect.
iwm use EntityFramework.Sqlite 7.0.0-rc1-final.

Entity Framework Entity Splitting Into a single POCO

I'm using Entity Framework 6.1.3 and I want to define a single POCO class (Subscriber) which hydrates itself from three SQL tables. I have tried to follow various on-line references including this one from Deliveron. Here are the table/model definitions...
SQL Tables
CREATE TABLE [Subscriber]
(
[SubscriberId] INT NOT NULL PRIMARY KEY,
[Username] NVARCHAR(50) NOT NULL,
[Email] NVARCHAR(100) NOT NULL
)
CREATE TABLE [SubscriberDetails]
(
[SubscriberId] INT NOT NULL PRIMARY KEY,
[FirstName] NVARCHAR(50) NOT NULL,
[LastName] NVARCHAR(50) NOT NULL,
CONSTRAINT [FK_SubscriberDetails_Subscriber]
FOREIGN KEY ([SubscriberId])
REFERENCES [Subscriber] ([SubscriberId]) ON DELETE CASCADE
)
CREATE TABLE [SubscriberProfile]
(
[SubscriberId] INT NOT NULL PRIMARY KEY,
[Bio] NVARCHAR(MAX) NULL,
CONSTRAINT [FK_SubscriberProfile_Subscriber]
FOREIGN KEY ([SubscriberId])
REFERENCES [Subscriber] ([SubscriberId]) ON DELETE CASCADE
)
Domain Model
public class Subscriber {
public int SubscriberId { get; set; }
public string Username { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Bio { get; set; }
};
In my DataContext OnModelCreating() method, I have added the following code:
modelBuilder.Entity<Subscriber>()
.HasKey(m => m.SubscriberId)
.Map(m => {
m.ToTable("Subscriber");
m.Properties(s => new {
s.SubscriberId,
s.UserName,
s.Email
});
})
.Map(m => {
m.ToTable("SubscriberDetails");
m.Properties(s => new {
s.FirstName,
s.LastName
});
})
.Map(m => {
m.ToTable("SubscriberProfile");
m.Properties(s => new {
s.Bio
});
});
Whilst this compiles and loads, when I try and run a query against the DataContext I get the following error:
System.Data.SqlClient.SqlException: Invalid object name
'dbo.Subscriber21'
Can anyone give me any direction on what I am doing wrong?

EF Code First: The INSERT statement conflicted with the FOREIGN KEY constraint

This is my trial project using breeze/angular/EF. I don't understand why I get this error because I thought I had the same structure working before.
public class TshirtOrder
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<OrderItem> OrderItems { get; set; }
}
public class OrderItem
{
public int Id { get; set; }
[ForeignKey("Type")]
public int TshirtTypeId { get; set; }
public virtual TshirtType Type { get; set; }
[ForeignKey("Size")]
public int TshirtSizeId { get; set; }
public virtual TshirtSize Size { get; set; }
public double UnitPrice { get; set; }
public int Quantity { get; set; }
[ForeignKey("TshirtOrder")]
public int TshirtOrderId { get; set; }
public TshirtOrder TshirtOrder { get; set; }
}
The table definition looks like this:
CREATE TABLE [dbo].[TshirtOrder] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[Name] NVARCHAR (MAX) NULL,
CONSTRAINT [PK_dbo.TshirtOrder] PRIMARY KEY CLUSTERED ([Id] ASC)
);
CREATE TABLE [dbo].[OrderItem] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[TshirtTypeId] INT NOT NULL,
[TshirtSizeId] INT NOT NULL,
[UnitPrice] FLOAT (53) NOT NULL,
[Quantity] INT NOT NULL,
[TshirtOrderId] INT NOT NULL,
CONSTRAINT [PK_dbo.OrderItem] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_dbo.OrderItem_dbo.TshirtType_TshirtTypeId] FOREIGN KEY ([TshirtTypeId]) REFERENCES [dbo].[TshirtType] ([Id]) ON DELETE CASCADE,
CONSTRAINT [FK_dbo.OrderItem_dbo.TshirtSize_TshirtSizeId] FOREIGN KEY ([TshirtSizeId]) REFERENCES [dbo].[TshirtSize] ([Id]) ON DELETE CASCADE,
CONSTRAINT [FK_dbo.OrderItem_dbo.TshirtOrder_TshirtOrderId] FOREIGN KEY ([TshirtOrderId]) REFERENCES [dbo].[TshirtOrder] ([Id]) ON DELETE CASCADE
);
This is how it gets saved in Breeze datacontext:
function _createTshirtOrder() {
var order = manager.createEntity("TshirtOrder");
order.orderItems.push(createOrderItem(lookups.tshirtTypes[0], lookups.tshirtSizes[0], 10));
common.saveEntity(order);
return order;
function createOrderItem(type, size, unitPrice) {
var item = manager.createEntity("OrderItem");
item.type = type;
item.size = size;
item.unitPrice = unitPrice;
item.quantity = 0;
return item;
}
}
Here is the exact error:
{"The INSERT statement conflicted with the FOREIGN KEY constraint \"FK_dbo.OrderItem_dbo.TshirtOrder_TshirtOrderId\". The conflict occurred in database \"dbbb\", table \"dbo.TshirtOrder\", column 'Id'.\r\nThe statement has been terminated."}
So, where is the problem?
I don't know what your "saveEntity" method looks like but I'm guessing it calls
entityManager.saveChanges([order]);
If so, then the problem is that you are only saving the order and NOT the orderItem as well, because you told it to only save the one order. Breeze tracks any changes to the entityManager so a better solution is usually to just let Breeze figure it out for you. i.e.
entityManager.saveChanges(); or entityManager.saveChanges(null, ... );
Which will save all added, modified or deleted records in the entityManager.
Alternately you can specify all of the entities you want saved.
entityManager.saveChanges([order, orderItem1, orderItem2, ... ]);