xml地图|网站地图|网站标签 [设为首页] [加入收藏]
枚举类型和结构体,全自动迁移数据库的实现
分类:编程

python高级系列文章目录

python高级——目录

 

 

 

一、定义的方法:

写在最后的广告

ZKWeb网页框架已经在实际项目中使用了这项技术,目前来看迁移部分还是比较稳定的。
这项技术最初是为了插件商城而开发的,在下载安装插件以后不需要重新编译主程序,不需要执行任何迁移命令就能使用。
目前虽然没有实现插件商城,也减少了很多日常开发的工作。

如果你有兴趣,欢迎加入ZKWeb交流群522083886共同探讨。

字典

'''
    字典是python内置类型中唯一的映射,先看创建字典的几种方法

    1、对象创建
    2、大括号
    3、zip
'''

if __name__ == "__main__":
    # 1、利用实例化对象的方法创建
    a = dict(key1=1, key2=2, all=[1, 2, 3])
    b = dict([('key3', 3), ('key4', 4)])
    c = dict({"key5": 5, "key6": 6})

    print("a:", a)     # a: {'key1': 1, 'all': [1, 2, 3], 'key2': 2}
    print("b:", b)     # b: {'key3': 3, 'key4': 4}
    print("c:", c)     # c: {'key6': 6, 'key5': 5}

    # 2、直接使用大括号
    d = {"key7": 7, "key8": 8}
    print("d:", d)     # d: {'key8': 8, 'key7': 7}

    # 3、使用zip
    e = dict(zip(("key9", "key10", "key11"), [9, 10, 11]))
    print("e:", e)     # e: {'key11': 11, 'key10': 10, 'key9': 9}

(1)字典推导式

 

'''
    字典推导式:字典推导式的创建方法同列表推导式类似

    以下直接引用《流畅的python》中的例子
'''


if __name__ == "__main__":
    DIAL_CODES = [
        (86, 'China'),
        (91, 'India'),
        (1, 'United States'),
        (62, 'Indonesia'),
        (55, 'Brazil'),
        (92, 'Pakistan'),
        (880, 'Bangladesh'),
        (234, 'Nigeria'),
        (7, 'Russia'),
        (81, 'Japan'),
    ]

    country_code = {country: code for code, country in DIAL_CODES}
    print(country_code)   # {'Russia': 7, 'Indonesia': 62, 'Brazil': 55, 'China': 86, 'India': 91, 'Bangladesh': 880, 'Pakistan': 92, 'United States': 1, 'Nigeria': 234, 'Japan': 81}

    code_upper = {code: country.upper() for country, code in country_code.items() if code < 66}
    print(code_upper)     # {1: 'UNITED STATES', 7: 'RUSSIA', 62: 'INDONESIA', 55: 'BRAZIL'}
(2)处理不存在的键
'''
    处理找不到的键

    在实际场景中,当使用d[key]的方法查找数据的时候,如果找不到该键,python会抛出KeyError异常;
    如果是取值操作,可以使用d.get(key, default)来解决,可以给找不到的键一个默认的值
    但是如果要给更新某个不存在键对应的值的时候,就稍显麻烦了,可以使用以下方法解决:
        1、用setdefault处理dict找不到的键
        2、使用defaultdict对象
        3、__missing__方法
'''

class Foo:
    def __init__(self, name=None):
        self.name = name

    def __repr__(self):
        return str(self.name)

    def setattr(self, key, value):
        self.__setattr__(key, value)
        return self


if __name__ == "__main__":
    d1 = {}
    print(d1.get("key", "default"))   # default   使用d.get(key, default)的方法取值


    # 1、用setdefault处理dict找不到的键
    d2 = {}
    d2.setdefault("key", [x for x in "adfaf"])  # setdefault虽然是set名字,但是是取值操作,只有当键不存在时才进行赋值,并返回该值
    l = d2.setdefault("key", [])
    print(l)                                    # ['a', 'd', 'f', 'a', 'f']

    d2.setdefault("key2", []).extend([1, 2, 3]) # 返回空列表,所以可在后面直接使用方法extend
    print(d2)                                   # {'key': 'default', 'key2': [1, 2, 3]}

    # 2、使用defaultdict对象
    #  在python中,还有一些dict的变种类型,defaultdict为其中一种,位于collections中
    from collections import defaultdict

    dic = defaultdict(list)                    # 将list的构造方法作为default_factory(只有__getitem__找不到值时调用)
    dic["key"].extend([1, 2, 3])               # dic中不含有"key"键,此时default_factory会被调用,创造一个空列表,并连接[1, 2, 3]
    print(dic["key"])                # [1, 2, 3]

    dic = defaultdict(Foo)           # 将Foo的构造方法作为default_factory创建一个defaultdict
    print(dic["key"].setattr("name", "default"))                # default

    # 3、__missing__方法
    # 所有的映射类型在找不到键的时候,都会牵扯到__missing__方法;如果在__getitem__找不到键的时候,python就会自动调用它
    # 另外,__missing__方法只会被getitem调用,对get或者__contains__没有影响

    class My_dict(dict):
        def __missing__(self, key):
            print("正在调用__missing__...")

    mdict = My_dict(one=1, two=2, three=3)
    print(mdict)     # {'two': 2, 'three': 3, 'one': 1}
    mdict["key"]     # 正在调用__missing__...
(3)字典的变种
'''
    在python中虽然只有dict为映射类型,但是dict有很多变种,上面defaultdict就是,除此之外还有:

    (1)OrderedDict: 有顺序的字典
     (2) ChainMap: 可以容纳数个不同的映射对象
     (3) Counter:  给键准备一个整数计数器,每次更新键的时候会增加该计数器
    (4)UserDict:  将标准的dict用python实现了一遍
'''


from collections import OrderedDict, ChainMap, Counter, UserDict

if __name__ == "__main__":
    # 1、OrderedDict
    d = OrderedDict()
    d['one'] = 1
    d['two'] = 2
    d['three'] = 3
    for _ in range(10):
        print("%d次:" % _)
        for k, v in d.items():
            print("**", k, v)        # OrderedDict迭代的时候的顺序总是跟插入顺序一致


    # 2、ChainMap

    pylookup = ChainMap(d, globals())   # d和globals()都是映射类型,ChainMap会将其组合
    for v, k in pylookup.items():
        print(v, k)

    # 3、Counter
    ct = Counter('asfjlajslfjals')
    print(ct)      # Counter({'j': 3, 'l': 3, 's': 3, 'a': 3, 'f': 2})
                   # 存储的是每个字母出现的次数
    ct.update('jjjjjjjjlllllllll')
    print(ct)      # # Counter({'l': 12, 'j': 11, 's': 3, 'a': 3, 'f': 2})

    import random
    ct2 = Counter([random.randrange(1, 5) for _ in range(100)])   # 列表推导式创建Counter
    print(ct2)     # Counter({1: 30, 2: 24, 4: 24, 3: 22})

    ct3 = Counter((random.randrange(1, 5) for _ in range(100)))   # 生成器创建Counter
    print(ct3)      # Counter({2: 40, 3: 23, 4: 20, 1: 17})

    class Foo:
        def __init__(self, num):
            self.l = [random.randrange(1, 5) for _ in range(num)]

        def __iter__(self):
            return iter(self.l)

    ct4 = Counter(Foo(100))            # 可迭代对象创建Counter
    print(ct4)      # Counter({2: 31, 3: 25, 4: 25, 1: 19})

    # 4、UserDict
    # 创建自定义的映射类型,一般以UserDict为基类

    class My_dict(UserDict):
        def __missing__(self, key):
            if isinstance(key, str):
                raise KeyError(key)
            return self[str(key)]

        def __contains__(self, key):
            return str(key) in self.data

        def __setitem__(self, key, item):
            print("调用__setitem__。。。")
            self.data[str(key)] = item

    mdict = My_dict()
    mdict["one"] = 1      # 调用__setitem__。。。(下同)
    mdict["two"] = 2
    mdict["three"] = 3
    print(mdict)   # {'three': 3, 'one': 1, 'two': 2}

 

main函数下

这里我将介绍ZKWeb网页框架在Fluent NHibernate和Entity Framework Core上使用的办法。
可以做到添加实体字段后,只需刷新网页就可以把变更应用到数据库。

集合

'''
    集合对于很多人并不陌生,中学阶段就已经接触过。集合具有:
    (1)确定性:每一个对象都能确定是不是某一集合的元素,没有确定性就不能成为集合
    (2)互异性:集合中任意两个元素都是不同的对象
    (3)无序性:{a,b,c}{c,b,a}是同一个集合

    在python中,set中的元素必须是可散列的,但set本身不可散列(但是frosenset是可散列的)


    另外:set实现了很多基础运算
    &(交集)、|(并集)、-(差集)
'''


if __name__ == "__main__":
    # 创建集合
    s1 = set([1, 2, 3])
    s2 = {1, 2, 3, 4}
    print(s1, s2)     # {1, 2, 3} {1, 2, 3, 4}

    # 集合推导式
    s3 = {x**2 for x in range(10)}
    print(s3)         # {0, 1, 64, 4, 36, 9, 16, 49, 81, 25}

 

set的操作方法很多,本文截自<流畅的python>一书,如下三个表:

表一:集合的数学方法

 

表2:集合的比较运算

 

表3:集合的其他运算

 

#region
//为里面的每个元素赋值:(结构体名+点+结构体里面的变量名称=值)
student st = new student();//使用之前需要先初始化一下
st.name = "张三";//初始化出来的变量名可以看做一个类对象
st.nianling = 21;//类对象的名称是不能相同的
st.sex = "男";
st.name = "王五";
//使用的时候利用变量名点出来其中的变量进行使用
Console.WriteLine(st.name);
st.qq.abc="qsqs";//结构体中包含另一个结构体类型,可以直接点出来一以下的变量
st.shuzu = new string [9];//使用之前需要先开辟空间
st.shuzu[0] = "赵六";//数组元素赋值方式

实现全自动迁移的思路

数据库迁移需要指定变更的部分,例如添加表和添加字段。
而实现全自动迁移需要自动生成这个变更的部分,具体来说需要

  • 获取数据库现有的结构
  • 获取代码中现有的结构
  • 对比结构之间的差异并生成迁移

这正是Entity Framework的Add-Migration(或dotnet ef migrations add)命令所做的事情,
接下来我们将看如何不使用这类的命令,在NHibernate, Entity Framework和Entity Framework Core中实现全自动的处理。

映射的再讨论 

'''
    python标准库里面的映射类型都是可变的,有时候需要使用不可变的映射,从python3.3开始,types模块中引入了
    MappingProxyType类,如果给这个类一个映射,那么它会返回这个映射的试图,该试图是动态的,原映射如果有改动
    可立即通过这个试图观察到,但是这个试图无法对该映射进行修改。
'''
from types import MappingProxyType

if __name__ == "__main__":
    d = {'one':1, 'two':2, 'three':3}
    d_proxy = MappingProxyType(d)
    print(d_proxy)     # {'three': 3, 'two': 2, 'one': 1}
    print(d_proxy['one'])  # 1
    for k, v in d_proxy.items():
        print(k, v)

    #d_proxy['four'] = 4   # 报错:TypeError: 'mappingproxy' object does not support item assignment
    d['four'] = 4
    print(d_proxy)     # {'two': 2, 'three': 3, 'four': 4, 'one': 1}

 

  另外,《流畅的python》77页到80页对散列表算法以及字典、集合的效率、平时需要注意的问题进行了比较详细的探讨,建议严谨并有兴趣的同仁阅读,该部分内容对理解字典类型无比有益,场景中捉摸不透的莫名其妙的bug可能会迎刃而解。

   重要的结论摘录如下:

  (1)键必须是可散列的

  (2)字典在内存上的开销巨大

  (3)键查询很快

  (4)键的次序取决于添加顺序

  (5)往字典里添加新键可能会改变已有键的顺序

 

 

写在最后

全自动迁移数据库如果正确使用,可以增强项目中各个模块的独立性,减少开发和部署的工作量。
但是因为不能手动控制迁移内容,有一定的局限和危险,需要了解好使用的ORM迁移的特点。

本文主要内容

可散列类型

泛映射类型

字典

    (1)字典推导式

  (2)处理不存在的键

    (3)字典的变种

集合

映射的再讨论

 

python高级——目录

文中代码均放在github上:https://github.com/ampeeg/cnblogs/tree/master/python高级

 

student st = new student();//这句话是在main函数之中定义了一个名为st的student类型的结构体。

Fluent NHibernate的全自动迁移

ZKWeb框架使用的完整代码可以查看这里

首先Fluent NHibernate需要添加所有实体的映射类型,以下是生成配置和添加实体映射类型的例子。
配置类的结构可以查看这里

var db = MsSqlConfiguration.MsSql2008.ConnectionString("连接字符串");
var configuration = Fluently.Configure();
configuration.Database(db);
configuration.Mappings(m => {
    m.FluentMappings.Add(typeof(FooEntityMap));
    m.FluentMappings.Add(typeof(BarEntityMap));
    ...
});

接下来是把所有实体的结构添加或更新到数据库。
NHibernate提供了SchemaUpdate,这个类可以自动检测数据库中是否已经有表或字段,没有时自动添加。
使用办法非常简单,以下是使用的例子

configuration.ExposeConfiguration(c => {
    // 第一个参数 false: 不把语句输出到控制台
    // 第二个参数 true: 实际在数据库中执行语句
    new SchemaUpdate(c).Execute(false, true);
});

到这一步就已经实现了全自动迁移,但我们还有改进的余地。
因为SchemaUpdate不保存状态,每次都要检测数据库中的整个结构,所以执行起来EF的迁移要缓慢很多,
ZKWeb框架为了减少每次启动网站的时间,在执行更新之前还会检测是否需要更新。

var scriptBuilder = new StringBuilder();
scriptBuilder.AppendLine("/* this file is for database migration checking, don't execute it */");
new SchemaExport(c).Create(s => scriptBuilder.AppendLine(s), false);
var script = scriptBuilder.ToString();
if (!File.Exists(ddlPath) || script != File.ReadAllText(ddlPath)) {
    new SchemaUpdate(c).Execute(false, true);
    onBuildFactorySuccess = () => File.WriteAllText(ddlPath, script);
}

这段代码使用了SchemaExport来生成所有表的DDL脚本,生成后和上次的生成结果对比,不一致时才调用SchemaUpdate更新。

NHibernate提供的自动迁移有以下的特征,使用时应该注意

  • 字段只会添加,不会删除,如果你重命名了字段原来的字段也会保留在数据库中
  • 字段类型如果改变,数据库不会跟着改变
  • 关联的外键如果改变,迁移时有可能会出错

总结NHibernate的自动迁移只会添加表和字段,基本不会修改原有的结构,有一定的限制但是比较安全。

泛映射类型

'''
    泛映射类型就是广义上的对应关系,在数学中,我们将集合A对应集合B中的对应法则称为"映射"(Mapping)
    同样,在python里,我们称"键值对"为映射,这其实也是一种对应法则
    如果一个数据类型是映射,那么它肯定属于collections.abc.Mapping,可使用isinstance函数测试

    PS: 字典是 Python 语言中唯一的映射类型。映射类型对象里哈希值(键) 和指向的对象(值)是一对多的关系。
'''

from collections import abc

# 我们测试一些常用的类型是不是映射
if __name__ == "__main__":
    print(isinstance({}, abc.Mapping))      # True   字典是典型的键值对
    print(isinstance([1, 2], abc.Mapping))  # False  列表是序列
    print(isinstance((1, 2), abc.Mapping))  # False  元组是序列
    print(isinstance('adfasfd', abc.Mapping))  # False  字符串也是序列
'''
   大家可以查看_collections_abc.py源代码,里面基本的类型包含:
    ["Awaitable", "Coroutine", "AsyncIterable", "AsyncIterator",
    "Hashable", "Iterable", "Iterator", "Generator",
    "Sized", "Container", "Callable",
     "Set", "MutableSet",
     "Mapping", "MutableMapping",
     "MappingView", "KeysView", "ItemsView", "ValuesView",
     "Sequence", "MutableSequence",
    "ByteString",
    ]
'''

 

'''
    如果我们自己想定义一个映射类型的对象,那么必须实现__getitem__、__iter__、__len__方法

    PS:关于该部分的原理,本人暂未查看说明文档,毕竟现实中几乎不可能自定义映射;有兴趣的同志可深入钻研。
'''


class Foo(abc.Mapping):
    def __init__(self, name):
        self.name = name

    def __getitem__(self, item):
        return self.name

    def __iter__(self):
        return iter(str(self.name))

    def __len__(self):
        return len(self.name)


print(isinstance(Foo("123"), abc.Mapping))      # True

 

这样就可以在用的时候省下再次初始化结构体。

在开发涉及到数据库的程序时,常会遇到一开始设计的结构不能满足需求需要再添加新字段或新表的情况,这时就需要进行数据库迁移。
实现数据库迁移有很多种办法,从手动管理各个版本的ddl脚本,到实现自己的migrator,或是使用Entity Framework提供的Code First迁移功能。
Entity Framework提供的迁移功能可以满足大部分人的需求,但仍会存在难以分项目管理迁移代码和容易出现"context has changed"错误的问题。

可散列类型

'''
    可散列数据类型(也称可hash)————我理解"可散列"就是"可hash"
    可hash的对象需要实现__hash__方法,返回hash值;另外为了与其他对象比较还需要有__eq__方法

    原子不可变数据类型(str、bytes和数值类型)都是可散列的,可散列对象必须满足下列要求:
    (1)实现了__hash__方法,并且所得到的hash值是不变的
    (2)实现了__eq__方法,用来比较
    (3)若a == b 为真,那么hash(a) == hash(b)也是真
'''


# 创建类Foo,并实现__hash__和__eq__

class Foo:
    def __init__(self, name):
        self.name = name

    def __hash__(self):
        print("正在hash...")
        return hash(self.name)

    def __eq__(self, other):
        print("正在比较...")
        return self.name == other.name

    def __repr__(self):
        return self.name


if __name__ == "__main__":

    f1 = Foo("小李")
    f2 = Foo("小红")
    f3 = Foo("小李")

    s = set([f1, f2, f3])        # 集合实现不重复的原理正好利用了散列表
    print(s)                     # {小红, 小李}
    print( f1 == f3, hash(f1) == hash(f3))      # True True 满足可散列对象的第三个条件
'''
    对于元组来说,只有当一个元组包含的所有元素都是可hash的情况下,它才是可hash的
'''
t1 = (1, 2, 3, [1, 2])   # 元组里的列表的值是可变的,所以不可hash
try:
    print(hash(t1))
except Exception as e:
    print(e)             # unhashable type: 'list'

t2 = (1, 2, 3, (1, 2))   # 元组里的元素都是不可变的,并且第二层元组里面的元素也不可变,所以可hash
print(hash(t2))          # 3896079550788208169

t3 = (1, 2, 3, frozenset([1, 2]))
print(hash(t3))          # -5691000848003037416

 

}

Entity Framework的全自动迁移

ZKWeb框架没有支持Entity Framework 6,但实现比较简单我就直接上代码了。
例子

// 调用静态函数,放到程序启动时即可
// Database是System.Data.Entity.Database
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, MyConfiguration>());

public class MyConfiguration : DbMigrationsConfiguration<MyContext> {
    public MyConfiguration() {
        AutomaticMigrationsEnabled = true; // 启用自动迁移功能
        AutomaticMigrationDataLossAllowed = true; // 允许自动删字段,危险但是不加这个不能重命名字段
    }
}

Entity Framework提供的自动迁移有以下的特征,使用时应该注意

  • 如果字段重命名,旧的字段会被删除掉,推荐做好数据的备份和尽量避免重命名字段
  • 外键关联和字段类型都会自动变化,变化时有可能会导致原有的数据丢失
  • 自动迁移的记录和使用工具迁移一样,都会保存在__MigrationHistory表中,切勿混用否则代码将不能用到新的数据库中

总结Entity Framework的迁移可以保证实体和数据库之间很强的一致性,但是使用不当会导致原有数据的丢失,请务必做好数据库的定时备份。

public string name;

Entity Framework Core的全自动迁移

Entity Framework Core去掉了SetInitializer选项,取而代之的是DatabaseFacade.MigrateDatabaseFacade.EnsureCreated
DatabaseFacade.Migrate可以应用使用ef命令生成的迁移代码,避免在生产环境中执行ef命令。
DatabaseFacade.EnsureCreated则从头创建所有数据表和字段,但只能创建不能更新,不会添加纪录到__MigrationHistory
这两个函数都不能实现全自动迁移,ZKWeb框架使用了EF内部提供的函数,完整代码可以查看这里

Entity Framework Core的自动迁移实现比较复杂,我们需要分两步走。

  • 第一步 创建迁移记录__ZKWeb_MigrationHistory表,这个表和EF自带的结构相同,但这个表是给自己用的不是给ef命令用的
  • 第二部 查找最后一条迁移记录,和当前的结构进行对比,找出差异并更新数据库

第一步的代码使用了EnsureCreated创建数据库和迁移记录表,其中EFCoreDatabaseContextBase只有迁移记录一个表。
创建完以后还要把带迁移记录的结构保留下来,用作后面的对比,如果这里不保留会导致迁移记录的重复创建错误。

using (var context = new EFCoreDatabaseContextBase(Database, ConnectionString)) {
    // We may need create a new database and migration history table
    // It's done here
    context.Database.EnsureCreated();
    initialModel = context.Model;
}

在执行第二步之前,还需要先判断连接的数据库是不是关系数据库,
因为Entity Framework Core以后还会支持redis mongodb等非关系型数据库,自动迁移只应该用在关系数据库中。

using (var context = new EFCoreDatabaseContext(Database, ConnectionString)) {
    var serviceProvider = ((IInfrastructure<IServiceProvider>)context).Instance;
    var databaseCreator = serviceProvider.GetService<IDatabaseCreator>();
    if (databaseCreator is IRelationalDatabaseCreator) {
        // It's a relational database, create and apply the migration
        MigrateRelationalDatabase(context, initialModel);
    } else {
        // It maybe an in-memory database or no-sql database, do nothing
    }
}

第二步需要查找最后一条迁移记录,和当前的结构进行对比,找出差异并更新数据库。

先看迁移记录表的内容,迁移记录表中有三个字段

  • Revision 每次迁移都会+1
  • Model 当前的结构,格式是c#代码
  • ProductVersion 迁移时Entity Framework Core的版本号

Model存放的代码例子如下,这段代码记录了所有表的所有字段的定义,是自动生成的。
后面我将会讲解如何生成这段代码。

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using ZKWeb.ORM.EFCore;

namespace ZKWeb.ORM.EFCore.Migrations
{
    [DbContext(typeof(EFCoreDatabaseContext))]
    partial class Migration_636089159513819123 : ModelSnapshot
    {
        protected override void BuildModel(ModelBuilder modelBuilder)
        {
            modelBuilder
                .HasAnnotation("ProductVersion", "1.0.0-rtm-21431")
                .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);

            modelBuilder.Entity("Example.Entities.Foo", b =>
                {
                    b.Property<Guid>("Id")
                        .ValueGeneratedOnAdd();

                    b.Property<string>("Name")
                        .IsRequired();
                });
            }
        }
    }
}

接下来查找最后一条迁移记录:

var lastModel = initialModel;
var histories = context.Set<EFCoreMigrationHistory>();
var lastMigration = histories.OrderByDescending(h => h.Revision).FirstOrDefault();

存在时,编译Model中的代码并且获取ModelSnapshot.Model的值,这个值就是上一次迁移时的完整结构。
不存在时,将使用initialModel的结构。
编译使用的是另外一个组件,你也可以用Roslyn CSharp Scripting包提供的接口编译。

if (lastMigration != null) {
    // Remove old snapshot code and assembly
    var tempPath = Path.GetTempPath();
    foreach (var file in Directory.EnumerateFiles(
        tempPath, ModelSnapshotFilePrefix + "*").ToList()) {
        try { File.Delete(file); } catch { }
    }
    // Write snapshot code to temp directory and compile it to assembly
    var assemblyName = ModelSnapshotFilePrefix + DateTime.UtcNow.Ticks;
    var codePath = Path.Combine(tempPath, assemblyName + ".cs");
    var assemblyPath = Path.Combine(tempPath, assemblyName + ".dll");
    var compileService = Application.Ioc.Resolve<ICompilerService>();
    var assemblyLoader = Application.Ioc.Resolve<IAssemblyLoader>();
    File.WriteAllText(codePath, lastMigration.Model);
    compileService.Compile(new[] { codePath }, assemblyName, assemblyPath);
    // Load assembly and create the snapshot instance
    var assembly = assemblyLoader.LoadFile(assemblyPath);
    var snapshot = (ModelSnapshot)Activator.CreateInstance(
        assembly.GetTypes().First(t =>
        typeof(ModelSnapshot).GetTypeInfo().IsAssignableFrom(t)));
    lastModel = snapshot.Model;
}

和当前的结构进行对比:

// Compare with the newest model
var modelDiffer = serviceProvider.GetService<IMigrationsModelDiffer>();
var sqlGenerator = serviceProvider.GetService<IMigrationsSqlGenerator>();
var commandExecutor = serviceProvider.GetService<IMigrationCommandExecutor>();
var operations = modelDiffer.GetDifferences(lastModel, context.Model);
if (operations.Count <= 0) {
    // There no difference
    return;
}

如果有差异,生成迁移命令(commands)和当前完整结构的快照(modelSnapshot)。
上面Model中的代码由这里的CSharpMigrationsGenerator生成,modelSnapshot的类型是string

// There some difference, we need perform the migration
var commands = sqlGenerator.Generate(operations, context.Model);
var connection = serviceProvider.GetService<IRelationalConnection>();
// Take a snapshot to the newest model
var codeHelper = new CSharpHelper();
var generator = new CSharpMigrationsGenerator(
    codeHelper,
    new CSharpMigrationOperationGenerator(codeHelper),
    new CSharpSnapshotGenerator(codeHelper));
var modelSnapshot = generator.GenerateSnapshot(
    ModelSnapshotNamespace, context.GetType(),
    ModelSnapshotClassPrefix + DateTime.UtcNow.Ticks, context.Model);

插入迁移记录并执行迁移命令:

// Insert the history first, if migration failed, delete it
var history = new EFCoreMigrationHistory(modelSnapshot);
histories.Add(history);
context.SaveChanges();
try {
    // Execute migration commands
    commandExecutor.ExecuteNonQuery(commands, connection);
} catch {
    histories.Remove(history);
    context.SaveChanges();
    throw;
}

到这里就完成了Entity Framework Core的自动迁移,以后每次有更新都会对比最后一次迁移时的结构并执行更新。
Entity Framework Core的迁移特点和Entity Framework一样,可以保证很强的一致性但需要注意防止数据的丢失。

{

 

 

二、用法:

可以在此前的student的结构体中在定义一个结构体

本文由澳门新葡亰手机版发布于编程,转载请注明出处:枚举类型和结构体,全自动迁移数据库的实现

上一篇:知识回顾 下一篇:网页截图完毕重大词高亮,静态构造函数
猜你喜欢
热门排行
精彩图文