# Licensed under the MIT License (MIT)
import os
import sys
import xml.etree.ElementTree as ET
INDENT = "\t"
NEWLINE = "\n"
OUTPUT_PATH = "SakilaDapper/Model/ModelsGenerated.cs"
OUTPUT_MAP_PATH = "SakilaDapper/Model/MappersGenerated.cs"
NAMESPACE = "SakilaDapper.Model"
def resolve_type(db_type: str) -> str:
if db_type.startswith("tinyint(1)"):
return "bool"
if any(db_type.startswith(t) for t in ["tinyint", "smallint", "mediumint", "int"]):
if "unsigned" in db_type:
return "uint"
return "int"
if db_type.startswith("bigint"):
if "unsigned" in db_type:
return "ulong"
return "long"
if any(db_type.startswith(t) for t in ["timestamp", "datetime"]):
return "DateTime"
if any(db_type.startswith(t) for t in ["varchar", "char", "tinytext", "mediumtext", "text", "longtext", "set", "enum", "geometry"]):
return "string"
if db_type.startswith("year"):
return "int"
if db_type.startswith("date"):
return "DateOnly"
if db_type.startswith("decimal"):
return "decimal"
if db_type.startswith("blob"):
return "byte[]"
raise ValueError(f"Unknown type: {db_type}")
def resolve_type_from_column(column: ET.Element) -> str:
db_type = column.get("DatabaseType").lower()
nullable = column.get("Nullable", "false").lower() == "true"
resolved_type = resolve_type(db_type)
if nullable and resolved_type not in ["string", "byte[]"]:
return resolved_type + "?"
return resolved_type
def get_class_lines(table: ET.Element):
class_name = table.get("ClassName")
table_comment = table.get("Comment")
yield ""
if table_comment:
yield "/// "
yield f"/// {table_comment}"
yield "/// "
yield f"public partial class {class_name}"
yield "{"
for column in table.findall("Column"):
field_name = column.get("FieldName")
comment = column.get("Comment")
if comment:
yield f"{INDENT}/// "
yield f"{INDENT}/// {comment}"
yield f"{INDENT}/// "
csharp_type = resolve_type_from_column(column)
yield f"{INDENT}public {csharp_type} {field_name} {{ get; set; }}"
yield "}"
def get_mappers_lines(table: ET.Element) -> str:
class_name = table.get("ClassName")
items = [
f'("{col.get("Name")}", nameof({class_name}.{col.get("FieldName")}))'
for col in table.findall("Column")
]
if not items:
return
yield f"{INDENT*2}Register<{class_name}>("
yield from (f"{INDENT*3}{line}," for line in items[:-1])
yield f"{INDENT*3}{items[-1]}"
yield f"{INDENT*2});"
xml_data = sys.stdin.read()
tree = ET.ElementTree(ET.fromstring(xml_data))
root = tree.getroot()
output_file = os.path.abspath(OUTPUT_PATH)
mappers = []
classes = []
for db in root.findall("Database"):
for schema in db.findall("Schema"):
for table in schema.findall("Table"):
classes.extend(get_class_lines(table))
mappers.extend(get_mappers_lines(table))
content_lines = [
"using System;",
"using System.ComponentModel.DataAnnotations;",
"using System.ComponentModel.DataAnnotations.Schema;",
"",
f"namespace {NAMESPACE};",
]
content_lines.extend(classes)
content = NEWLINE.join(content_lines)
with open(output_file, "w", encoding="utf-8", newline=NEWLINE) as f:
f.write(content)
mappers_lines = [
"using Dapper;",
"namespace SakilaDapper.Model;",
"",
"public class MapperGenerated",
"{",
INDENT + "private static void Register(params (string Column, string Property)[] map)",
INDENT + "{",
INDENT*2 + "var type = typeof(T);",
INDENT*2 + "var propMap = map.ToDictionary(",
INDENT*3 + "kvp => kvp.Column,",
INDENT*3 + "kvp => type.GetProperty(kvp.Property)",
INDENT*4 + "?? throw new InvalidOperationException($\"Property {kvp.Property} not found on {type}\")",
INDENT*2 + ");",
"",
INDENT*2 + "SqlMapper.SetTypeMap(typeof(T),",
INDENT*3 + "new CustomPropertyTypeMap(typeof(T),",
INDENT*3 + "(_, columnName) => propMap[columnName]",
INDENT*2 + "));",
INDENT + "}",
"",
INDENT + "public static void Register()",
INDENT + "{"
]
mappers_lines.extend(mappers)
mappers_lines.append(INDENT + "}")
mappers_lines.append("}")
output_map_file = os.path.abspath(OUTPUT_MAP_PATH)
map_content = NEWLINE.join(mappers_lines)
with open(output_map_file, "w", encoding="utf-8", newline=NEWLINE) as f:
f.write(map_content)