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?
Related
I have the following 3 tables in a legacy database.
CREATE TABLE Colour(
ColourCode char(3) NOT NULL,
CONSTRAINT PK_Colour PRIMARY KEY CLUSTERED
(
ColourCode ASC
)
CREATE TABLE ColourGroup(
ColourGroup varchar(6) NOT NULL,
CONSTRAINT PK_ColourGroup PRIMARY KEY CLUSTERED
(
ColourGroup ASC
)
CREATE TABLE Colour_ColourGroup(
ColourCode char(3) NOT NULL,
ColourGroup varchar(6) NOT NULL,
CONSTRAINT PK_Colour_ColourGroup PRIMARY KEY CLUSTERED
(
ColourCode ASC,
ColourGroup ASC
)
ALTER TABLE Colour_ColourGroup WITH CHECK ADD CONSTRAINT FK_Colour_ColourGroup_Colour FOREIGN KEY(ColourCode)
REFERENCES Colour (ColourCode)
ALTER TABLE Colour_ColourGroup WITH CHECK ADD CONSTRAINT FK_Colour_ColourGroup_ColourGroup FOREIGN KEY(ColourGroup)
REFERENCES ColourGroup (ColourGroup)
and the following EF classes to represent them in code
public class Colour
{
public string ColourId { get; set; }
public ICollection<ColourGroup> ColourGroups { get; set; }
}
public class ColourGroup
{
public string ColourGroupId { get; set; }
public ICollection<Colour> Colours { get; set; }
}
public class ColourColourGroup
{
public string ColourId { get; set; }
public string ColourGroupId { get; set; }
public Colour Colour { get; set; }
public ColourGroup ColourGroup { get; set; }
}
I'm using Fluent API to link ColourColourGroup to the Colour_ColourGroup table and to specify some DB column names.
builder.Entity<Colour>(entity =>
{
entity.Property(e => e.ColourId).HasColumnType("char(3)")
.HasColumnName("ColourCode");
entity.Property(e => e.FinishId).HasMaxLength(3);
entity.Property(e => e.Description).HasMaxLength(30);
entity.HasKey(e => e.ColourId);
});
builder.Entity<ColourGroup>(entity =>
{
entity.Property(e => e.ColourGroupId).HasMaxLength(6)
.HasColumnName("ColourGroup");
entity.HasKey(e => e.ColourGroupId);
});
builder.Entity<ColourColourGroup>(entity =>
{
entity.ToTable("Colour_ColourGroup");
entity.Property(e => e.ColourGroupId).HasMaxLength(6)
.HasColumnName("ColourGroup");
entity.Property(e => e.ColourId).HasMaxLength(3)
.HasColumnName("ColourCode");
entity.HasKey(e => new { e.ColourId, e.ColourGroupId });
});
But I don't know how to link the 3 tables together. If it was just a one-to-many relationship between Colour and ColourGroup, I think you'd use InverseProperty (or whatever the Fluent equivalent is) on ColourGroup.Colours.
Any query that accesses these entities fail with the above error, for example:
var group = _context.ColourGroups.Where(g => g.ColourGroupId == "ALLPC").FirstOrDefault();
How do I tell EF that the Colour.ColourGroups and ColourGroup.Colours properties are "mapped" by the corresponding properties in ColourColourGroup?
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.
How do you specify a custom column name with a HasOne relationship in EF7?
Consider the following sample classes:
public class House
{
public int Id { get; set; }
public int BedroomCount { get; set; }
public Address Address { get; set; }
}
public class Address
{
public int Id { get; set; }
public string StreetName { get; set; }
public string StreetNumber { get; set; }
}
And this fluent configuration:
modelBuilder.Entity<House>()
.HasOne(x => x.Address)
.WithOne()
.OnDelete(DeleteBehavior.Cascade);
Which leads to this DB configuration:
CREATE TABLE [House] (
[Id] int NOT NULL IDENTITY,
[AddressId] int,
[BedroomCount] int NOT NULL,
CONSTRAINT [PK_House] PRIMARY KEY ([Id]),
CONSTRAINT [FK_House_Address_AddressId] FOREIGN KEY ([AddressId]) REFERENCES [Address] ([Id]) ON DELETE CASCADE);
CREATE TABLE [Address] (
[Id] int NOT NULL IDENTITY,
[StreetName] nvarchar(max),
[StreetNumber] nvarchar(max),
CONSTRAINT [PK_Address] PRIMARY KEY ([Id]));
How do I specify a column name other than "AddressId" on the House table? I cannot find a method similar to HasColumnName like there is on non-navigation properties.
I'm using Entity Framework 7 RC1-Final.
You can use Data Annotations to configure the foreign key of your relationship.
public int AddressID { get; set; }
[ForeignKey("AddressID")]
public Address Address { get; set; }
This requires a property that will be used as the foreign key in your relationship. Also, note that it is recommended that you have a explicit foreign key for your relationships other than a shadow foreign key. This will prevent you to have a lot of problems when inserting/updating since you don't need to set the entire navigation property Address to save a House entity. See the problem here
Not tested but perhaps this could work (can't find a way to install EF7 right now)
modelBuilder.Entity<House>()
.HasOne(x => x.Address)
.WithOne()
.OnDelete(DeleteBehavior.Cascade);
.HasForeignKey(x => x.AddressID);
You can check here for more examples: Foreign Key Relationships EF7
You can get property first from modelBuilder.Entity<House>().Metadata.GetProperties() list and then set its name
property.Relational().ColumnName = "YouCustomId"
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, ... ]);
I have 2 classes: Client and Survey.
Each Client can have many surveys - but only one default survey.
I have defined the classes like this:
public class Client
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public string ClientName { get; set; }
public Nullable<int> DefaultSurveyID { get; set; }
[ForeignKey("DefaultSurveyID")]
public virtual Survey DefaultSurvey { get; set; }
public virtual ICollection<Survey> Surveys { get; set; }
}
public class Survey
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public string SurveyName { get; set; }
[Required]
public int ClientID { get; set; }
[ForeignKey("ClientID")]
public virtual Client Client { get; set; }
}
This creates the Client table as I expect:
[dbo].[Clients]
(
[ID] [int] IDENTITY(1,1) NOT NULL,
[ClientName] [nvarchar](max) NULL,
[DefaultSurveyID] [int] NULL
)
But the Survey table has an extra foreign key:
[dbo].[Surveys]
(
[ID] [int] IDENTITY(1,1) NOT NULL,
[SurveyName] [nvarchar](max) NULL,
[ClientID] [int] NOT NULL,
[Client_ID] [int] NULL
)
Why is Code First generating this relationship and how to I tell it not to?
The problem is that when you have multiple relationships between two entities, EF Code First isn't able to find out which navigation properties match up, unless, you tell it how, here is the code:
public class Client
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public string ClientName { get; set; }
/****Change Nullable<int> by int?, looks better****/
public int? DefaultSurveyID { get; set; }
/****You need to add this attribute****/
[InverseProperty("ID")]
[ForeignKey("DefaultSurveyID")]
public virtual Survey DefaultSurvey { get; set; }
public virtual ICollection<Survey> Surveys { get; set; }
}
With your previous version, EF was creating that extra relationship because it didn't know that the DefaultSurvey property was referencing the ID of the Survey class, but you can let it know that, adding the attribute InverseProperty whose parameter is the name of the property in Survey you need DefaultSurvey to match with.
You can do it using code-first, but not being a code first expert I cheated :-)
1) I created the tables and relationships (as above without the extra Client_ID) in the database using SMS
2) I used Reverse Engineer Code First to create the required classes and mappings
3) I dropped the database and recreated it using context.Database.Create()
Original table defs:
CREATE TABLE [dbo].[Client](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](50) NULL,
[DefaultSurveyId] [int] NULL,
CONSTRAINT [PK_dbo.Client] PRIMARY KEY NONCLUSTERED
(
[Id] ASC
)
)
CREATE TABLE [dbo].[Survey](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](50) NULL,
[ClientId] [int] NULL,
CONSTRAINT [PK_dbo.Survey] PRIMARY KEY NONCLUSTERED
(
[Id] ASC
)
)
Plus foreign keys
ALTER TABLE [dbo].[Survey] WITH CHECK
ADD CONSTRAINT [FK_dbo.Survey_dbo.Client_ClientId] FOREIGN KEY([ClientId])
REFERENCES [dbo].[Client] ([Id])
ALTER TABLE [dbo].[Client] WITH CHECK
ADD CONSTRAINT [FK_dbo.Client_dbo.Survey_DefaultSurveyId]
FOREIGN KEY([DefaultSurveyId]) REFERENCES [dbo].[Survey] ([Id])
Code generated by reverse engineering:
public partial class Client
{
public Client()
{
this.Surveys = new List<Survey>();
}
public int Id { get; set; }
public string Name { get; set; }
public int? DefaultSurveyId { get; set; }
public virtual Survey DefaultSurvey { get; set; }
public virtual ICollection<Survey> Surveys { get; set; }
}
public partial class Survey
{
public Survey()
{
this.Clients = new List<Client>();
}
public int Id { get; set; }
public string Name { get; set; }
public int? ClientId { get; set; }
public virtual ICollection<Client> Clients { get; set; }
public virtual Client Client { get; set; }
}
public class ClientMap : EntityTypeConfiguration<Client>
{
#region Constructors and Destructors
public ClientMap()
{
// Primary Key
this.HasKey(t => t.Id);
// Properties
this.Property(t => t.Name).HasMaxLength(50);
// Table & Column Mappings
this.ToTable("Client");
this.Property(t => t.Id).HasColumnName("Id");
this.Property(t => t.Name).HasColumnName("Name");
this.Property(t => t.DefaultSurveyId).HasColumnName("DefaultSurveyId");
// Relationships
this.HasOptional(t => t.DefaultSurvey)
.WithMany(t => t.Clients).HasForeignKey(d => d.DefaultSurveyId);
}
#endregion
}
public class SurveyMap : EntityTypeConfiguration<Survey>
{
#region Constructors and Destructors
public SurveyMap()
{
// Primary Key
this.HasKey(t => t.Id);
// Properties
this.Property(t => t.Name).HasMaxLength(50);
// Table & Column Mappings
this.ToTable("Survey");
this.Property(t => t.Id).HasColumnName("Id");
this.Property(t => t.Name).HasColumnName("Name");
this.Property(t => t.ClientId).HasColumnName("ClientId");
// Relationships
this.HasOptional(t => t.Client)
.WithMany(t => t.Surveys).HasForeignKey(d => d.ClientId);
}
#endregion
}
Entity Framework does exactly what it's told to do. What you've told it is that there is both a one-to-many and a one-to-one relationship between Clients and Surveys. It generated both FKs in the Survey table in order to map both of the relationships that you've requested. It has no idea that you're trying to connect the two relationships together, nor do I think does it have the ability to deal with that.
As an alternative you might want to consider adding a IsDefaultSurvey field on the Survey object so that you can query for the default survey through the Surveys collection that you have on the Client object. You could even go one step further and put it in as a NotMapped property on the Client object so that you could still use Client.DefaultSurvey to get the correct survey, and not have to change any of your other code, as follows:
[NotMapped]
public Survey DefaultSurvey
{
get { return this.Surveys.First(s => s.IsDefaultSurvey); }
}
Please notice that adding the code below you will fix the issue.
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext() : base("DefaultConnection")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Client>()
.HasOptional(x => x.DefaultSurvey)
.WithMany(x => x.Surveys);
.HasForeignKey(p => p.DefaultSurveyID);
{
}