在自有C#低代码开发平台上有建模的功能,业务模型是用XML描述的当先系统的数据结构和页面元素。
对于简单的字典形业务,进行模型配置后既可自动实现数据库表的创建和前端HTML页面的定义,十分方便。
对于复杂的业务,也可以通过此模型定义数据库结构,但是在视图层需要进行接口的二次开发,以此实现复杂的功能。
这里不对低代码架构进行描述,因为平时有写文档的需求,需要对XML模型进行整理,生成整个系统的E-R图。
数据库的结构可以通过PD逆向工程,而文档中需要的ER图则缺少相应的工具。
进行了一番查找,觉得Python 下的 erdantic 是一个方案,几经测试终于成功画出了EP图
先上图:

参考的站点为
https://erdantic.drivendata.org/stable/
https://pygraphviz.github.io/documentation/stable/install.html
安装相关库过程
1.下载 graphviz

2.在默认目录下安装 graphviz

3.安装pygraphviz
注意这里就用到graphviz默认的安装路径了
python -m pip install --global-option=build_ext --global-option="-IC:\Program Files\Graphviz\include" --global-option="-LC:\Program Files\Graphviz\lib" pygraphviz
4.安装 erdantic
pip install erdantic
直接安装的版本会有中文乱码,需要安装开发版
pip install git+https://github.com/drivendataorg/erdantic.git#egg=erdantic
5.修改开发版中的配置,增加中文字体
要修改的文件为erd.py

修改成微软雅黑字体

开发过程
1.解析现有模型
先对模型进行一些处理,将业务模型中相对独立的类型都放到前面 (Python对类引用时先后关系会有要求)
分析类型间的依赖关系,这里的原理是拓扑排序。
用到了一个库,Topological Sorting in C# - CodeProject

核心代码为
public static class TopologicalSort
{
private static Func<T, IEnumerable<T>> RemapDependencies<T, TKey>(IEnumerable<T> source, Func<T, IEnumerable<TKey>> getDependencies, Func<T, TKey> getKey)
{
var map = source.ToDictionary(getKey);
return item =>
{
var dependencies = getDependencies(item);
return dependencies != null
? dependencies.Select(key => map[key])
: null;
};
}
public static IList<T> Sort<T, TKey>(IEnumerable<T> source, Func<T, IEnumerable<TKey>> getDependencies, Func<T, TKey> getKey, bool ignoreCycles = false)
{
ICollection<T> source2 = (source as ICollection<T>) ?? source.ToArray();
return Sort<T>(source2, RemapDependencies(source2, getDependencies, getKey), null, ignoreCycles);
}
public static IList<T> Sort<T, TKey>(IEnumerable<T> source, Func<T, IEnumerable<T>> getDependencies, Func<T, TKey> getKey, bool ignoreCycles = false)
{
return Sort<T>(source, getDependencies, new GenericEqualityComparer<T, TKey>(getKey), ignoreCycles);
}
public static IList<T> Sort<T>(IEnumerable<T> source, Func<T, IEnumerable<T>> getDependencies, IEqualityComparer<T> comparer = null, bool ignoreCycles = false)
{
var sorted = new List<T>();
var visited = new Dictionary<T, bool>(comparer);
foreach (var item in source)
{
Visit(item, getDependencies, sorted, visited, ignoreCycles);
}
return sorted;
}
public static IList<T> SortWithSimpleTypes<T>(IEnumerable<T> source, Func<T, IEnumerable<T>> getDependencies, IEnumerable<T> simpleTypes, IEqualityComparer<T> comparer = null, bool ignoreCycles = true)
{
var sorted = new List<T>();
sorted.AddRange(simpleTypes);
var visited = new Dictionary<T, bool>(comparer);
foreach (var item in source)
{
Visit(item, getDependencies, sorted, visited, ignoreCycles);
}
return sorted;
}
public static void Visit<T>(T item, Func<T, IEnumerable<T>> getDependencies, List<T> sorted, Dictionary<T, bool> visited, bool ignoreCycles)
{
bool inProcess;
var alreadyVisited = visited.TryGetValue(item, out inProcess);
if (alreadyVisited)
{
if (inProcess && !ignoreCycles)
{
throw new ArgumentException("Cyclic dependency found.");
}
}
else
{
visited[item] = true;
var dependencies = getDependencies(item);
if (dependencies != null)
{
foreach (var dependency in dependencies)
{
Visit(dependency, getDependencies, sorted, visited, ignoreCycles);
}
}
visited[item] = false;
if (!sorted.Contains(item))
{
sorted.Add(item);
}
}
}
public static IList<ICollection<T>> Group<T, TKey>(IEnumerable<T> source, Func<T, IEnumerable<TKey>> getDependencies, Func<T, TKey> getKey, bool ignoreCycles = true)
{
ICollection<T> source2 = (source as ICollection<T>) ?? source.ToArray();
return Group<T>(source2, RemapDependencies(source2, getDependencies, getKey), null, ignoreCycles);
}
public static IList<ICollection<T>> Group<T, TKey>(IEnumerable<T> source, Func<T, IEnumerable<T>> getDependencies, Func<T, TKey> getKey, bool ignoreCycles = true)
{
return Group<T>(source, getDependencies, new GenericEqualityComparer<T, TKey>(getKey), ignoreCycles);
}
public static IList<ICollection<T>> Group<T>(IEnumerable<T> source, Func<T, IEnumerable<T>> getDependencies, IEqualityComparer<T> comparer = null, bool ignoreCycles = true)
{
var sorted = new List<ICollection<T>>();
var visited = new Dictionary<T, int>(comparer);
foreach (var item in source)
{
Visit(item, getDependencies, sorted, visited, ignoreCycles);
}
return sorted;
}
public static int Visit<T>(T item, Func<T, IEnumerable<T>> getDependencies, List<ICollection<T>> sorted, Dictionary<T, int> visited, bool ignoreCycles)
{
const int inProcess = -1;
int level;
var alreadyVisited = visited.TryGetValue(item, out level);
if (alreadyVisited)
{
if (level == inProcess && ignoreCycles)
{
throw new ArgumentException("Cyclic dependency found.");
}
}
else
{
visited[item] = (level = inProcess);
var dependencies = getDependencies(item);
if (dependencies != null)
{
foreach (var dependency in dependencies)
{
var depLevel = Visit(dependency, getDependencies, sorted, visited, ignoreCycles);
level = Math.Max(level, depLevel);
}
}
visited[item] = ++level;
while (sorted.Count <= level)
{
sorted.Add(new Collection<T>());
}
sorted[level].Add(item);
}
return level;
}
}
2.在C#中将类型都生成Python类代码,就是一个字符串替换,如下代码做过删减仅为示例。
static void GeneratePythonContent(IList<_DemClass> cls, _DemModel model)
{
string memberT = " ♣MemberName♣: ♣MemberType♣";
string dataTypeT = @"
class ♣ClassNameEn♣(BaseModel):
♣MemberNames♣
";
StringBuilder sb = new StringBuilder();
foreach (var classItem in cls)
{
List<string> memberResult = new List<string>();
var commonMembers = classItem.GetCommonMembers();
foreach (var commonMember in commonMembers)
{
memberResult.Add(memberT.Replace("♣MemberName♣", name).Replace("♣MemberType♣", "int"));
}
var dependencyMembers = classItem.Members.Except(commonMembers);
foreach (var member in dependencyMembers)
{
if (member.IsMultiInstance)
{
memberResult.Add(memberT.Replace("♣MemberName♣", member.DisplayName).Replace("♣MemberType♣", "List[" + member.DataTypeName + "]"));
}
else
{
memberResult.Add(memberT.Replace("♣MemberName♣", member.DisplayName).Replace("♣MemberType♣", member.DataTypeName));
}
}
string dataTypeResult = dataTypeT.Replace("♣ClassNameEn♣", classItem.Name).Replace("♣MemberNames♣", string.Join("\n", memberResult));
sb.AppendLine(dataTypeResult);
}
string cc = sb.ToString();
}
最终生成的Python类格式为:

3.组织Python类到一个py文件,并通过erdantic 生成图
#!/usr/bin/python3
# -*- coding:utf-8 -*-
import erdantic as erd
from erdantic.examples.pydantic import Party
from datetime import datetime
from typing import List
from pydantic import BaseModel
class ChannelType(BaseModel):
主键: int
name: str
'''
其他的类型略...
'''
class VideoManagement(BaseModel):
主键: int
名称: str
提交时间: datetime
编号: str
备注: str
文件: List[str]
ProjectNode: str
提交人: dps_User
## 指定一个基础类
VideoManagement.update_forward_refs(**locals())
diagram = erd.create(VideoManagement)
diagram.draw("diagram.png")
网友评论