【ASP.NET Core】EF Core - “影子属性”

有朋友说老周近来博客更新较慢,确实有些慢,由于有些 bug 要研究,另外就是老周把部份内容转到直播上面,因此写博客的内容减小了一点。前端

老周以为,视频直播可能会好一些,虽然个人水平通常,不过直播时,老周能够现场演示,可能会比看博客效果要好(由于现场演示,有时候会有失误,没办法,水平有限)。还有一个,就是.NET 的资料其实不少,毕竟也发展了十几年了,有些东西若是别人都写过了,那我也很差意思重复了。.NET Core 尽管是跨平台版本,但核心依然是.net 基础,咱们不须要全新去学习,只要掌握一些新的变化就能够了。目前比较期待 .NET Core 3 的正式发布,等正式上线了,老周再挑一些有意义的内容写一下。数据库

此外,老周也可能会写一写其余方面的博客,好比 Python、GO、Ruby、Typescript 等。老周并非只会玩.NET ,只不过老周是主攻 .NET,在接触 .NET 以前,老周就学过不少东西,好比古老的 QBasic、Pascal ,老周在上初中时就学过。后来向 VB、C、C++ 进攻,顺便把 Ruby、Python、PB 也调戏一下,后来有一段时间,Delphi 和 E 语言也挺流行的,因此顺便也玩了两把。编程

再后来,学过 Java 和 PHP,抛 Java 而投 .NET 是由于 Java 太复杂,效率不高,没有深度把玩的兴趣。如今所谓的 Python 很热门纯属是商业炒做,Python 又不是什么新玩意儿,很古老了,固然相对于 C 来讲,是新了一点,究其特色,就是一种脚本语言(虽然有人死要说它不是脚本语言)。如今网上更有些无知小辈,觉得本身会写几行 Python 代码就处处去蹭热点,告诉你,老周当年学各类编程语言时,说不定你还没出生呢。因此,若是你真心喜欢 Python 的话,你用心去学就是了(其实老周也喜欢用 Python 来作图表),没必要理会商业炒做。api

记得去年 C 语言也被商业炒做了几个月,再往前几年,Javascript 和 Web 前端也被拼命炒做,说得好像 js 是万能的似的,吓得老周都不敢写前端了。最近几年,IT 界开始怀旧了,各类远古生物都被挖出来了,多是如今计算机行业已经没什么能够创新的缘由吧。如今说得较多的是人工智障,这个能够用,也能够不用,反正不痛不痒,算不上生产力革命(至少其震动效果比不上当年 Office 问世时对企业生产的影响大)。不过,人工智障在某些辅助领域仍是有用的,好比如今有些小区的智能门,应用效果还能够。可是,漏洞也是百出的,总之,人类能够用它来进行辅助,但不能过于依赖它。它不能解决全部问题。数组

许多科幻小说都会说人类会被机器人消灭。机器人也是人创造出来的,机器不可能比人强,也不可能灭掉人类(除非机器人比奥特曼里面的超兽还牛逼)。若是人类真的智力在衰减,那么根源仍是在人类本身。说白了就是,只有多是人类本身灭掉本身。你也不用以为太恐怖,其实只要你不要太依赖机器就好,不要失去你的本能和思考方式就行。编程语言

就像咱们码农,老周也同样,每天跟计算机打交道,但老周一直坚持:用电脑,但不依赖电脑,多作些机器不能作的事。再加一句:科学只能解决数学和工程问题,而人的问题,须要哲学和美学来解决ide

=============================================================================函数

好了,以上的都是 F 话,下面我们聊正题。今天我们耍一下 EF Core 中的影子属性。这个词翻译版本 TMD 多,有翻译为“卷影属性”的,如今的文档又改成“阴影属性”,这很差听,太有心理阴影了,故而,老周以为,叫“影子属性”好一些。学习

无论叫什么,你只要知道它是个啥就行。老周喜欢一句话总结,因此,来一句话:测试

模型类中没有定义的,但数据表中存在的属性——即模型类与数据表中没有对应关系的属性。

老周就用一个简单的示例来讲明一下吧。这个示例也是老周在视频直播时用的。

假设,有个模型类,叫 Student。

    public class Student
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
    }

但我想要一个属性,用来记录数据记录被写进数据库的时间,即还有一个属性,叫 InsertTime,不过,这个属性在 Student 类中是没有定义的,但在数据表中是有这一字段的。

所以,在从 DbContext 类派生时,须要重写 OnModelCreating 方法,经过 Model Builder 来定义这个“隐藏”的属性。

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Student>().Property<DateTime>("InsertTime");
        }

这个 InsertTime 属性就成了影子属性了。

 

下面是 DbContext 派生类的完整代码,我放出来是方便你去抄袭的,放心吧,无版权税的,尽管抄。

    public class MyContext : DbContext
    {
        public DbSet<Student> Students { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(@"server=(localdb)\MSSQLLocalDB;database=TestDb");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Student>().Property<DateTime>("InsertTime");
        }
    }

 

如今,到 Startup 类中,注册一下服务,让自定义的数据库上下文能够进行依赖注入。

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            services.AddDbContext<MyContext>();
        }

刚刚在 MyContext 类中已经配置链接字符串了,因此注册服务时就不用指定链接字符串了。

 

按老周前面所写的博文,接下来是建立数据迁移,不过,这一次老周使用的是直接在运行的时候建立数据库,方法也是很简单的,找到 Main 入口点,把里面的代码改一下(项目模板默认生成的代码不进行大改)。

        public static void Main(string[] args)
        {
            var host = CreateWebHostBuilder(args).Build();
            using(IServiceScope scope= host.Services.CreateScope()) { MyContext c = scope.ServiceProvider.GetService<MyContext>(); c.Database.EnsureCreated(); }             host.Run();
        }

也就是在 host 运行以前,建立一个“做用域”级别的服务实例,建立数据库,这个实例是临时使用,不遵循服务容器的生命周期规则。EnsureCreated 方法会检测数据库是否存在,若是不存在,就建立,而后返回 true;若是数据库已经存在,不作任何处理并返回 false。

若是你不打算写入一些初始数据,能够不在意方法的返回值,若是要写入初始数据,能够 if 一下,若是方法返回 true,就写一下数据进去(true 表示新数据库)。

 

接下来,我们用一个 API 控制器来测试一下。

    [Route("api/[action]")]
    public class TestController : Controller
    {
        readonly MyContext context; public TestController(MyContext c) => context = c; 
        [HttpPost]
        public ActionResult AddNew([FromBody]Student stu)
        {
            ……
        }

        [HttpGet]
        public JArray GetList()
        {
            ……
        }
    }

数据库上下文的实例由于已经注册到服务容器中,因此经过构造函数能够获得其实例引用。这个控制器有两个 action,AddNew 方法用来提交数据,以 POST 方式访问。

        [HttpPost]
        public ActionResult AddNew([FromBody]Student stu)
        {
            // 添加实体
            context.Students.Add(stu);
            // 设置影子属性
            context.Entry(stu).Property<DateTime>("InsertTime").CurrentValue = DateTime.Now;
            context.SaveChanges();
            return Ok("操做成功");  
        }

记得,参数要加上 FromBody 特性,由于它要从 HTTP 消息正文中提取,上次我直播时就是忘了写这个,因此提交不到数据。

这里要注意影子属性的赋值方法,由于它没有在 Student 类中公开,你不能经过访问成员来设置它,只能先经过 CurrentValue 属性来设置。

 

而后还有一个 GetList 方法,以 GET 方式来访问。有来返回全部 Student 数据。此处我用 JArray 以 JSON 数组格式返回。

        [HttpGet]
        public JArray GetList()
        {
            var q = from s in context.Students
                    select new
                    {
                        s.Id,
                        s.Name,
                        s.Age,
                        // 读取影子属性值
                        InsertTime = EF.Property<DateTime>(s, "InsertTime")
                    };
            JArray arr = new JArray();
            foreach (var s in q)
            {
                JObject obj = new JObject();
                obj.Add("id", new JValue(s.Id));
                obj.Add("name", new JValue(s.Name));
                obj.Add("age", new JValue(s.Age));
                obj.Add("add_time", new JValue(s.InsertTime));
                arr.Add(obj);
            }
            return arr;
        }

读取影子属性的方法是用 EF.Property 静态方法,第一个参数是实体模型类的实例,第二个参数是影子属性的名字。这个方法只能在 LINQ 树中使用,若是不在 LINQ 中用会发生异常。这里还有一个问题,就是在 select 子句中用 new 返回的匿名类型没法建立 JObject 对象,因此只好手动去构建。

 

如今能够测一下了,首先提交一下数据。

{
    "name":"李三跳",
    "age":52
}

接着获取一下数据列表。

[
    {
        "id": 1,
        "name": "什么鬼",
        "age": 29,
        "add_time": "2018-11-26T11:15:38.2012809"
    },
    {
        "id": 2,
        "name": "陈大扣",
        "age": 83,
        "add_time": "2018-11-26T11:21:07.5374308"
    },
    {
        "id": 3,
        "name": "李三跳",
        "age": 52,
        "add_time": "2018-11-26T12:41:51.758849"
    }
]

 

好了,示例就完成了。

影子属性的典型用法就像刚刚这个例子这样,能够用来记录数据的添加时间或者更新时间,但这种数据,通常不须要在实体模型中公开。

相关文章
相关标签/搜索