美文网首页
采用 erdantic 将系统模型生成E-R图

采用 erdantic 将系统模型生成E-R图

作者: 百里有声 | 来源:发表于2023-01-04 17:13 被阅读0次

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

image.png
参考的站点为
https://erdantic.drivendata.org/stable/
https://pygraphviz.github.io/documentation/stable/install.html

安装相关库过程

1.下载 graphviz

image.png

2.在默认目录下安装 graphviz

image.png

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


image.png

修改成微软雅黑字体


image.png

开发过程

1.解析现有模型

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

image.png
核心代码为
 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类格式为:


image.png

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")

最终效果为首图

相关文章

  • 采用 erdantic 将系统模型生成E-R图

    在自有C#低代码开发平台上有建模的功能,业务模型是用XML描述的当先系统的数据结构和页面元素。对于简单的字典形业务...

  • 实用数据库设计

    数据库设计的三个阶段 概念设计 将需求分析数据抽象成局部E-R模型,再将局部E-R模型集成为全局E-R模型E-R图...

  • 智慧商超管理系统

    智慧商超管理系统 绘制E-R图 将E-R图转化为数据表 根据数据表,在SQL server 2008中建表

  • 2018-11-04

    智慧商超 绘制E-R图 将E-R图转化为数据表 sql代码

  • 2018-11-04

    智慧商超 绘制E-R图 将E-R图转化为数据表 sql代码

  • 笔记二、数据分析与建模

    一、建立数据的过程 二、实体(Entity)-联系(Relation)模型(E-R图) 1、术语对照 2、E-R模...

  • 智能商超管理系统

    智能商超管理系统 E-R图 数据库图

  • 2018-12-06

    2.4系统数据库设计 1.系统框图 2.根据系统框图画出E-R图 3.根据E-R图提取出数据表的各个字段

  • 智能超市管理系统

    E-R图 ER模型转换成数据表,并指出主键

  • 2018-12-07

    《2.4 系统数据库设计》 1.描述抽象数据库表的过程,绘制流程图 1.标识实体2.绘制E-R图3.E-R模型转换...

网友评论

      本文标题:采用 erdantic 将系统模型生成E-R图

      本文链接:https://www.haomeiwen.com/subject/zdorcdtx.html