Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make MigrationsModelDiffer:ToReferentialAction protected rather than private #15828

Open
cmorgan091 opened this issue May 28, 2019 · 5 comments
Open

Comments

@cmorgan091
Copy link

@cmorgan091 cmorgan091 commented May 28, 2019

The MigrationsModelDiffer is great for being able to override certain areas to change functionality as needed. However, a relatively minor thing, when overriding the Diff method for ForeignKeys, there is a call to a private method ToReferentialAction.

I can copy this in to my superseding class of course, but it would be nice if ToReferentialAction was protected rather than private

Further technical details

EF Core version: 2.2.4
Database Provider: Microsoft.EntityFrameworkCore.SqlServer
Operating system: Windows 10
IDE: Visual Studio 2017 15.9.9

@bricelam

This comment has been minimized.

Copy link
Contributor

@bricelam bricelam commented May 28, 2019

Out of curiosity, what functionality are you changing/adding?

@cmorgan091

This comment has been minimized.

Copy link
Author

@cmorgan091 cmorgan091 commented May 28, 2019

@bricelam - Good question and I apologise for the overly long answer that it entails

TL;DR
I am implementing DDD by having multiple Contexts that link back to a common Context (containing for example Tenant, User, Security etc...), since the module contexts have foreign keys back to the common contexts they are picked up in migrations and try to create the tables. This is essentially a hack of a solution to https://github.com/aspnet/EntityFrameworkCore/issues/2725. By implementing a custom MigrationsModelDiffer I can look to ignore the schema that represents the common context, since I have to override IEnumerable<MigrationOperation> Diff(IEnumerable<IForeignKey> source, IEnumerable<IForeignKey> target, DiffContext diffContext) I also need to re-implement private static ReferentialAction ToReferentialAction(DeleteBehavior deleteBehavior) since it is private and not protect (as I say its nothing major, but would just be a couple of lines of code that could be omitted)

DbContexts
So we have two DbContexts, CommonDbContext which is the central core context just showing holding Tenant details, and ModuleDbContext which describes things relating to a module and there would be multiple modules each with their own DbContext.

public class ModuleDbContext : DbContext
{
	public DbSet<SessionDto> Session { get; set; }
	public DbSet<CountryDto> Country { get; set; }
}

public class CommonDbContext : DbContext
{
	public DbSet<TenantDto> Tenant { get; set; }
}

Dto's
Basic Dto's with some nav properties between them, key element being foreign keys from ModuleDbContext DbSet's to those in CommonDbContext

[Table("Tenant", Schema = "Common")]
public class TenantDto
{
	[Key]
	public int TenantId { get; set; }

	public string Name { get; set; }
}

[Table("Session", Schema = "Module")]
public class SessionDto
{
	[Key]
	public Guid SessionId { get; set; }

	public string Name { get; set; }

	public int TenantId { get; set; }


	public virtual TenantDto Tenant { get; set; }
	
	public virtual ICollection<CountryDto> Countries { get; set; }
}

[Table("Country", Schema = "Module")]
public class CountryDto
{
	[Key]
	public Guid CountryId { get; set; }

	public string Name { get; set; }

	public Guid SessionId { get; set; }

	public virtual SessionDto Session { get; set; }
}

So from this point I can generate a migration for CommonDbContext and it will just have the one table in (since no navs from Common to Module).

However when I go to generate an initial migration for ModuleDbContext, it is also going to try to create the Tenant table in the Common schema because of the foreign key from Session to Tenant.

SO to get over this I implement a custom migration differ a la:

public class IgnoreSchemaMigrationsModelDiffer : MigrationsModelDiffer
{
	private string SchemaToIgnore = "Common";

	public IgnoreSchemaMigrationsModelDiffer(IRelationalTypeMappingSource typeMappingSource, IMigrationsAnnotationProvider migrationsAnnotations, IChangeDetector changeDetector, StateManagerDependencies stateManagerDependencies, CommandBatchPreparerDependencies commandBatchPreparerDependencies)
		: base(typeMappingSource, migrationsAnnotations, changeDetector, stateManagerDependencies, commandBatchPreparerDependencies)
	{ }

	public override bool HasDifferences(IModel source, IModel target)
		=> Diff(source, target, new IgnoreSchemaDiffContext(source, target, SchemaToIgnore)).Any();

	public override IReadOnlyList<MigrationOperation> GetDifferences(IModel source, IModel target)
	{
		var diffContext = new IgnoreSchemaDiffContext(source, target, SchemaToIgnore);
		return Sort(Diff(source, target, diffContext), diffContext);
	}

	private static ReferentialAction ToReferentialAction(DeleteBehavior deleteBehavior)
		=> deleteBehavior == DeleteBehavior.Cascade
			? ReferentialAction.Cascade
			: deleteBehavior == DeleteBehavior.SetNull
				? ReferentialAction.SetNull
				: ReferentialAction.Restrict;


	protected override IEnumerable<MigrationOperation> Diff(
		IEnumerable<IForeignKey> source,
		IEnumerable<IForeignKey> target,
		DiffContext diffContext)
		=> DiffCollection(
			source,
			target,
			diffContext,
			Diff,
			Add,
			Remove,
			(s, t, c) =>
			{
				if (s.Relational().Name == t.Relational().Name)
				{
					if (s.Properties.Select(p => p.Relational().ColumnName).SequenceEqual(
						t.Properties.Select(p => c.FindSource(p)?.Relational().ColumnName)))
					{
						if (c.FindSourceTable(s.PrincipalEntityType).Schema == SchemaToIgnore ||
						c.FindSourceTable(s.PrincipalEntityType)
							== c.FindSource(c.FindTargetTable(t.PrincipalEntityType)))
						{
							if (t.PrincipalKey.Properties.Select(p => c.FindSource(p)?.Relational().ColumnName).First() == null ||
							s.PrincipalKey.Properties.Select(p => p.Relational().ColumnName).SequenceEqual(
								t.PrincipalKey.Properties.Select(p => c.FindSource(p)?.Relational().ColumnName)))
							{
								if (ToReferentialAction(s.DeleteBehavior) == ToReferentialAction(t.DeleteBehavior))
								{
									if (!HasDifferences(MigrationsAnnotations.For(s), MigrationsAnnotations.For(t)))
									{
										return true;
									}
								}
							}
						}
					}
				}

				return false;
			});

	protected class IgnoreSchemaDiffContext : DiffContext
	{
		public IgnoreSchemaDiffContext(IModel source, IModel target, string schemaToIgnore)
			: base(source, target)
		{
			SchemaToIgnore = schemaToIgnore;
		}

		private readonly string SchemaToIgnore;

		public override IEnumerable<TableMapping> GetSourceTables()
		{
			var tables = base.GetSourceTables();

			return tables.Where(x => x.Schema != SchemaToIgnore);
		}

		public override IEnumerable<TableMapping> GetTargetTables()
		{
			var tables = base.GetTargetTables();

			return tables.Where(x => x.Schema != SchemaToIgnore);
		}
	}
}

I found the easiest way to get around the issue was to ignore the tables from the common schema. Whilst this worked, it had a slight side effect that each subsequent migration opted to remove and recreate each of the foreign keys between schemas (as it detects a difference). Hence the override of the Diff for the ForeignKeys.

Hope that makes sense.

So basically I am trying to control the way in which Migrations are generated to exclude a specific schema. Hopefully issue 2725 will long term provide a way to do this, but I needed something sooner so this was my solution (which currently appears to be working well).

Comments welcome

@bricelam

This comment has been minimized.

Copy link
Contributor

@bricelam bricelam commented May 29, 2019

Makes sense. 😄 Feel free to send a PR to make things virtual and split things out into new methods. (Similar to PR #14430). But issue #2725 is also still planned for 3.0, so it may make sense just to wait.

@Marusyk

This comment has been minimized.

Copy link
Contributor

@Marusyk Marusyk commented Jul 5, 2019

Hi @cmorgan091, do you work on it?

@joaorsfonseca

This comment has been minimized.

Copy link

@joaorsfonseca joaorsfonseca commented Jul 24, 2019

@cmorgan091 where should i call/execute/override IgnoreSchemaMigrationsModelDiffer class?

I have one DbContext inherited from another one.

public class Project1Context : ProjectContext
{
	public Project1Context(DbContextOptions<Project1Context> options)
		: base(options)
	{
	}

	public DbSet<NewEntity> NewEntity { get; set; }
}
	
public class ProjectContext : DbContext
{
	public ProjectContext(DbContextOptions<ProjectContext> options) 
		: base(options)
	{
	}

	protected ProjectContext(DbContextOptions options)
		: base(options)
	{
	}

	public DbSet<Entity1> Entity1 { get; set; }
}

When i do add-migrations on Project1Context, the tables in inherited are also being created.
NewEntity has a FK to Entity1 (different contexts)

Thanks.

@ajcvickers ajcvickers removed the help wanted label Aug 5, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
6 participants
You can’t perform that action at this time.