1+ // Copyright 2017 the original author or authors.
2+ //
3+ // Licensed under the Apache License, Version 2.0 (the "License");
4+ // you may not use this file except in compliance with the License.
5+ // You may obtain a copy of the License at
6+ //
7+ // https://www.apache.org/licenses/LICENSE-2.0
8+ //
9+ // Unless required by applicable law or agreed to in writing, software
10+ // distributed under the License is distributed on an "AS IS" BASIS,
11+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ // See the License for the specific language governing permissions and
13+ // limitations under the License.
14+
15+ using Microsoft . Extensions . Logging ;
16+ using Steeltoe . Management . Endpoint ;
17+ using System ;
18+ using System . Collections . Generic ;
19+ using System . Data . Common ;
20+ using System . Linq ;
21+ using System . Reflection ;
22+
23+ namespace Steeltoe . Management . EndpointBase . DbMigrations
24+ {
25+ public class DbMigrationsEndpoint : AbstractEndpoint < Dictionary < string , DbMigrationsDescriptor > >
26+ {
27+ /// <summary>
28+ /// Hacky class to allow mocking migration methods in unit tests
29+ /// </summary>
30+ public class DbMigrationsEndpointHelper
31+ {
32+ internal virtual Assembly ScanRootAssembly => Assembly . GetEntryAssembly ( ) ;
33+
34+ internal virtual IEnumerable < string > GetPendingMigrations ( object context ) => GetMigrationsReflectively ( context , _getPendingMigrations ) ;
35+
36+ internal virtual IEnumerable < string > GetAppliedMigrations ( object context ) => GetMigrationsReflectively ( context , _getAppliedMigrations ) ;
37+
38+ internal virtual IEnumerable < string > GetMigrations ( object context ) => GetMigrationsReflectively ( context , _getMigrations ) ;
39+
40+ private IEnumerable < string > GetMigrationsReflectively ( object dbContext , MethodInfo method )
41+ {
42+ var dbFacade = _getDatabase . Invoke ( dbContext , null ) ;
43+ return ( IEnumerable < string > ) method . Invoke ( null , new [ ] { dbFacade } ) ;
44+ }
45+ }
46+
47+ internal static Type _dbContextType ;
48+ internal static MethodInfo _getDatabase ;
49+ internal static MethodInfo _getPendingMigrations ;
50+ internal static MethodInfo _getAppliedMigrations ;
51+ internal static MethodInfo _getMigrations ;
52+
53+ private readonly IServiceProvider _container ;
54+ private readonly DbMigrationsEndpointHelper _endpointHelper ;
55+ private readonly ILogger < DbMigrationsEndpoint > _logger ;
56+
57+ static DbMigrationsEndpoint ( )
58+ {
59+ _dbContextType = Type . GetType ( "Microsoft.EntityFrameworkCore.DbContext, Microsoft.EntityFrameworkCore" ) ;
60+ _getDatabase = _dbContextType . GetProperty ( "Database" , BindingFlags . Public | BindingFlags . Instance ) . GetMethod ;
61+ var extensions = Type . GetType ( "Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions,Microsoft.EntityFrameworkCore.Relational" ) ;
62+ _getPendingMigrations = extensions . GetMethod ( "GetPendingMigrations" , BindingFlags . Static | BindingFlags . Public ) ;
63+ _getAppliedMigrations = extensions . GetMethod ( "GetAppliedMigrations" , BindingFlags . Static | BindingFlags . Public ) ;
64+ _getMigrations = extensions . GetMethod ( "GetMigrations" , BindingFlags . Static | BindingFlags . Public ) ;
65+ }
66+
67+ public DbMigrationsEndpoint (
68+ IDbMigrationsOptions options ,
69+ IServiceProvider container ,
70+ ILogger < DbMigrationsEndpoint > logger = null )
71+ : this ( options , container , new DbMigrationsEndpointHelper ( ) , logger )
72+ {
73+ }
74+
75+ public DbMigrationsEndpoint (
76+ IDbMigrationsOptions options ,
77+ IServiceProvider container ,
78+ DbMigrationsEndpointHelper endpointHelper ,
79+ ILogger < DbMigrationsEndpoint > logger = null )
80+ : base ( options )
81+ {
82+ _container = container ;
83+ _endpointHelper = endpointHelper ;
84+ _logger = logger ;
85+ }
86+
87+ public override Dictionary < string , DbMigrationsDescriptor > Invoke ( ) => DoInvoke ( ) ;
88+
89+ private Dictionary < string , DbMigrationsDescriptor > DoInvoke ( )
90+ {
91+ var result = new Dictionary < string , DbMigrationsDescriptor > ( ) ;
92+ var knownEfContexts = _endpointHelper . ScanRootAssembly
93+ . GetReferencedAssemblies ( )
94+ . Select ( Assembly . Load )
95+ . SelectMany ( x => x . DefinedTypes )
96+ . Union ( _endpointHelper . ScanRootAssembly . DefinedTypes )
97+ . Where ( type => ! type . IsAbstract && type . AsType ( ) != _dbContextType && _dbContextType . GetTypeInfo ( ) . IsAssignableFrom ( type . AsType ( ) ) )
98+ . Select ( typeInfo => typeInfo . AsType ( ) )
99+ . ToList ( ) ;
100+ foreach ( var contextType in knownEfContexts )
101+ {
102+ var dbContext = _container . GetService ( contextType ) ;
103+ if ( dbContext == null )
104+ {
105+ continue ;
106+ }
107+
108+ var descriptor = new DbMigrationsDescriptor ( ) ;
109+ var contextName = dbContext . GetType ( ) . Name ;
110+ result . Add ( contextName , descriptor ) ;
111+ try
112+ {
113+ descriptor . PendingMigrations = _endpointHelper . GetPendingMigrations ( dbContext ) . ToList ( ) ;
114+ descriptor . AppliedMigrations = _endpointHelper . GetAppliedMigrations ( dbContext ) . ToList ( ) ;
115+ }
116+ catch ( DbException e ) when ( e . Message . Contains ( "exist" ) )
117+ {
118+ // todo: maybe improve detection logic when database is new. hard to do generically across all providers
119+ descriptor . PendingMigrations = _endpointHelper . GetMigrations ( dbContext ) . ToList ( ) ;
120+ }
121+ }
122+
123+ return result ;
124+ }
125+ }
126+ }
0 commit comments