Have an Entity Framework field reference one of several derived tables via a discriminator (Using TPT) - sql

I'm trying to find a way to create this relationship from Entity Framework 5's Code First.
In the example below, I have some Stock which can either belong to a Warehouse or a Row.
Stock Table
'Id', 'SerialNo', 'Discriminator', 'LocationId'
345, ABC123, Warehouse, 4
123, ABC124, Row, 12
Warehouse Table
'Id', 'Name'
4, WH-One
8, WH-Two
Row Table
'Id', 'Name'
6, RowA
12, RowB
The Discriminator column on the table Stock determines which of the related tables the LocationId is referring to. For example:
Stock with Id '345' has a reference to row with Id: 4 (WH-One) on the Warehouse table
Can this type of relationship be mapped with EF Code First? It's certainly no trouble to query against this structure in SQL Server.
SELECT Stock.SerialNo, COALESCE(Warehouse.Name, Row.Name) AS Name
FROM Stock
LEFT OUTER JOIN Warehouse ON Stock.Id = Warehouse.Id AND Stock.Discriminator = 'Warehouse'
LEFT OUTER JOIN Row ON Stock.Id = Row.Id AND Stock.Discriminator = 'Row'
Which should return:
SerialNo, Name
ABC123, WH-One
ABC124, RowB
Ideally, I'd like to have either an interface or abstract base class which Warehouse and Row and derive from. And a navigation property called Location that would refer to either table cast to the abstract/interface.
Alternatively, I'd like to know if anyone has a better suggestion to describe a relationship in EF Code First that allows one entity to reference one of several other similar entities.

It's Code First so I would do this:
First - create the classes I want in code:
public abstract class Location
{
public int ID { get; set; }
public string Name { get; set; }
public ICollection<Stock> StockItems { get; set; }
}
public class Warehouse : Location
{
public int NumberOfLoadingDocks { get; set; }
}
public class Row : Location
{
public int NumberOfShelves { get; set; }
}
public class Stock
{
public int ID { get; set; }
public string SerialNo { get; set; }
public int LocationID { get; set; }
}
Then add them to the context:
public partial class MyContext : DbContext
{
public DbSet<Location> Locations { get; set; }
public DbSet<Warehouse> Warehouses { get; set; }
public DbSet<Row> Rows { get; set; }
public DbSet<Stock> Stocks { get; set; }
}
Then in Package-Manager console:
Add-Migration "Locations"
Update-Database -Script
That lets me see the tables Entity Framework would create:
CREATE TABLE [dbo].[Stocks] (
[ID] [int] NOT NULL IDENTITY,
[SerialNo] [nvarchar](max),
[LocationID] [int] NOT NULL,
CONSTRAINT [PK_dbo.Stocks] PRIMARY KEY ([ID])
)
CREATE INDEX [IX_LocationID] ON [dbo].[Stocks]([LocationID])
CREATE TABLE [dbo].[Locations] (
[ID] [int] NOT NULL IDENTITY,
[Name] [nvarchar](max),
[NumberOfShelves] [int],
[NumberOfLoadingDocks] [int],
[Discriminator] [nvarchar](128) NOT NULL,
CONSTRAINT [PK_dbo.Locations] PRIMARY KEY ([ID])
)
ALTER TABLE [dbo].[Stocks] ADD CONSTRAINT [FK_dbo.Stocks_dbo.Locations_LocationID] FOREIGN KEY ([LocationID]) REFERENCES [dbo].[Locations] ([ID])
INSERT [dbo].[__MigrationHistory]([MigrationId], [ContextKey], [Model], [ProductVersion])
VALUES (N'201309120832257_Locations'....
Not the same tables that you have. This is Table-per-Hierarchy inheritance. If you are not happy with that then you need to look at Table Per Type inheritance
EDIT
To get TPT inheritance you can add the Table attribute to the inherited classes like this:
[System.ComponentModel.DataAnnotations.Schema.Table("Warehouses")]
public class Warehouse : Location
{
public int NumberOfLoadingDocks { get; set; }
}
[System.ComponentModel.DataAnnotations.Schema.Table("Rows")]
public class Row : Location
{
public int NumberOfShelves { get; set; }
}
And now you get separate tables for each type:
CREATE TABLE [dbo].[Locations] (
[ID] [int] NOT NULL IDENTITY,
[Name] [nvarchar](max),
CONSTRAINT [PK_dbo.Locations] PRIMARY KEY ([ID])
)
CREATE TABLE [dbo].[Rows] (
[ID] [int] NOT NULL,
[NumberOfShelves] [int] NOT NULL,
CONSTRAINT [PK_dbo.Rows] PRIMARY KEY ([ID])
)
CREATE INDEX [IX_ID] ON [dbo].[Rows]([ID])
CREATE TABLE [dbo].[Warehouses] (
[ID] [int] NOT NULL,
[NumberOfLoadingDocks] [int] NOT NULL,
CONSTRAINT [PK_dbo.Warehouses] PRIMARY KEY ([ID])
)
CREATE INDEX [IX_ID] ON [dbo].[Warehouses]([ID])
ALTER TABLE [dbo].[Stocks] ADD CONSTRAINT [FK_dbo.Stocks_dbo.Locations_LocationID] FOREIGN KEY ([LocationID]) REFERENCES [dbo].[Locations] ([ID])
ALTER TABLE [dbo].[Rows] ADD CONSTRAINT [FK_dbo.Rows_dbo.Locations_ID] FOREIGN KEY ([ID]) REFERENCES [dbo].[Locations] ([ID])
ALTER TABLE [dbo].[Warehouses] ADD CONSTRAINT [FK_dbo.Warehouses_dbo.Locations_ID] FOREIGN KEY ([ID]) REFERENCES [dbo].[Locations] ([ID])
And if you are not happy with that you should look at Table Per Concrete Type Inheritance...

Related

Entity Framework - not using linked table id in insert query

I have 2 tables, Foo and Bar, Foo has a link to a Bar record
public class Foo
{
public int Id { get; set; }
public Bar SomeBar{ get; set; }
}
public class Bar
{
public int? Id { get; set; }
...
}
the SQL table (with the FK constraint between the two tables):
CREATE TABLE [dbo].[Foo] (
[Id] INT IDENTITY (1, 1) NOT NULL PRIMARY KEY,
[SomeBarId] INT NOT NULL,
CONSTRAINT [FK_Foo_Bar] FOREIGN KEY ([SomeBarId]) REFERENCES [Bar]([Id]),
);
when I save the table, Entity does not use SomeBarId in the query, producing an error, while I have set a FK constraint between the two tables
Cannot insert the value NULL into column 'SomeBarId ', table 'dbo.foo'; column does not allow nulls. INSERT fails.
how do I tell entity to use the field SomeBarId when doing the insert ?
var someBar = await _context.Bars.FindAsync(1); // fetch a Bar record
foo.SomeBar = someBar; // linking the objects
_context.Foo.Add(foo);
await _context.SaveChangesAsync();
I expect EF to get someBar.Id and use it in the query when inserting Foo in DB
thanks for the time you'll spend helping me on this
Try adding ForeignKey attribute in class Foo and remove the nullable Id in Bar
public class Foo
{
[ForeignKey("Bar")]
public int Id { get; set; }
public Bar SomeBar{ get; set; }
}
public class Bar
{
public int Id { get; set; }
...
}

.Net Core EF doesn't allow me to enter same Foreign Key

I have got a Model with Foreign Key. Some how .NET Core EF doesn't let me enter the same Foreign key twice to the table.
public class DolsMcaClientModel
{
[Key]
public int DolsMcaClientID { get; set; }
public int DolsMcaItemID { get; set; }
public virtual DolsMcaItemModel DolsMcaItemModel { get; set; }
public int ClientID { get; set; }
[Required]
public int FileID { get; set; }
public virtual FileModel FileModel { get; set; }
}
In this table DolsMcaClientID is my Identity Column and DolsMcaItemID is my Foreign column. DolsMcaItemModel is my navigation property.
I can't enter the same DolsMcaItemID for different client.
DolsMcaItemID acting like a primary key in the table. But it is not.
ERROR: System.Data.SqlClient.SqlException: Cannot insert duplicate key row in object 'dbo.DolsMcaClientModels' with unique index 'IX_DolsMcaClientModels_DolsMcaItemID'. The duplicate key value is (1).
If I delete DolsMcaItemID 1 item from the table, then i can add new entry to the table with DolsMcaItemId 1, but only once with any clientID... i can't enter DolsMcaItem 1 for with any other clientID
My SQL Table create query is:
CREATE TABLE [dbo].[DolsMcaClientModels](
[DolsMcaClientID] [int] IDENTITY(1,1) NOT NULL,
[DolsMcaItemID] [int] NOT NULL,
[ClientID] [int] NOT NULL,
[FileID] [int] NOT NULL,
CONSTRAINT [PK_DolsMcaClientModels] PRIMARY KEY CLUSTERED
(
[DolsMcaClientID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
ALTER TABLE [dbo].[DolsMcaClientModels] WITH CHECK ADD CONSTRAINT [FK_DolsMcaClientModels_DolsMcaItemModels_DolsMcaItemID] FOREIGN KEY([DolsMcaItemID])
REFERENCES [dbo].[DolsMcaItemModels] ([DolsMcaItemID])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[DolsMcaClientModels] CHECK CONSTRAINT [FK_DolsMcaClientModels_DolsMcaItemModels_DolsMcaItemID]
GO
ALTER TABLE [dbo].[DolsMcaClientModels] WITH CHECK ADD CONSTRAINT [FK_DolsMcaClientModels_FileModels_FileID] FOREIGN KEY([FileID])
REFERENCES [dbo].[FileModels] ([FileID])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[DolsMcaClientModels] CHECK CONSTRAINT [FK_DolsMcaClientModels_FileModels_FileID]
GO
The UNIQUE index [IX_DolsMcaClientModels_DolsMcaItemID] on that column is preventing you from committing another record with the same [DolsMcaItemID].
Fix is to drop the unique index:
ALTER TABLE [dbo].[DolsMcaClientModels] DROP INDEX [IX_DolsMcaClientModels_DolsMcaItemID];
CREATE INDEX [IX_DolsMcaClientModels_DolsMcaItemID] ON [dbo].[DolsMcaClientModels] ([DolsMcaItemID])
Don't forget to scaffold your db context to remove the constraint from code.

invalid Column name Practice_PK

I have the below code where am working on an ASp.net MVC application
public class Element
{
/// <summary>
/// Primary Key
/// </summary>
[Key]
public int Pk { get; set; }
[ForeignKey("Practice_PK")]
[Display(Name = "Practices")]
public Practice Practice { get; set; }
[Display(Name = "Practices")]
public int Practice_PK { get; set; }
}
Database
CREATE TABLE [dbo].[Elements] (
[Pk] INT IDENTITY (1, 1) NOT NULL,
[Practice_PK] INT NULL,
CONSTRAINT [FK_dbo.Elements_dbo.Practices_Practice_PK] FOREIGN KEY ([Practice_PK]) REFERENCES [dbo].[Practices] ([PK]) ON DELETE CASCADE
);
GO
CREATE NONCLUSTERED INDEX [IX_Practice_PK]
ON [dbo].[Elements]([Practice_PK] ASC);
I am getting the Error "Invalid Column name Practice_PK" everytime i try to create, Edit a page. I know this has been addressed many times but the error itself is so ambiguous i dont know how to resolve.
I just resolved my issue .Basically the 'Practice_PK' returning the error was for another variable ElementType (Not included the code above ) that is of the type ElementType that should have had a Practice_PK varaible but did not

Fluent NHibernate mapping to return description from lookup table

We have the following database structure:
UserTeam table
Id (PK, int not null)
UserId (FK, int, not null)
TeamId (FK, int, not null)
RoleId (FK, int, not null)
libRole table
Id (PK, int not null)
Description (varchar(255), not null)
And we have an entity as follows:
public class UserTeam
{
public int Id { get; set; }
public Team Team { get; set; }
public User User { get; set; }
public int RoleId { get; set; }
public string Role { get; set; }
}
We are using Fluent NHibernate and configuring NHibernate automatically (ie, using Automapping classes with overrides).
We are trying to get JUST the description column from the libRole table into the "Role" property on the UserTeam table, but really struggling. The following is the closest we have got:
public class UserTeamMap : IAutoMappingOverride<UserTeam>
{
public void Override( FluentNHibernate.Automapping.AutoMapping<UserTeam> mapping )
{
mapping.References( m => m.User ).Column( "UserId" );
mapping.References( m => m.Team ).Column( "TeamId" );
mapping.Join("Role", join =>
{
join.Fetch.Join();
join.KeyColumn( "Id" );
join.Map( x => x.Role, "Description" );
} );
}
}
Which generates the following SQL:
SELECT
TOP (#p0) this_.Id as Id70_0_,
this_.RoleId as RoleId70_0_,
this_.TeamId as TeamId70_0_,
this_.UserId as UserId70_0_,
this_1_.Description as Descript2_71_0_
FROM
[UserTeam] this_
inner join
libRole this_1_
on this_.Id=this_1_.Id;
Close, but NHibernate is using the Id column on both the UserTeam table and the libRole table in the join, when it should be doing on this_.RoleId=this_1_.Id
What are we missing? We don't really want to create a "libRole" entity within the application, as all we really care about is the description values - which are user configurable, so we can't just use an enum either. Can anyone help?
Join uses the primary key of the parent table. Its not possible to change this to a foreign key. See the docs for further details on what is possible with Join.
In this situation I would recommend creating an entity for the lookup. But if you really want to take this approach you could map the property with a formula, i.e.
Map(x => x.Role).Formula("(select description from libRole where Id = RoleId)");
Note this isn't perfect because it uses RoleId so if the query has another table with a column named RoleId then the DBMS will complain when trying to execute the SQL.

Mapping self-referencing IDictionary<string, Entity> with Fluent NHibernate

I have the following entities in my domain model:
class Entity
{
public int Id { get; set; }
}
class Foo : Entity
{
public IDictionary<string, Attribute> Attributes { get; set; }
}
class Bar : Entity
{
public IDictionary<string, Attribute> Attributes { get; set; }
}
class Attribute : Entity
{
public string Key { get; set; }
public string Value { get; set; }
public IDictionary<string, Attribute> Attributes { get; set; }
}
I'd like to map these dictionaries with Fluent NHibernate. I've gotten most things to work, but first I'm having difficulties with the self-referencing Attribute.Attributes property. This is due to NHibernate making the Key a primary key of the Attribute table as well as the Id it inherits from Entity. This is how my mapping works:
ManyToManyPart<Attribute> manyToMany = mapping
.HasManyToMany<Attribute>(x => x.Attributes)
.ChildKeyColumn("AttributeId")
.ParentKeyColumn(String.Concat(entityName, "Id"))
.AsMap(x => x.Key, a => a.Column("`Key`"))
.Cascade.AllDeleteOrphan();
if (entityType == typeof(Attribute))
{
manyToMany
.Table("AttributeAttribute")
.ParentKeyColumn("ParentAttributeId");
}
If I replace the if statement with the following:
if (entityType == typeof(Attribute))
{
manyToMany
.Table("Attribute")
.ParentKeyColumn("ParentAttributeId");
}
I get the following exception:
NHibernate.FKUnmatchingColumnsException : Foreign key (FK_Attribute_Attribute [ParentAttributeId])) must have same number of columns as the referenced primary key (Attribute [ParentAttributeId, Key])
This is due to NHibernate automatically making Key the primary key alongside Id in my Attribute column. I'd like Key to not be primary key, since it shows up in all of my many to many tables;
create table FooAttribute (
FooId INT not null,
AttributeId INT not null,
[Key] NVARCHAR(255) not null
)
I'd like the foreign keys to only reference Id and not (Id, Key), since having Key as a primary key requires it to be unique, which it won't be across all of my ManyToManys.
where do you map Attribute itself (does it contain a Composite Key)?
AttributeValue may be a better name to show that it contains a value.
.AsMap(x => x.Key) is enough to say that Key should be the dictionary key
create table FooAttribute (
FooId INT not null,
AttributeValueId INT not null
)
or consider using
mapping.HasMany(x => x.Attributes)
.KeyColumn(entity + Id)
.AsMap(x => x.Key)
.Cascade.AllDeleteOrphan();
which will create
create table Attribute (
Id INT not null,
FooId INT,
BarId INT,
ParentAttributeId INT,
Key TEXT,
Value TEXT,
)