Entity Framework making wrong property relations - c#

I have a database with a lot of tables created using code-first.
3 of the tables are
public class Machine
{
[Key]
public long ID { get; set; }
...
public virtual MachineTypeApprovalHist MachineTypeApproval { get; set; }
}
public class MachineTypeApprovalHist
{
[Key]
public long ID { get; internal set; }
...
}
public class MachineTypeApproval
{
[Key]
public long ID { get; set; }
...
}
The weird thing is that EF creates a foreign key from Machine to MachineTypeApproval (not MachineTypeApprovalHist as it should!).
I found out after long time of debugging by looking in the database table directly to see the relations between the tables. The error I got was
The UPDATE statement conflicted with the FOREIGN KEY constraint "FK_dbo.Machines_dbo.MachineTypeApprovals_MachineTypeApproval_ID". The conflict occurred in database "ATPData", table "dbo.MachineTypeApprovals", column 'ID'.
The statement has been terminated.
The error comes because it tries to use a ID from MachineTypeApprovalHist that is not in MachineTypeApprovals.
I have tried to rename the property MachineTypeApproval to TypeApproval and making a new migration, but it only renamed the table column and index.
I cannot recreate the database from scratch since I will lose my data, so what can I do to fix this?

public class DatabaseContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder
.Entity<Machine>()
.HasOptional(_ => _.MachineTypeApproval)
.WithMany();
}
}
will generate these SQL statements:
ALTER TABLE [dbo].[Machines] WITH CHECK ADD CONSTRAINT [FK_dbo.Machines_dbo.MachineTypeApprovalHists_MachineTypeApproval_ID] FOREIGN KEY([MachineTypeApproval_ID])
REFERENCES [dbo].[MachineTypeApprovalHists] ([ID])
GO
ALTER TABLE [dbo].[Machines] CHECK CONSTRAINT [FK_dbo.Machines_dbo.MachineTypeApprovalHists_MachineTypeApproval_ID]
GO

I found a way to fix this, though I feel it should be easier.
The way I did it was to add another property to my Machine class to replace the first one.
public class Machine
{
[Key]
public long ID { get; set; }
...
public virtual MachineTypeApprovalHist MachineTypeApproval { get; set; }
//new property
public virtual MachineTypeApprovalHist TypeApproval {get; set;}
}
then I made a new migration which creates a new columns with foreign key to the correct table.
Now, I couldn't just remove my original property and update the database since I would lose data. So first i removed the original property MachineTypeApproval from Machine class, then adding a new migration, adding to that migrations Up method the following on the first line before any other call
Sql("update [dbo].Machines set TypeApproval_ID = MachineTypeApproval_ID ");
In this way the first migration correctly creates the new property, the second migration copies data from the old column to the new, and the second migration removes the old column, and my model and db is now correct.
I just hate that I need two migrations to do this. Also it looks like EF uses the property names BEFORE the property type to determine which table to use, which seems totally crazy to me

Related

Entity Framework throwing conflict with reference constraint while deleting

I have the following entity declared
public class TransactionEvent
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public virtual List<TransactionSignInError> SignInErrors { get; set; }
}
And the context
public class TransactionAuditsDbContext : DbContext
{
public virtual DbSet<TransactionEvent> TransactionEvents { get; set; }
}
Now when I try to delete a transaction event, I want the relevant SignInError rows to be deleted as well. I realize I can do this by using cascade on delete if I had set that up in the context, too late for that now.
How can I delete successfully a transaction? I'm getting this error.
The DELETE statement conflicted with the REFERENCE constraint "FK_dbo.TransactionSignInErrors_dbo.TransactionEvents_TransactionEvent_Id". The conflict occurred in database "db", table "dbo.TransactionSignInErrors", column 'TransactionEvent_Id'
I have tried clearing the SignInErrors list before deleting, that did get rid of the above error but left NULLs in the TransactionSignInErrors table.
What you want, is "Cascade on Delete": if a TransactionEvent is deleted, then you also want that all its TransactionSignInErrors are deleted.
This works on a one-to-many relation, this does not work on a many-to-many-relation.
If you have a one-to-many relation between TransactionEvents and TransactionSignInErrors, and you followed the entity framework conventions, you will have classes like
public class TransactionEvent
{
public Guid Id { get; set; }
...
// Every TransactionEvent has zero or more TransactionSignInErrors (one-to-many)
public virtual ICollection<TransactionSignInError> SignInErrors { get; set; }
}
public class TransactionSignInError
{
public Guid Id { get; set; }
...
// Every TransactionSignInError belongs to exactly oneTransactionEvent, using foreign key
public Guid TransactionEventId {get; set;}
public virtual TransactionEvent TransactionEvent { get; set; }
}
public class TransactionAuditsDbContext : DbContext
{
public DbSet<TransactionEvent> TransactionEvents { get; set; }
public DbSet<TransactionSignInError> TransactionSignInErrors {get; set;}
}
This is all that entity framework needs to know to detect the tables, the columns in the tables and the one-to-many relation between these two tables.
In entity framework the non virtual properties represent the columns in the table, the virtual properties represent the relations between the tables (one-to-many, many-to-many, ...)
The foreign key TransactionEventId is a real column, hence it is non-virtual. TransactionEvent is not a real column, it only refers to the relation, hence it is declared virtual.
If you stick to the conventions, there is no need for attributes, nor fluent API. Only if you want non-default identifiers for tables, columns, column types or non-default behaviour for table relations, you might need attributes or fluent API.
Default behaviour is cascade on delete: if you delete a TransactionEvent, all its TransactioinSigninErrors are also deleted.
I'm not sure whether your problems arise because you have a GUID as primary key, instead of an int. If you want, you can inform entity framework about your one-to-many relation and cascade on delete in OnModelCreating:
protected override void OnModelCreating (DbModelBuilder modelBuilder)
{
// Every TransactionEvent has zero or more TransactionSignInErrors
// Every TransactionSignInError belongs to exactly one TransactionEvent
// using foreign key TransactionEventId.
// Also: cascade on delete:
modelBuilder.Entity<TransactionEvent>()
.HasMany(transactionEvent => transactionEvent.TransactionSignInErrors)
.WithRequired(transactionSignInError => transactionSignInError.TransactionEvent)
.HasForeignKey(transactionSignInError => transactionSignInError.TransactionEventId)
.WillCascadeOnDelete();
So three major changes to your code:
The DbSets in the DbContext are non-virtual
Added the table TransactionSignInErrors to your DbContext
If that is not enough for CascadeOnDelete (check this first!) add fluent API.
Small change: Use ICollection instead of IList.
Rationale: if you fetch a TransactionEvent with its TransactionSignInErrors, does TransactionEvent.SignInErrors[4] have a defined meaning? Wouldn't it be better if people have no access to methods that they don't know what they really mean?
If you want to use a cascade delete you have to include the children:
var removingRow=_context.Set<TransactionEvent>()
.Include(x=> x.SignInErrors )
.Where(x => x.Id ==id)
.FirstOrDefault();
if(removingRow != null)
{
_context.Remove(removingRow);
_context.SaveChanges();
}
Your post has the tag of entity-framework. I'm not sure how things work with Entity Framework 6 or previous versions, but with Entity Framework Core you can solve your issue like -
var tEvent = dbCtx.TransactionEvents
.Include(p=> p.SignInErrors)
.FirstOrDefault(p => p.Id == id);
foreach (var error in eventx.SignInErrors)
{
dbCtx.SignInErrors.Remove(error);
}
dbCtx.TransactionEvents.Remove(tEvent);
dbCtx.SaveChanges();

Entity Framework creates non-existing column in query

I face the problem that EF creates a column in the query that does not exist in the Oracle database table.
The simplified model which is created by EF looks like this (I use DB first approach):
public partial class USER
{
public int ID { get; set; }
public string NAME { get; set; }
public int PROCESS_ID { get; set; }
public virtual PROCESS PROCESS { get; set; }
}
public partial class PROCESS
{
public PROCESS()
{
this.USER = new HashSet<User>();
}
public int ID { get; set; }
public virtual ICollection<USER> USER { get; set; }
}
I set up the foreign key constraint in the oracle sql developer.
When I try to get the Users for a selected Process like this:
var users = context.Users.Where(u => u.PROCESS_ID == 0);
It produces following error:
ORA-00904: "Extent1"."R1": invalid ID
So i took a look on the produced SQL:
SELECT
"Extent1".ID,
"Extent1".NAME,
"Extent1".R1,
FROM DB.USER "Extent1"
WHERE "Extent1".R1 = :p__linq__0
Of course this produces an error because R1 isn't a column in the table. But I can't figure out where it comes from. It seems like EF can't map the foreign key properly thats why it's also missing in the generated SQL query?
Maybe someone has a tip for me :)
To follow up my comment, here is a link to the conventions.
The convention for a foreign key is that it must have the same data type as the principal entity's primary key property and the name must follow one of these patterns:
[navigation property name][principal primary key property name]Id
[principal class name][primary key property name]Id
[principal primary key property name]Id
Your convention [navigation property name]_ID isn't on the list.
Encountered the same error recently while working with Oracle using DevArt provider. Turned out it was caused by a column name being longer than 30 chars. OP mentioned that the model posted in his question is a simplified one so it still may be the case.

Entity Framework Core Database First - Multiple Foreign keys to one table

Background Information
I am currently working with EF Core using a database first implementation.
Current tables
Fizz
{
[Id] INT
[Category] varchar
[Value] varchar
}
Buzz
{
[Id] UniqueIdentifier
[TypeId1] INT
[TypeId2] INT
CONSTRAINT [FK_Buzz_Fizz_1] FOREIGN KEY ([TypeId1] REFERENCES [Fizz][Id])
CONSTRAINT [FK_Buzz_Fizz_2] FOREIGN KEY ([TypeId2] REFERENCES [Fizz][Id])
}
Fizz currently acts a lookup table. Doing this allows for a single data repository to be used to find different values by category.
Buzz is a table that has two different type values to be stored e.g. TypeId1 could be brand which would exist in Fizz as (id, Brands, Nestle) and TypeId2 could be a flavor which would exist in Fizz as (id, Flavors, Grape).
The Issue
I scaffold the db to create the Data Models.
When running the application the following occurrs:
InvalidOperationException: Unable to determine the relationship represented by navigation property 'Buzz.TypeId1' of type 'Fizz'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
One solution that has occurred to me is to break this lookup table (Fizz) into multiple tables that way the references could be resolved by not having duplicate types used for Foreign Keys.
This would require re-work of the logic for the current data repository to either access multiple tables or be split into multiple data repos.
Another solution would be to modify the DBContext that is generated and use DataAnnotations on the DataModel. I would like to avoid doing this as the Context and Models will be regenerated in the future and these changes will be overwritten.
Is there a way to have a datamodel generated from a table that has multiple Foreign Keys to a single table without having to modify the generated code?
For posterity:
With the database approach a scaffold of the db is done to create the context and data models.
The data models generated (using the example tables above) look something like this -
public partial class Buzz
{
public Buzz()
{ }
public Guid Id { get; set; }
public int TypeId1 { get; set; }
public int TypeId2 { get; set; }
public Fizz TypeId1Fizz { get; set; }
public Fizz TypeId2Fizz { get; set; }
}
public partial class Fizz
{
public Fizz()
{ }
public int Id { get; set; }
public string Category { get; set; }
public string Value { get; set; }
public ICollection<Buzz> TypeId1Fizz { get; set; }
public ICollection<Buzz> TypeId2Fizz { get; set; }
}
The issue is that the relationship in Buzz could not be resolved.
The solution
When using scaffold on the database all models are generated as partials to a specified folder. I created a partial for the Buzz class in another directory that lives inside of the directory created by the scaffold (be sure that the namespaces match VS likes to add the directory name to the namespace and the partials won't be matched).
public partial class Buzz
{
[NotMapped]
public Fizz TypeId1Fizz { get; set; }
[NotMapped]
public Fizz TypeId2Fizz { get; set; }
}
but Leustherin then you lose the ability to utilize .Include for Fizz! EntityFramework won't create an SQL join statement for you so you will have to make an extra trip to the DB to obtain your look up value!
To get around this, override the Get or GetAll function of your data repository and create your own join statement.
Why I chose this solution
Maintainability.
Anytime the DataModels are regenerated instead of getting a runtime error there is now a compile error reminding the dev to delete the extra properties from the generated data model.
There is no other modification of automatically generated files.
There are no major schema changes done to accommodate the change.
I will do my best to keep this updated.

EF Core - self referencing entity

I have a table that should refer to the same table. But when I try to update the database schema, it throws me this error:
When i try to update the schema, PMC throws this error:
System.Data.SqlClient.SqlException (0x80131904): Introducing FOREIGN
KEY constraint 'FK_Directory_Directory_SubOfId' on table 'Directory'
may cause cycles or multiple cascade paths. Specify ON DELETE NO
ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY
constraints.
I tried setting ON DELETE to CASCADE but nothing, still the same error. I do not know how to set NO ACTION because mapping does not offer this option.
What with this?
Entity:
public class Directory : BaseEntity
{
public int ID { get; set; }
public int? SubOfId { get; set; }
[ForeignKey("SubOfId")]
public Directory SubOf { get; set; }
public virtual ICollection<ImageDirectory> ImageDirectory { get; set; }
}
Model builder:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
...
builder.Entity<Directory>().HasOne(e => e.SubOf).WithOne().HasForeignKey<Directory>(a => a.SubOfId).OnDelete(DeleteBehavior.Cascade);
}
This exception isn't due to self-referencing. You get this when an entity can be deleted via multiple cascade paths. Based on the code you've provided, my best guess is that something going on with ImageDirectory and relationships in play there is actually the source of the issue.
Long and short, you need to investigate your object graph to see where removing one entity type might cause multiple cascades. Then, you'll need to shut off some of those cascades to proceed. There's not much more that can be said to help you, unfortunately, without being able to see all your entities and the relationships between them all.

Ef6, Many to many, Inserts and Primary keys

I have a problem with populating the right values in a junction table for a many to many relationship. In the image below I have simplified what I am trying to do. The "Table on the left" has values in the database that I want to use. The table of the right is about to receive new records. It has a navigation property to the Junction table and the same is true for the table on the left. The junction table has navigation properties to both tables on its rear and they are set to required.
When I create the new records in the table on the right, I also add to it records in the junction table. The TOL_ID is known as it is saved in the database, but the TOR_ID is about to be created, therefore unknown. When I attempt to call the SaveChanges in my context, it tries to save the junction table records first, before the TOR_IDs have been populated for the record on the right. I though that marking the navigation property as Required would make the EF understand that TOR_ID must exist before creating the junction table row. Instead it tries to insert an existing TOL_ID and 0, which gives a violation when trying to insert many table on the right records connected to the same TOL_ID.
Note: Saving the TOR_IDs first and then connecting with junction records is not an option, as the creation of the junction table records are part of a "Slowly changing dimension" type 6 flow.
This is how it really looks like in the code:
// The newRating is the new object corresponding the Table on the right
var newRating = new ModuleRating()
{
// The moduleRating.RatedDriveUnit already exists in the db
RatedDriveUnit = moduleRating.RatedDriveUnit
};
newModule.Ratings.Add(newRating);
If you follow the Code First below classes will helpful
public class TOL
{
[Key]
public int TOL_ID { get; set; }
public int Col1 { get; set; }
public ICollection<TOR> Tors { get; set; }
}
public class TOR
{
[Key]
public int TOR_ID { get; set; }
public int Col1 { get; set; }
public ICollection<TOL> Tols { get; set; }
}
public class TolTorContext : DbContext
{
public DbSet<TOL> Tols { get; set; }
public DbSet<TOR> Tors { get; set; }
}
If you follow database first approach,make FK at Join Table and try to chhange id names TOLId , TORId
I'am sorry for spending your time. After some investigation I noticed that the supposed composite unique index (TOL_ID and TOR_ID) of the junction table had been set wrong. Instead two separate unique indexes had been applied to TOL_ID and TOR_ID, which resulted a constrain violation as soon as one of those two values appeared twice.

Categories

Resources