There was a need to create .proto definition files from the definitions of a reverse engineered database first project. The approach taken was that of using System.Emit to generate the type definitions and feed those to protobuf-net and use its ability to generate the .proto files.
There are only three classes needed:
- ContextFinder
- ClassGenerator
- Program
The ContextFinder is pretty straight forward. It uses reflection to get all the generic parameters of DbSet<> properties within a DbContext. Then, ClassGenerator is used to copy the properties of the Types we harvested into a new type with the addition of adding ProtoContract and ProtoMember appropriately. Then, the Program class just loads the assembly from the file specified and runs the previously two mentioned classes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ClassGenerator | |
{ | |
private readonly ModuleBuilder _moduleBuilder; | |
public ClassGenerator() | |
{ | |
var an = new AssemblyName("DynamicProtoAssembly"); | |
AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(an,AssemblyBuilderAccess.Run); | |
_moduleBuilder = assemblyBuilder.DefineDynamicModule("DynamicProtoModule"); | |
} | |
public Type CreateType(Type typeToCopy) | |
{ | |
TypeBuilder tb = _moduleBuilder.DefineType(typeToCopy.Name + "Proto", | |
TypeAttributes.Public | | |
TypeAttributes.Class | | |
TypeAttributes.AutoClass | | |
TypeAttributes.AnsiClass | | |
TypeAttributes.BeforeFieldInit | | |
TypeAttributes.AutoLayout, | |
null); | |
ConstructorBuilder constructor = tb.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName); | |
var ci = typeof(ProtoContractAttribute).GetConstructor(new Type[0]); | |
var builder = new CustomAttributeBuilder(ci,new object[0]); | |
tb.SetCustomAttribute(builder); | |
var propertiesToCopy = typeToCopy.GetProperties(); | |
for (int i = 0; i < propertiesToCopy.Length; i++) | |
{ | |
var propertyInfo = propertiesToCopy[i]; | |
CreateProperty(tb,propertyInfo.Name,propertyInfo.PropertyType,i); | |
} | |
return tb.CreateType(); | |
} | |
private static void CreateProperty(TypeBuilder tb, string propertyName, Type propertyType, int i) | |
{ | |
FieldBuilder fieldBuilder = tb.DefineField("_" + propertyName, propertyType, FieldAttributes.Private); | |
PropertyBuilder propertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null); | |
MethodBuilder getPropMthdBldr = tb.DefineMethod("get_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes); | |
ILGenerator getIl = getPropMthdBldr.GetILGenerator(); | |
getIl.Emit(OpCodes.Ldarg_0); | |
getIl.Emit(OpCodes.Ldfld, fieldBuilder); | |
getIl.Emit(OpCodes.Ret); | |
MethodBuilder setPropMthdBldr = | |
tb.DefineMethod("set_" + propertyName, | |
MethodAttributes.Public | | |
MethodAttributes.SpecialName | | |
MethodAttributes.HideBySig, | |
null, new[] { propertyType }); | |
ILGenerator setIl = setPropMthdBldr.GetILGenerator(); | |
Label modifyProperty = setIl.DefineLabel(); | |
Label exitSet = setIl.DefineLabel(); | |
setIl.MarkLabel(modifyProperty); | |
setIl.Emit(OpCodes.Ldarg_0); | |
setIl.Emit(OpCodes.Ldarg_1); | |
setIl.Emit(OpCodes.Stfld, fieldBuilder); | |
setIl.Emit(OpCodes.Nop); | |
setIl.MarkLabel(exitSet); | |
setIl.Emit(OpCodes.Ret); | |
propertyBuilder.SetGetMethod(getPropMthdBldr); | |
propertyBuilder.SetSetMethod(setPropMthdBldr); | |
var ci = typeof(ProtoMemberAttribute).GetConstructor(new [] { typeof(int) }); | |
var builder = new CustomAttributeBuilder(ci, new object[] { i + 1 }); | |
propertyBuilder.SetCustomAttribute(builder); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ContextFinder | |
{ | |
public IEnumerable<Type> GetAllTypesInContextDbSets(Assembly assembly) | |
{ | |
return GetContextTypes(assembly) | |
.Select(x => GetDataSetTypes(x)) | |
.SelectMany(x => x) | |
.Select(x => x.GetGenericArguments()[0]); | |
} | |
private IEnumerable<Type> GetContextTypes(Assembly assembly) | |
{ | |
return assembly.GetTypes() | |
.Where(myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf(typeof(DbContext))); | |
} | |
private IEnumerable<Type> GetDataSetTypes(Type context) | |
{ | |
return context.GetProperties() | |
.Select(x => x.PropertyType) | |
.Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(DbSet<>)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
if (args.Length < 1) | |
{ | |
Console.WriteLine("The first argument should be a path to the assembly"); | |
return; | |
} | |
Assembly assembly = Assembly.LoadFile(args[0]); | |
ContextFinder finder = new ContextFinder(); | |
var types = finder.GetAllTypesInContextDbSets(assembly); | |
ClassGenerator generator = new ClassGenerator(); | |
var protoTypes = types.Select(x => generator.CreateType(x)); | |
foreach (var protoType in protoTypes) | |
{ | |
Console.WriteLine(GenerateProtoFile(protoType)); | |
} | |
} | |
private static string GenerateProtoFile(Type protoType) | |
{ | |
MethodInfo methodInfo = typeof(Serializer).GetMethod(nameof(Serializer.GetProto),new [] {typeof(ProtoSyntax)}); | |
MethodInfo genericMethod = methodInfo.MakeGenericMethod(protoType); | |
return (string) genericMethod.Invoke(null, new object[] { ProtoSyntax.Proto3 }); | |
} | |
} |