I use in some project EF code First with TPH (in part of classes)
I have class like this:
namespace Billing.Model.Domain.Entities.BilProject
{
[Table("ProjectPriceList")]
public abstract class ProjectPriceList
{
[Key, Column("ProjectId", Order = 0)]
public int ProjectId { get; set; }
[Key, Column("PriceListId", Order = 1)]
public int PriceListId { get; set; }
[Key, Column("PriceListSourceType", Order = 2)]
public int PriceListSourceType { get; set; }
[ForeignKey("PriceListSourceType")]
public virtual BillingProjectCode PriceListSourceTypeCode { get; set; }
public int AddUsrnm { get; set; }
public DateTime AddTmstmp { get; set; }
[ForeignKey("ProjectId")]
public virtual BillingProject BillingProject { get; set; }
public virtual SnapShotPriceList SnapShotPriceList{ get; set; }
}
public class BillingProjectPriceList : ProjectPriceList
{
[ForeignKey("PriceListId")]
public virtual PriceList PriceList { get; set; }
}
public class EstimateProjectPriceList : ProjectPriceList
{
}
}
When I try to generate sql code to create a data base by
Update-Database -Script from console
I receive this SQL as result:
CREATE TABLE [dbo].[ProjectPriceList] (
[ProjectId] [int] NOT NULL,
[PriceListId] [int] NOT NULL,
[PriceListSourceType] [int] NOT NULL,
[AddUsrnm] [int] NOT NULL,
[AddTmstmp] [datetime] NOT NULL,
[Discriminator] [nvarchar](128) NOT NULL,
CONSTRAINT [PK_dbo.ProjectPriceList] PRIMARY KEY ([ProjectId], [PriceListId], [PriceListSourceType])
)
CREATE INDEX [IX_ProjectId] ON [dbo].[ProjectPriceList]([ProjectId])
CREATE INDEX [IX_PriceListId] ON [dbo].[ProjectPriceList]([PriceListId])
CREATE INDEX [IX_PriceListSourceType] ON [dbo].[ProjectPriceList]([PriceListSourceType])
ALTER TABLE [dbo].[ProjectPriceList] ADD CONSTRAINT [FK_dbo.ProjectPriceList_dbo.BillingProject_ProjectId] FOREIGN KEY ([ProjectId]) REFERENCES [dbo].[BillingProject] ([ProjectId]) ON DELETE CASCADE
ALTER TABLE [dbo].[ProjectPriceList] ADD CONSTRAINT [FK_dbo.ProjectPriceList_dbo.BillingProjectCode_PriceListSourceType] FOREIGN KEY ([PriceListSourceType]) REFERENCES [dbo].[BillingProjectCode] ([CodeId]) ON DELETE CASCADE
ALTER TABLE [dbo].[ProjectPriceList] ADD CONSTRAINT [FK_dbo.ProjectPriceList_dbo.PriceList_PriceListId] FOREIGN KEY ([PriceListId]) REFERENCES [dbo].[PriceList] ([PriceListId]) ON DELETE CASCADE
The last FK Constraint is redudant and make me trouble if i try add new EstimateProjectPriceList.
My question is
How to stop create this FK by some code Data Annotations or Fluent Api in OnModelCreating ?
Thnx for any help..
Related
Is there an easy way to have a setup like this in EF Core?
ProjectEntity
Id
Name
List<Notes>
CustomerEntity
Id
Name
List<Notes>
NotesEntity
Id
Date
Note
Every parent entity would have a one-to-many relation to same child entity. So I can not use normal behavior as
NotesEntity
Id
ParentId
Date
Note
I have some idea to have like above but also add one field that said what the parent entity is, is that the right way to do it or is there a better way? If I use this way I can't use EF Core normal behavior with one-to-many relationship? I need to make more manual work for search / add and so on?
Edit :
Entity Framework multiple parent tables I found this solution, but there I need to make a connection from my child to every parent I use, it could be alot of them.
Did also find a solution like :
BaseEntity
List<Notes>
ProjectEntity:BaseEntity
NotesEntity
Id
BaseEntityId
...
This last solution maybe is the best way to do it if I have alot of parent entities?
[EDIT 220922]
Could [Owned] type has collection of other Items? Or this feature won't work on owned entitys? I guess this behavior isn't supported?
[Owned]
public class Note
{
public int Id { get; set; }
public string Text { get; set; }
public int UserId { get; set; }
public User User { get; set; }
public ICollection<string> Tags { get; set; }
}
I got an error on ICollection-row when I try to add-migration.
Unabel to determine the relationshop represented by navigation ... of
typ 'ICollection' Either manually configure the relationship, or
ignore this property using the '[NotMapped]' attribute.....
Maybe I could have one middleentity like :
public class NoteTagsEntity
{
public int Id { get; set; }
public ICollection<string> Tags { get; set; }
}
And then :
[Owned]
public class Note
{
public int Id { get; set; }
public string Text { get; set; }
public int UserId { get; set; }
public User User { get; set; }
public int NoteTagsId { get; set; }
public NoteTagsId NoteTagsId { get; set; }
}
Edit
I solved the Note functionality with having more FK's, one that point to Id of parent and one FK Id that point to what module that use that particular note. Here I don't have parent - child relation in my entities, I need to do this connection by myself but in this way it's easy to apply more modules that use note's later.
Use Owned Entity Types, and each entity will get its own notes table.
eg
public abstract class Entity
{
public int Id { get; set; }
}
public abstract class EntityWithNotes: Entity
{
public virtual ICollection<Note> Notes { get; set; }
}
[Owned]
public class Note
{
public int Id { get; set; }
public string Text { get; set; }
}
public class Project : EntityWithNotes
{
public string Name { get; set; }
}
public class Customer : EntityWithNotes
{
public string Name { get; set; }
}
creates
CREATE TABLE [Customer_Notes] (
[Id] int NOT NULL IDENTITY,
[CustomerId] int NOT NULL,
[Text] nvarchar(max) NOT NULL,
CONSTRAINT [PK_Customer_Notes] PRIMARY KEY ([CustomerId], [Id]),
CONSTRAINT [FK_Customer_Notes_Customer_CustomerId] FOREIGN KEY ([CustomerId]) REFERENCES [Customer] ([Id]) ON DELETE CASCADE
);
CREATE TABLE [Project_Notes] (
[Id] int NOT NULL IDENTITY,
[ProjectId] int NOT NULL,
[Text] nvarchar(max) NOT NULL,
CONSTRAINT [PK_Project_Notes] PRIMARY KEY ([ProjectId], [Id]),
CONSTRAINT [FK_Project_Notes_Project_ProjectId] FOREIGN KEY ([ProjectId]) REFERENCES [Project] ([Id]) ON DELETE CASCADE
);
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
I just can't find any code that changes the foreign key. How do I tell the context that the foreign key has changed so that it updates the database? I have been attempting to get this to work for 2 months now:
These are the models:
namespace MyApp.WebApi.Models
{
public class Project
{
public int ProjectId { get; set; }
public string Description { get; set; }
public string Name { get; set; }
// Foreign Key - Project Status
public virtual ProjectStatus ProjectStatus { get; set; }
}
public class ProjectStatus
{
public int ProjectStatusId { get; set; }
public string Name { get; set; }
}
public class HerculesWebApiContext : DbContext
{
public HerculesWebApiContext() : base("name=HerculesWebApiContext") { }
public DbSet<Project> Projects { get; set; }
public DbSet<ProjectStatus> ProjectStatuses { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder) { }
}
}
This is the controller for the Web API:
namespace MyApp.WebApi.Controllers
{
public class ProjectController : ApiController
{
private HerculesWebApiContext db = new HerculesWebApiContext();
public void PutProject(int id, [FromBody]Project project)
{
if (!ModelState.IsValid) throw new HttpResponseException(HttpStatusCode.BadRequest);
if (id != project.ProjectId) throw new HttpResponseException(HttpStatusCode.BadRequest);
// I need some code here to tell EF that the FK has changed
db.Entry(project).State = EntityState.Modified;
// Try catch around this
db.SaveChanges();
}
The above code will not update the Project Status Foreign Key even if it has changed in the JSON object that is passed to the function. The object is returned in the same format that the Web API provided it but with the Project Status object updated:
[{"ProjectStatus":{"ProjectStatusId":2,"Name":"Started"},"ProjectId":3,"Description":"test description","Name":"test project"}]
I have tried:
db.Projects.Attach(db.Projects.Single(c => c.ProjectId == project.ProjectId));
((IObjectContextAdapter)db).ObjectContext
.ApplyCurrentValues("Projects", project);
I have also tried:
db.Entry(project.ProjectStatus).CurrentValues.SetValues(new ProjectStatus { ProjectStatusId = project.ProjectStatus.ProjectStatusId });
I have also tried this:
var per = new ProjectStatus { ProjectStatusId = project.ProjectStatus.ProjectStatusId }; // create the stub
db.ProjectStatuses.Attach(per);
db.Entry(project.ProjectStatus).CurrentValues.SetValues(per);
These are the table T-SQL files that are automatically created from my models:
Projects Table:
CREATE TABLE [dbo].[Projects] (
[ProjectId] INT IDENTITY (1, 1) NOT NULL,
[Description] NVARCHAR (MAX) NULL,
[Name] NVARCHAR (MAX) NULL,
[ProjectStatus_ProjectStatusId] INT NOT NULL,
CONSTRAINT [PK_dbo.Projects] PRIMARY KEY CLUSTERED ([ProjectId] ASC),
CONSTRAINT [FK_dbo.Projects_dbo.ProjectStatus_ProjectStatusId] FOREIGN KEY ([ProjectStatus_ProjectStatusId]) REFERENCES [dbo].[ProjectStatus] ([ProjectStatusId]) ON DELETE CASCADE
Project Statuses Table
CREATE TABLE [dbo].[ProjectStatus] (
[ProjectStatusId] INT IDENTITY (1, 1) NOT NULL,
[Name] NVARCHAR (MAX) NULL,
CONSTRAINT [PK_dbo.ProjectStatus] PRIMARY KEY CLUSTERED ([ProjectStatusId] ASC)
);
You need to do two things. First explicitly state your primary keys for each class.
Add the [Key] attribute to
Project class, above the line public int ProjectId { get; set; }
ProjectStatus class, above the line public int ProjectStatusId { get; set; }
Secondly add the property for your foreign key to your Project class. Add this line:
public int ProjectStatusId { get; set; }
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'm using EF 5.0 (CodeFirst) with VS 2012 and am having trouble making a query using SqlQuery. The problem happens in the mapping between the property name of the entity and the name of the column in the database.
My entity (model):
[Table("Teste")]
public class TesteEntity
{
[Column("Teste_Id")]
public int Id { get; set; }
[Required]
public bool IsAdministrator { get; set; }
[Required]
public string Name { get; set; }
}
When I run the query, I get an error.
Rotine:
List<TesteEntity> list = dataContext.Database.SqlQuery<TesteEntity>("SELECT * FROM Teste").ToList();
Error:
The data reader is incompatible with the specified '....TesteEntity'. A member of the type, 'Id', does not have a corresponding column in the data reader with the same name.
Table structure in database:
CREATE TABLE [dbo].[Teste]( [Id] [int] IDENTITY(1,1) NOT NULL,
[IsAdministrator] [bit] NOT NULL, [Name] [nvarchar](max) COLLATE
Latin1_General_CI_AS NOT NULL, CONSTRAINT [PK_dbo.Teste] PRIMARY KEY
CLUSTERED ( [Id] ASC )WITH (PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF)
ON [PRIMARY] ) ON [PRIMARY]
Obviously when I take DataAnnotation [Column ("Teste_Id")] and recreate the table, everything works, but wanted to know if you have to do this query using DataAnnotation [Column ("Teste_Id")].
Thanks
Please, write this code as follow:
[Table("Teste")]
public class TesteEntity
{
[Column("TesteId")]
[Key]
public int Id { get; set; }
[Required]
public bool IsAdministrator { get; set; }
[Required]
public string Name { get; set; }
}
I suggest you to write your code How EF CF work fine. For your code it is as follow:
public class Teste
{
public int TesteId{ get; set; }
[Required]
public bool IsAdministrator { get; set; }
[Required]
public string Name { get; set; }
}