1+ using System . Reflection ;
12using EfCore . EncryptedProperties . Configuration ;
23using Microsoft . EntityFrameworkCore ;
4+ using Microsoft . EntityFrameworkCore . Metadata ;
35using Microsoft . EntityFrameworkCore . Metadata . Builders ;
46using Microsoft . EntityFrameworkCore . Metadata . Conventions ;
57using Microsoft . EntityFrameworkCore . Metadata . Conventions . Infrastructure ;
6- using Microsoft . EntityFrameworkCore . Metadata ;
78
89namespace EfCore . EncryptedProperties . Infrastructure ;
910
1011internal sealed class EncryptedPropertiesConventionSetPlugin : IConventionSetPlugin
1112{
1213 public ConventionSet ModifyConventions ( ConventionSet conventionSet )
1314 {
14- conventionSet . ModelFinalizingConventions . Add ( new EncryptedPropertiesStorageConvention ( ) ) ;
15+ conventionSet . ModelFinalizingConventions . Insert ( 0 , new EncryptedPropertiesStorageConvention ( ) ) ;
1516 return conventionSet ;
1617 }
1718}
@@ -22,6 +23,9 @@ public void ProcessModelFinalizing(
2223 IConventionModelBuilder modelBuilder ,
2324 IConventionContext < IConventionModelBuilder > context )
2425 {
26+ ApplyEncryptedAttributes ( modelBuilder ) ;
27+ RemoveUnusedEncryptedValueEntityTypes ( modelBuilder ) ;
28+
2529 foreach ( var entityType in modelBuilder . Metadata . GetEntityTypes ( ) . ToList ( ) )
2630 {
2731 foreach ( var property in entityType . GetProperties ( ) . ToList ( ) )
@@ -38,6 +42,89 @@ public void ProcessModelFinalizing(
3842 }
3943 }
4044
45+ private static void ApplyEncryptedAttributes ( IConventionModelBuilder modelBuilder )
46+ {
47+ foreach ( var entityType in modelBuilder . Metadata . GetEntityTypes ( ) . ToList ( ) )
48+ {
49+ if ( entityType . ClrType is null || IsEncryptedValueType ( entityType . ClrType ) )
50+ continue ;
51+
52+ foreach ( var propertyInfo in entityType . ClrType . GetProperties (
53+ BindingFlags . Instance | BindingFlags . Public | BindingFlags . NonPublic ) )
54+ {
55+ var encryptedAttribute = GetEncryptedAttribute ( propertyInfo ) ;
56+ if ( encryptedAttribute is null )
57+ continue ;
58+
59+ var property = entityType . FindProperty ( propertyInfo . Name )
60+ ?? ConvertAnnotatedNavigationToProperty ( entityType , propertyInfo ) ;
61+
62+ ApplyEncryptedAttribute ( property , encryptedAttribute ) ;
63+ }
64+ }
65+ }
66+
67+ private static IConventionProperty ConvertAnnotatedNavigationToProperty (
68+ IConventionEntityType entityType ,
69+ PropertyInfo propertyInfo )
70+ {
71+ var navigation = entityType . FindNavigation ( propertyInfo . Name ) ;
72+ if ( navigation is not null )
73+ {
74+ navigation . ForeignKey . DeclaringEntityType . Builder . HasNoRelationship (
75+ navigation . ForeignKey ,
76+ fromDataAnnotation : true ) ;
77+ }
78+
79+ var propertyBuilder = entityType . Builder . Property (
80+ propertyInfo . PropertyType ,
81+ propertyInfo . Name ,
82+ setTypeConfigurationSource : false ,
83+ fromDataAnnotation : true ) ;
84+
85+ return propertyBuilder ? . Metadata
86+ ?? throw new InvalidOperationException (
87+ $ "Unable to configure encrypted property '{ entityType . ClrType . FullName } .{ propertyInfo . Name } ' from data annotations.") ;
88+ }
89+
90+ private static void RemoveUnusedEncryptedValueEntityTypes ( IConventionModelBuilder modelBuilder )
91+ {
92+ foreach ( var entityType in modelBuilder . Metadata . GetEntityTypes ( ) . ToList ( ) )
93+ {
94+ if ( ! IsEncryptedValueType ( entityType . ClrType ) )
95+ continue ;
96+
97+ if ( entityType . GetForeignKeys ( ) . Any ( ) || entityType . GetReferencingForeignKeys ( ) . Any ( ) )
98+ continue ;
99+
100+ modelBuilder . HasNoEntityType ( entityType , fromDataAnnotation : true ) ;
101+ }
102+ }
103+
104+ private static void ApplyEncryptedAttribute ( IConventionProperty property , EncryptedAttribute encryptedAttribute )
105+ {
106+ if ( property . FindAnnotation ( EncryptedPropertyAnnotations . IsEncrypted ) is not null )
107+ return ;
108+
109+ var keyPurpose = string . IsNullOrWhiteSpace ( encryptedAttribute . KeyPurpose )
110+ ? "default"
111+ : encryptedAttribute . KeyPurpose ;
112+
113+ property . Builder . HasAnnotation ( EncryptedPropertyAnnotations . IsEncrypted , true , fromDataAnnotation : true ) ;
114+ property . Builder . HasAnnotation ( EncryptedPropertyAnnotations . KeyPurpose , keyPurpose , fromDataAnnotation : true ) ;
115+ property . Builder . HasAnnotation (
116+ EncryptedPropertyAnnotations . Materialization ,
117+ GetMaterializationMode ( property . ClrType ) ,
118+ fromDataAnnotation : true ) ;
119+ }
120+
121+ private static EncryptedAttribute ? GetEncryptedAttribute ( PropertyInfo propertyInfo )
122+ {
123+ return propertyInfo . GetCustomAttributes ( typeof ( EncryptedAttribute ) , inherit : true )
124+ . OfType < EncryptedAttribute > ( )
125+ . FirstOrDefault ( ) ;
126+ }
127+
41128 private static void ConfigureCiphertextStorage ( IConventionEntityType entityType , IConventionProperty plaintextProperty )
42129 {
43130 if ( plaintextProperty . IsKey ( )
@@ -115,8 +202,13 @@ private static string GetCiphertextPropertyName(IConventionEntityType entityType
115202
116203 private static string GetMaterializationMode ( Type clrType )
117204 {
118- return clrType . IsGenericType && clrType . GetGenericTypeDefinition ( ) == typeof ( EncryptedValue < > )
205+ return IsEncryptedValueType ( clrType )
119206 ? "Lazy"
120207 : "DecryptOnRead" ;
121208 }
209+
210+ private static bool IsEncryptedValueType ( Type type )
211+ {
212+ return type . IsGenericType && type . GetGenericTypeDefinition ( ) == typeof ( EncryptedValue < > ) ;
213+ }
122214}
0 commit comments