Recreate migration when making a change to the model - c#

I have spent a lot of time writing ADO.NET code recently. I have moved back to EF Core and I am now looking at the config below:
modelBuilder.Entity<PersonSport>().HasKey(sc => new { sc.PersonId, sc.SportId });
modelBuilder.Entity<Sport>()
.ToTable("Sport")
.HasDiscriminator<string>("SportType")
.HasValue<Football>("Football")
.HasValue<Running>("Running");
modelBuilder.Entity<PersonSport>()
.HasOne<Person>(sc => sc.Person)
.WithMany(s => s.PersonSport)
.HasForeignKey(sc => sc.PersonId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<PersonSport>()
.HasOne<Sport>(sc => sc.Sport)
.WithMany(s => s.PersonSport)
.HasForeignKey(sc => sc.SportId)
.OnDelete(DeleteBehavior.Cascade);
var navigation = modelBuilder.Entity<Person>().Metadata.FindNavigation(nameof(ConsoleApp1.Person.PersonSport));
navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
If I delete a Person or a Sport then the approapriate PersonSport records are deleted because DeleteBehaviour.Cascade is set for OnDelete. If I want to change this to DeleteBehaviour.Restrict, then I have to recreate the migrations and update the database. Why? The reason I ask why is because PersonSportContext.OnModelCreating runs every time I start the program.
I realise this is quite a simple question. I have been away from the ORM for a while and am much more experienced with ADO.NET.
I have spent the last hour or so reading through several similar questions on here, however I have not found the answer to my specific question.

You don't have to use Migrations. It's just a convenient way to ensure that your EF model and database schema are in sync. Some other ways to keep them in sync:
Reverse engineer your model (Scaffold-DbContext or dotnet ef dbcontext scaffold) every time you change the database schema.
Manually make changes to your EF model and database schema at the same time.

Related

EF Core - How to allow multiple cascade paths for foreign key deletion

I have a database in SQL Server 2019, with 4 tables. 1 for users, 1 for contacts, 1 for messages, and the last table that contains joined relationships between contacts and messages. (See image)
Contacts and Messages are linked to the user via a foreign key, so when the user gets deleted, it would cascade the deletion of contacts and messages.
Messages can have multiple contacts linked, but not all contacts will be linked to a message.
A message may have no contacts at all.
I now need a foreign key that would delete the relevant records in the MessageContacts table when a contact gets deleted, as well as when a message gets deleted.
(Please note that this is just a demo scenario I set up to explain my problem)
Creating this scenario in Entity Framework Core, does not work, and gives me the following error:
Introducing FOREIGN KEY constraint 'FK_MessageContacts_Messages_messageId' on table 'MessageContacts' may cause cycles or multiple cascade paths
Please see the configuration of my DbContext below:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<AppUser>(entity =>
{
entity.ToTable("AppUser");
entity.HasKey(e => e.id);
});
modelBuilder.Entity<Message>(entity =>
{
entity.ToTable("Messages");
entity.HasKey(e => e.id);
entity.HasOne<AppUser>().WithMany().HasForeignKey(e => e.userId);
});
modelBuilder.Entity<Contact>(entity =>
{
entity.ToTable("Contacts");
entity.HasKey(e => e.id);
entity.HasOne<AppUser>().WithMany().HasForeignKey(e => e.userId);
});
modelBuilder.Entity<MessageContact>(entity =>
{
entity.ToTable("MessageContacts");
entity.HasKey(e =>new {e.messageId,e.contactId });
//Removing one of the below prevents my error, but then cascading deletions don't work
entity.HasOne<Message>().WithMany().HasForeignKey(e => e.messageId);
entity.HasOne<Contact>().WithMany().HasForeignKey(e => e.contactId);
});
}
Removing the FK from MessageContacts for either Contacts or Messages prevents the error, but then the delete cascading does not work correctly. IE. Removing the FK on MessageContacts to Messages means that deleting a message, will not delete related records in MessageContacts leaving me with orphans.
Note: I have implicitly set the .OnDelete(DeleteBehavior.Cascade) option and it did not solve my problem either.
So I guess my question is, how do I accomplish this without the ability to define multiple cascade paths?
I would like to avoid doing anything manually in the database, so have everything done via EF Core migrations (if possible).
Maybe the entire model above is flawed, and someone has a better solution?
Thanks in advance.
The issue is with Microsoft's SQL Server and there's no way to get around it, unless you're willing to forget about cascading deletions via foreign keys, and instead, record the Id of the record you want to delete, then use it to manually remove all related records. That way, you aren't left with a bunch of orphaned records.
This is a really terrible way to go about it. The worst part is that SQL Server is a paid-for RDBMS, but it doesn't allow you to do something, that you can do using a free RDBMS like MySql or MariaDB.
So my solution?
Goodbye Sql Server, hello MySql.
This solution is only as practical as a developer or administrator's ability to switch from one RDBMS to another. This may not be possible at all in certain production scenarios.
I wish I could provide a better answer, but I don't think there is one.

Bulk update add to many-to-many with EF Extended?

I have a situation where I need to add a new item to a property for a group of objects that has a many-to-many relationship. Is there any way to do this in bulk using EntityFramework.Extended? Something like...
Ctx.Foos
.Where(f => fooIds.Contains(f.FooId))
.Update(f => f.Bars.Add(bar)) // <-- what would go here???
Obviously, the Update() part is not correct. For the time being, I've worked around it by retrieving the set and looping through. Just wondering if there is a better way.
The short answer is: NO
The Update() method allows only to update properties directly related to the entity. Since you want to update a many or many relations (not a property), this library does not allow to do it.
Disclaimer: I'm the owner of the project Entity Framework Extensions
The best & fastest way if you need performance is by using the Entity Framework Extensions library with the BulkSaveChanges method.
Ctx.Foos
.Where(f => fooIds.Contains(f.FooId))
.ToList()
.ForEach(f => f.Bars.Add(bar))
Ctx.BulkSaveChanges();

How to disable cascade delete for many-to-many relationships between some tables in Entity Framework 6?

My question is a bit similar to this, although I use EF6.
The problem is that I have two entities which are connected through a mapping table - and have a many-to-many relationship like this for example:
modelBuilder.Entity<Team>()
.HasMany(t => t.Members)
.WithMany()
.Map(c =>
{
c.ToTable("TeamMemberMapping");
});
So in this example one team can have multiple members and one guy can be a part of multiple teams.
The problem with this is when I delete a guy, all his team mappings will be deleted, because Entity Framework uses cascade delete by default as a delete action.
I'd like to turn this off - so the DB shouldn't allow to delete a guy, if he is a part of some team.
I know that I can remove the many-to-many cascade delete convention globally with this:
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
But this is too harsh for me. I would like to do this only for this table.
My other idea is (since I use code-first migrations) removing the convention just temporarily, so I can generate an update step which would drop all the foreign keys:
DropForeignKey("dbo.TeamMemberMapping", "Employee_Id", "dbo.Employee");
DropForeignKey("dbo.TeamMemberMapping", "Team_Id", "dbo.Team");
DropForeignKey("dbo.SomeOtherMapping", "Some_Id", "dbo.SomeTable");
DropForeignKey("dbo.SomeOtherMapping", "Other_Id", "dbo.OtherTable");
AddForeignKey("dbo.TeamMemberMapping", "Employee_Id", "dbo.Employee", "Id");
AddForeignKey("dbo.TeamMemberMapping", "Team_Id", "dbo.Team", "Id");
AddForeignKey("dbo.SomeOtherMapping", "Some_Id", "dbo.SomeTable", "Id");
AddForeignKey("dbo.SomeOtherMapping", "Other_Id", "dbo.OtherTable", "Id");
Now I can edit this migration to skip all the other mappings (like "SomeOtherMapping") and only deal with "TeamMemberMapping".
Then I would revert my temporary change (e.g. removing the convention)
Is there any drawbacks of this solution? Because it seems to be a workaround for me.
Is there any other solution?

Copy an Entity Framework graph leads to unwanted duplicates

I’m trying to copy/clone entity graph with EF6.1 and getting duplicate entities.
Below is a piece of my model which consist of a Template that I want to modify, copy and assign to different users, something like Save As function.
Here is my entities model:
What I’m doing is:
var newTemplate = ctx.Templates
.Where(t => t.TemplateId == SelectedTemplate.TemplateId)
.Include(t => t.Properties.Select(p => p.PropertyCollections))
.Include(t => t.Properties.Select(p => p.Values))
.AsNoTracking()
.First();
newTemplate.TemplateName = newTemplateName;
ctx.Templates.Add(newTemplate);
ctx.SaveChanges();
And what I get is shown below where “Template1” is the source and “Template2” is the copy in which every ‘PropertyCollection’ has a duplicated entry for each ‘Property’.
Result after copy:
I understood that with AsNoTracking there is no identity mapping which is the reason behind this but I can’t find even a custom solution.
I didn't really test your code, but I think your Entities might really get messed up when doing it that way. Maybe this attempt would work for you. It's for EF4 but might work.
You are adding the whole graph, so EF is inserting everything. You are using AsNoTracking to "trick" EF instead of its original purpose.
I would suggest you to write a few lines of code to actually implement your business requirement, which is create a new Template based on another one.
So, get the template (without the AsNoTracking), and create a new template initializing the properties based on the original template values. Then add the new template to the context. EF will insert the new template and reference the existing dependent entities.
This is also a safer way to implement this, as in the future you might require to set some properties with different values in the new template.

Fluent NHibernate Many-to-many mapping with auto-generated pk instead of composite key

I'm working on a RoleProvider in .NET, using Fluent NHibernate to map tables in an Oracle 9.2 database.
The problem is that the many-to-many table connecting users and roles uses a primary key generated from a sequence, as opposed to a composite key. I can't really change this, because I'm writing it to be implemented in a larger existing system.
Here is my UserMap:
public UserMap()
{
this.Table("USR");
HasMany(x => x.Memberships).Cascade.All()
.Table("MEMBERSHIP").Inverse().LazyLoad();
HasManyToMany(x => x.Roles)
.Table("USR_ROLE")
.Cascade.SaveUpdate()
.ParentKeyColumn("USR_ID")
.ChildKeyColumn("ROLE_ID")
.Not.LazyLoad();
}
And my RoleMap:
public RoleMap()
{
this.Table("ROLE");
Map(x => x.Description).Column("ROLE_NAME");
Map(x => x.Comment).Column("ROLE_COMMENT");
HasManyToMany(x => x.Users)
.Table("USR_ROLE")
.ParentKeyColumn("ROLE_ID")
.ChildKeyColumn("USR_ID")
.Inverse();
}
Yet, this is giving me the error:
Type 'FluentNHibernate.Cfg.FluentConfigurationException' in assembly 'FluentNHibernate, Version=1.0.0.593, Culture=neutral, PublicKeyToken=8aa435e3cb308880' is not marked as serializable.
Is there a simple fix to allow this HasMayToMany to use my PersistentObjectMap extension? I'm thinking I may have to add a convention for this many-to-many relationship, but I don't know where to start with that, since I've just started using NHibernate and Fluent NHibernate only recently.
I've been working on this problem for a while and I can't seem to find a solution.
Any help would be much appreciated. Thanks.
EDIT: I think I've found a possible solution here: http://marekblotny.blogspot.com/2009/02/fluent-nhbernate-and-collections.html
I'll try the above method of creating an entity and a class map for the linking table and post my findings.
EDIT 2: I created a linking entity as mentioned in the above blog post and downloaded the newest binaries (1.0.0.623).
This helped me discover that the issue was with setting lazy load and trying to add roles to the user object in a completely new session.
I modified the code to move OpenSession to the BeginRequest of an HttpModule as described here. After doing this, I changed my data access code from wrapping the open session in a using statement, which closes the session when it is finished, to getting the current session and wrapping only the transaction in a using statement.
This seems to have resolved the bulk of my issue, but I am now getting an error that says "Could not insert collection" into the USR_ROLE table. And I'm wondering if the above code should work with a UserRoleMap described as:
public UserRoleMap()
{
this.Table("USR_ROLE");
/* maps audit fields id, created date/user, updated date/user */
this.PersistentObjectMap("USR_ROLE");
/* Link these tables */
References(x => x.Role).Column("ROLE_ID");
References(x => x.User).Column("USR_ID");
}
Hibernate's documentation for many-to-many relationship suggests creating an object to maintain a one-to-many/many-to-one, as in an ERD. I'm sure this would be much easier with conventional naming standards, but I have to stick with certain abbreviations and odd (and not always properly-implemented) conventions.
To fix this, I created an Entity, Mapping, and Repository for UserRole. And, instead of HasManyToMany mapping in the User and Role Entities, I have a HasMany mapping. It's a little weird, because I now have:
IList<UserRole> UserRoles {get; protected set;}
and IList<Role> Roles { get{ return UserRoles.Select(u => u.Role).ToList(); } }
This works, however, I'm not 100% sure why this works and the HasManyToMany doesn't.

Categories

Resources