ASP.NET HttpRuntime.Cache缓存类使用总结 - Kencery - 博客园

mikel阅读(156)

来源: ASP.NET HttpRuntime.Cache缓存类使用总结 – Kencery – 博客园

  1.高性能文件缓存key-value存储—Redis

  2.高性能文件缓存key-value存储—Memcached

备注:三篇博文结合阅读,简单理解并且使用,如果想深入学习,请多参考文章中给出的博文地址。

1.前言

a.在Web开发中,我们经常能够使用到缓存对象(Cache),在ASP.NET中提供了两种缓存对象,HttpContext.Current.Cache和HttpRuntime.Cache,那么他们有什么区别呢?下面简单描述一下:

(1):HttpContext.Current.Cache 为当前Http请求获取Cache对象,通俗来说就是由于此缓存封装在了HttpContenxt中,而HttpContext只局限于Web中,所以此缓存信息只能够在Web中使用。

(2):HttpRuntime.Cache 获取当前应用程序的Cache,通俗来说就是此缓存信息虽然被放在了System.Web命名空间下,但是非Web程序也可以使用此缓存。

上面两种类型作对比,我们就能够看出,一般情况下我们都建议使用HttpRuntime.Cache 。

b.在缓存领域中,现在不止只有ASP.NET提供的缓存,还有Redis和Memcached等开源的Key_Value存储系统,也是基于内存去管理的,那么我们在这些文章中基本也都会有说到。

c.那么这篇文章我们就简单的封装一下HttpRuntime.Cache类的公用类,提供给别人直接使用,如有错误或者问题,请留言,必在第一时间去处理。

d.缓存详解:http://www.cnblogs.com/caoxch/archive/2006/11/20/566236.html

e.GitHub地址:https://github.com/kencery/Common/tree/master/KenceryCommonMethod/%E7%BC%93%E5%AD%98

2.为什么使用缓存

a.那么说了这么多,最终要的一个问题是我们为什么要使用缓存呢?

(1):降低延迟,使响应速度加快。

(2):降低网络传输,使响应速度加快。

………………………

简单总结就是一句话,使用缓存是为了使系统更加稳定和快速。

b.上面我们知道了为什么使用缓存,那么在什么情况下我们能够使用缓存呢?

(1):数据可能会被频繁的使用。

(2):数据的访问不频繁,但是它的生命周期很长,这样的数据建议也缓存起来,比如:淘宝的商品明细。

………………….

c.当然一般系统中我们建议使用Memcached和Redis键值对来存储缓存信息。

d.那么是不是我们在写程序的时候想写缓存就写缓存呢?当然不是,我们还要判断哪里该用,哪里不该用,比如:如果我们整页输出缓存的话,会影响我们数据的更新等等…….

e.这篇博文我们整理了HttpRuntime Cache的共同类,将如何使用此类都已经在代码中展示出来,代码如下:

3.代码展示

复制代码
  1 // 源文件头信息:
  2 // <copyright file="HttpRuntimeCache.cs">
  3 // Copyright(c)2014-2034 Kencery.All rights reserved.
  4 // 个人博客:http://www.cnblogs.com/hanyinglong
  5 // 创建人:韩迎龙(kencery)
  6 // 创建时间:2015-8-11
  7 // </copyright>
  8 
  9 using System;
 10 using System.Collections;
 11 using System.Web;
 12 using System.Web.Caching;
 13 
 14 namespace KenceryCommonMethod
 15 {
 16     /// <summary>
 17     /// HttpRuntime Cache读取设置缓存信息封装
 18     /// <auther>
 19     ///     <name>Kencery</name>
 20     ///     <date>2015-8-11</date>
 21     /// </auther>
 22     /// 使用描述:给缓存赋值使用HttpRuntimeCache.Set(key,value....)等参数(第三个参数可以传递文件的路径(HttpContext.Current.Server.MapPath()))
 23     /// 读取缓存中的值使用JObject jObject=HttpRuntimeCache.Get(key) as JObject,读取到值之后就可以进行一系列判断
 24     /// </summary>
 25     public class HttpRuntimeCache
 26     {
 27         /// <summary>
 28         /// 设置缓存时间,配置(从配置文件中读取)
 29         /// </summary>
 30         private const double Seconds = 30*24*60*60;
 31 
 32         /// <summary>
 33         /// 缓存指定对象,设置缓存
 34         /// </summary>
 35         public static bool Set(string key, object value)
 36         {
 37             return Set(key, value, null, DateTime.Now.AddSeconds(Seconds), Cache.NoSlidingExpiration,
 38                 CacheItemPriority.Default, null);
 39         }
 40 
 41         /// <summary>
 42         ///  缓存指定对象,设置缓存
 43         /// </summary>
 44         public static bool Set(string key, object value, string path)
 45         {
 46             try
 47             {
 48                 var cacheDependency = new CacheDependency(path);
 49                 return Set(key, value, cacheDependency);
 50             }
 51             catch
 52             {
 53                 return false;
 54             }
 55         }
 56 
 57         /// <summary>
 58         /// 缓存指定对象,设置缓存
 59         /// </summary>
 60         public static bool Set(string key, object value, CacheDependency cacheDependency)
 61         {
 62             return Set(key, value, cacheDependency, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration,
 63                 CacheItemPriority.Default, null);
 64         }
 65 
 66         /// <summary>
 67         /// 缓存指定对象,设置缓存
 68         /// </summary>
 69         public static bool Set(string key, object value, double seconds, bool isAbsulute)
 70         {
 71             return Set(key, value, null, (isAbsulute ? DateTime.Now.AddSeconds(seconds) : Cache.NoAbsoluteExpiration),
 72                 (isAbsulute ? Cache.NoSlidingExpiration : TimeSpan.FromSeconds(seconds)), CacheItemPriority.Default,
 73                 null);
 74         }
 75 
 76         /// <summary>
 77         /// 获取缓存对象
 78         /// </summary>
 79         public static object Get(string key)
 80         {
 81             return GetPrivate(key);
 82         }
 83 
 84         /// <summary>
 85         /// 判断缓存中是否含有缓存该键
 86         /// </summary>
 87         public static bool Exists(string key)
 88         {
 89             return (GetPrivate(key) != null);
 90         }
 91 
 92         /// <summary>
 93         /// 移除缓存对象
 94         /// </summary>
 95         /// <param name="key"></param>
 96         /// <returns></returns>
 97         public static bool Remove(string key)
 98         {
 99             if (string.IsNullOrEmpty(key))
100             {
101                 return false;
102             }
103             HttpRuntime.Cache.Remove(key);
104             return true;
105         }
106 
107         /// <summary>
108         /// 移除所有缓存
109         /// </summary>
110         /// <returns></returns>
111         public static bool RemoveAll()
112         {
113             IDictionaryEnumerator iDictionaryEnumerator = HttpRuntime.Cache.GetEnumerator();
114             while (iDictionaryEnumerator.MoveNext())
115             {
116                 HttpRuntime.Cache.Remove(Convert.ToString(iDictionaryEnumerator.Key));
117             }
118             return true;
119         }
120 
121         //------------------------提供给上面方法进行调用-----------------------------------
122         /// <summary>
123         /// 设置缓存
124         /// </summary>
125         public static bool Set(string key, object value, CacheDependency cacheDependency, DateTime dateTime,
126             TimeSpan timeSpan, CacheItemPriority cacheItemPriority, CacheItemRemovedCallback cacheItemRemovedCallback)
127         {
128             if (string.IsNullOrEmpty(key) || value == null)
129             {
130                 return false;
131             }
132             HttpRuntime.Cache.Insert(key, value, cacheDependency, dateTime, timeSpan, cacheItemPriority,
133                 cacheItemRemovedCallback);
134             return true;
135         }
136 
137         /// <summary>
138         /// 获取缓存
139         /// </summary>
140         private static object GetPrivate(string key)
141         {
142             return string.IsNullOrEmpty(key) ? null : HttpRuntime.Cache.Get(key);
143         }
144     }
145 }
复制代码

C# 6.0 内插字符串 (Interpolated Strings ) - 刀是什么样的刀 - 博客园

mikel阅读(219)

来源: C# 6.0 内插字符串 (Interpolated Strings ) – 刀是什么样的刀 – 博客园

看Interpolated Strings之前,让我们先看EF Core 2.0 的一个新的特性:String interpolation in FromSql and ExecuteSqlCommand

var city = "London";

using (var context = CreateContext())
{
    context.Customers
       .FromSql($@"
           SELECT *
           FROM Customers
           WHERE City = {city}")
       .ToArray();
}

SQL语句以参数化的方式执行,所以是防字符串注入的。

@p0='London' (Size = 4000)

SELECT *
FROM Customers
WHERE City = @p0

一直认为Interpolated Strings只是String.Format的语法糖,传给FromSQL的方法只是一个普通的字符串,已经移除了花括号,并把变量替换成了对应的值。FromSql获取不到变量信息,怎么实现参数化查询的呢? OK,让我们从头看起吧。

什么是内插字符串 (Interpolated Strings)

内插字符串是C# 6.0 引入的新的语法,它允许在字符串中插入表达式。

var name = "world";
Console.WriteLine($"hello {name}");

这种方式相对与之前的string.Format或者string.Concat更容易书写,可读性更高。就这点,已经可以令大多数人满意了。事实上,它不仅仅是一个简单的字符串。

内插字符串 (Interpolated Strings) 是什么?

用代码来回答这个问题:

var name = "world";
string str1 = $"hello {name}";  //等于 var str1 = $"hello {name}";
IFormattable str2 =  $"hello {name}";
FormattableString str3 = $"hello {name}";

可以看出,Interpolated Strings 可以隐式转换为3种形式。实际上式编译器默默的为我们做了转换:

var name = "world";
string str1 = string.Format("hello {0}",name);  //等于 var str1 = $"hello {name}";
IFormattable str2 = FormattableStringFactory.Create("hello {0}",name);
FormattableString str3 = FormattableStringFactory.Create("hello {0}",name);
  • IFormattable 从.net Framwork 1 时代就有了,只有一个ToString方法,可以传入IFormatProvider来控制字符串的格式化。今天的主角不是他。
  • FormattableString 伴随Interpolated Strings引入的新类。

FormattableString 是什么?

先看一段代码

var name = "world";
FormattableString fmtString = $"hello {name}";
Console.WriteLine(fmtString.ArgumentCount);  //1
Console.WriteLine(fmtString.Format);  //hello {0}
foreach (var arg in fmtString.GetArguments())
{
    Console.WriteLine(arg);  //world
    Console.WriteLine(arg.GetType()); //System.String
}

可以看出FormattableString保存了Interpolated Strings的所有信息,所以EF Core 2.0能够以参数化的方式来执行SQL了。

EF Core 中的注意事项

因为隐式转换的原因,在使用EF Core的FromSql 方法和 ExecuteSqlCommand方法时,需要特别小心。一不留神就会调入陷阱。

var city = "London";

using (var context = CreateContext())
{
    //方法一,非参数化
    var sql = $" SELECT * FROM Customers WHERE City = {city}";
    context.Customers.FromSql(sql).ToArray();

    //方法二,参数化
    context.Customers.FromSql($" SELECT * FROM Customers WHERE City = {city}").ToArray();

   //方法三,参数化
    FormattableString fsql = $" SELECT * FROM Customers WHERE City = {city}";
    context.Customers.FromSql(fsql).ToArray();

    //方法四,非参数化
    var sql = " SELECT * FROM Customers WHERE City = @p0";
    context.Customers.FromSql(sql, city).ToArray();

}

第一种方法,因为sql的赋值被编译成String.Format方法的调用,返回的是字符串。sql变量传入FromSql方法时,又经过一次System.String 到Microsoft.EntityFrameworkCore.RawSqlString隐式转换。但sql变量本身已经丢失了参数信息,所以无法实现参数化的查询。
第四种方法, 也是Interpolated Strings -> String -> RawSqlString的转换过程,但因为变量是分开传入FromSql方法的,所以是以参数化的方式执行的。

其他

熟悉ES2015的同学可以看看JavaScript中的实现,Tagged template literals,这和Interpolated Strings 非常类似。

昨晚凌晨12点发帖,不知道为什么被移除首页了。感觉是篇幅不够的原因,重新加了点EF Core注意事项,但超过1小时没办法重新回首页了。七年来的第一篇文章,有点遗憾。希望大家喜欢。

.NET单点登录实现方法----两种 - 其实丿很简单 - 博客园

mikel阅读(159)

来源: .NET单点登录实现方法—-两种 – 其实丿很简单 – 博客园

第一种模式:同一顶级域名下cookie共享,代码如下

1
2
3
4
5
6
HttpCookie cookies = new HttpCookie("Token");
cookies.Expires = DateTime.Now.AddDays(7);
cookies.Path = "/";
cookies.Domain = "xxxx.com";//设置同一顶级域名
cookies.Value = "token的值";
System.Web.HttpContext.Current.Response.Cookies.Add(cookies);

 

第二种模式:跨域下实现单点登录

IIS 搭建https站点_iis 新建443-CSDN博客

mikel阅读(147)

来源: IIS 搭建https站点_iis 新建443-CSDN博客

1. 新建自签名证书

 

2. 新建一个https站点,type选https, 端口号默认443,也可以更改。证书下拉选择上一步创建的那个.hostname填写自己想要的。高版本iis可以直接填写host name. 低版本需要修改,用图二方法

 

 

 

3. 创建好后,选中网站,点击右栏的ssl settings. 操作如下。这样新建的站点就不会弹出是否继续的安全提示

 

 

4. 然后host里配置好域名指向本机。至此,自搭建https 站点就此完成。
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/flyingshuai/article/details/105795847

本地PostMan双击无响应解决方法_为什么双击了postman转了一下就关掉了-CSDN博客

mikel阅读(416)

来源: 本地PostMan双击无响应解决方法_为什么双击了postman转了一下就关掉了-CSDN博客

解决步骤
本地安装postman后,一直使用正常,现突然双击无响应,网上查了一些解决方法,可能是我本地自动更新导致此问题,以下是我解决的过程。猜想可能是高版本不稳定,或者更新过程文件有缺失。

(1)通过桌面快捷方式找到postman的exe启动文件的路径
(2)选择低版本文件夹里面的启动文件

(3)启动正常后,通过File==>settings==>Update 关闭自动更新

————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/qq_38820345/article/details/109547494

未能加载文件或程序集“Newtonsoft.Json”或它的某一个依赖项。找到的程序集清单 --解决-CSDN博客

mikel阅读(270)

来源: 未能加载文件或程序集“Newtonsoft.Json”或它的某一个依赖项。找到的程序集清单 –解决-CSDN博客

一:页面显示如下错误

 

二 运行时显示

 

三:在web.config中修改Newtonsoft.Json.dll版本

出错前显示如下:

 

找到Newtonsoft.Json.dll引用的版本

然后修改成当前版本

 

然后保存web.config 运行即可。
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/mym2018/article/details/82143781

阿里云短信 - __破 - 博客园

mikel阅读(174)

来源: 阿里云短信 – __破 – 博客园

阿里云短信

需要创建Accesskey

 

需要创建签名 发送模板

调用api发送短信相关代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.po.reggie.utils;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.profile.DefaultProfile;
/**
 * 短信发送工具类
 */
public class SMSUtils {
    /**
     * 发送短信
     * @param signName 签名
     * @param templateCode 模板
     * @param phoneNumbers 手机号
     * @param param 参数
     */
    public static void sendMessage(String signName, String templateCode,String phoneNumbers,String param){
        DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou""LTAI5tM6p--------MScq1gq""4fhp0t8j0y2-----UIlbgfH1GU");
        IAcsClient client = new DefaultAcsClient(profile);
        SendSmsRequest request = new SendSmsRequest();
        request.setSysRegionId("cn-hangzhou");
        request.setPhoneNumbers(phoneNumbers);
        request.setSignName(signName);
        request.setTemplateCode(templateCode);
        request.setTemplateParam("{\"code\":\""+param+"\"}");
        try {
            SendSmsResponse response = client.getAcsResponse(request);
            System.out.println("短信发送成功");
        }catch (ClientException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        sendMessage("破土重生科技股份有限公司","SMS_154950909","13522715896","12345678");
    }
}

代码开发-导入maven坐标

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.5.16</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
<version>2.1.0</version>
</dependency>

验证码的校验和登录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package com.po.reggie.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.po.reggie.common.R;
import com.po.reggie.domain.User;
import com.po.reggie.service.UserService;
import com.po.reggie.utils.ValidateCodeUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
import java.util.Map;
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
    @Autowired
    private UserService userService;
    /**
     * 发送手机短信验证码
     * @param user
     * @return
     */
    @PostMapping("/sendMsg")
    public R<String> sendMsg(@RequestBody User user, HttpSession session){
        //获取手机号
        String phone = user.getPhone();
        if(StringUtils.isNotEmpty(phone)){
            //生成随机的4位验证码
            String code = ValidateCodeUtils.generateValidateCode(4).toString();
            log.info("code={}",code);
            //调用阿里云提供的短信服务API完成发送短信
            //SMSUtils.sendMessage("瑞吉外卖","",phone,code);
            //需要将生成的验证码保存到Session
            session.setAttribute(phone,code);
            return R.success("手机验证码短信发送成功");
        }
        return R.error("短信发送失败");
    }
    /**
     * 移动端用户登录
     * @param map
     * @param session
     * @return
     */
    @PostMapping("/login")
    public R<User> login(@RequestBody Map map, HttpSession session){
        log.info(map.toString());
        //获取手机号
        String phone = map.get("phone").toString();
        //获取验证码
        String code = map.get("code").toString();
        //从Session中获取保存的验证码
        Object codeInSession = session.getAttribute(phone);
        //进行验证码的比对(页面提交的验证码和Session中保存的验证码比对)
        if(codeInSession != null && codeInSession.equals(code)){
            //如果能够比对成功,说明登录成功
            LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(User::getPhone,phone);
            User user = userService.getOne(queryWrapper);
            if(user == null){
                //判断当前手机号对应的用户是否为新用户,如果是新用户就自动完成注册
                user = new User();
                user.setPhone(phone);
                user.setStatus(1);
                userService.save(user);
            }
            session.setAttribute("user",user.getId());
            return R.success(user);
        }
        return R.error("登录失败");
    }
}

 

.Net对接阿里云短信平台 - 王精灵 - 博客园

mikel阅读(171)

来源: .Net对接阿里云短信平台 – 王精灵 – 博客园

一、在对接阿里云短信平台之前需完成阿里云短信平台短信签名和短信模板的配置,在此不做过多说明,建议采用企业账号的身份申请短信签名和短信模板更容易审核通过一些
阿里云短信平台地址 https://www.aliyun.com/product/sms?spm=5176.19720258.J_2686872250.7.7b812c4aBro5hF

二、完成以上配置之后在项目Nuget包中安装:AlibabaCloud.SDK.Dysmsapi20170525

三、主体代码

复制代码
        public void SendSms(string PhoneNumbers)
        {
            var verifyCode = GetVerifyCode();
            JObject jObject = new JObject();
            jObject.Add("code", verifyCode);
            AlibabaCloud.SDK.Dysmsapi20170525.Client client = CreateClient("accessKeyId", "accessKeySecret");
            AlibabaCloud.SDK.Dysmsapi20170525.Models.SendSmsRequest sendSmsRequest = new AlibabaCloud.SDK.Dysmsapi20170525.Models.SendSmsRequest
            {
                PhoneNumbers = PhoneNumbers,
                SignName = "SignName",
                TemplateCode = "TemplateCode",
                TemplateParam = jObject.ToString()
            };
            SendSmsResponse sendSmsResponse = client.SendSms(sendSmsRequest);
            if (sendSmsResponse.Body.Code == "OK" && sendSmsResponse.Body.Message == "OK")
            {
                textEdit1.Text = sendSmsResponse.Body.BizId;
                SetCacheTimeSpan(sendSmsResponse.Body.BizId, verifyCode, 20);
            }
            else if("isv.BUSINESS_LIMIT_CONTROL".Equals(sendSmsResponse.Body.Code))
            {
                throw new Exception("获取验证码过于频繁");
            }
        }


        public static AlibabaCloud.SDK.Dysmsapi20170525.Client CreateClient(string accessKeyId, string accessKeySecret)
        {
            AlibabaCloud.OpenApiClient.Models.Config config = new AlibabaCloud.OpenApiClient.Models.Config
            {
                AccessKeyId = accessKeyId,
                AccessKeySecret = accessKeySecret,
            };
            config.Endpoint = "dysmsapi.aliyuncs.com";
            return new AlibabaCloud.SDK.Dysmsapi20170525.Client(config);
        }


        /// <summary>
        /// 生成6位数随机验证码
        /// </summary>
        /// <returns></returns>
        private static int GetVerifyCode()
        {
            Random random = new Random();
            return random.Next(100000, 999999);
        }

        /// <summary>
        /// 设置缓存相对过期时间
        /// </summary>
        /// <param name="cacheKey">key</param>
        /// <param name="objValue">缓存对象</param>
        /// <param name="timeSpan">过期时间(秒)</param>
        public static void SetCacheTimeSpan(string cacheKey, object objValue, long timeSpan)
        {
            System.Web.Caching.Cache objCache = HttpRuntime.Cache;
            objCache.Insert(cacheKey, objValue, null, DateTime.MaxValue, TimeSpan.FromSeconds(timeSpan));
        }


        /// <summary>
        /// 获取Cache的value
        /// </summary>
        /// <param name="cacheKey"></param>
        /// <returns></returns>
        public object GetCacheValue(string cacheKey)
        {
            System.Web.Caching.Cache objCache = HttpRuntime.Cache;
            return objCache.Get(cacheKey);
        }
复制代码

四、调用

            SendSms(PhoneNumbers);

Token,Session,Cookie,JWT,Oauth2傻傻分不清楚 - 苏三说技术 - 博客园

mikel阅读(152)

来源: Token,Session,Cookie,JWT,Oauth2傻傻分不清楚 – 苏三说技术 – 博客园

前言

最近发现有些小伙伴,对Token、Session、Cookie、JWT、OAuth2这些概念非常容易搞混。

有些小伙伴在工作中可能会遇到过这样的困惑:

  • 做登录功能时,到底该用Session还是JWT?
  • OAuth2和Token是什么关系?
  • 为什么有的方案要把Token存在Cookie里?

今天这篇文章专门跟大家一起聊聊这个话题,希望对你会有所帮助。

一、从餐厅就餐模型开始讲

为了让大家更好理解,我先用一个餐厅就餐的比喻来解释这些概念:

image

现在,让我们深入每个概念的技术细节。

二、Cookie:HTTP的世界身份证

2.1 什么是Cookie?

Cookie是存储在浏览器端的一小段文本数据,由服务器通过HTTP响应头的Set-Cookie字段发送给浏览器,浏览器随后会自动在每次请求中通过Cookie头将其带回给服务器。

工作原理

image

2.2 Cookie实战代码

// 服务器设置Cookie
@PostMapping("/login")
public ResponseEntity login(@RequestBody User user, HttpServletResponse response) {
    if (authService.authenticate(user)) {
        Cookie cookie = new Cookie("session_id", generateSessionId());
        cookie.setMaxAge(3600); // 1小时有效期
        cookie.setHttpOnly(true); // 防止XSS攻击
        cookie.setSecure(true); // 仅HTTPS传输
        cookie.setPath("/"); // 对整个站点有效
        response.addCookie(cookie);
        return ResponseEntity.ok().build();
    }
    return ResponseEntity.status(401).build();
}

// 读取Cookie
@GetMapping("/profile")
public ResponseEntity getProfile(@CookieValue("session_id") String sessionId) {
    User user = sessionService.getUserBySession(sessionId);
    return ResponseEntity.ok(user);
}

2.3 Cookie的重要属性

属性 作用 安全建议
HttpOnly 防止JavaScript访问 必须设置为true,防XSS
Secure 仅通过HTTPS传输 生产环境必须设置为true
SameSite 控制跨站请求时是否发送Cookie 建议设置为Strict或Lax
Max-Age 设置Cookie有效期 根据业务安全性要求设置

三、Session:服务端的用户档案

3.1 什么是Session?

Session是存储在服务器端的用户状态信息。服务器为每个用户创建一个唯一的Session ID,并通过Cookie将这个ID传递给浏览器,浏览器后续请求时带上这个ID,服务器就能识别用户身份。

Session存储结构

// 典型的Session数据结构
public class UserSession {
    private String sessionId;
    private String userId;
    private String username;
    private Date loginTime;
    private Date lastAccessTime;
    private Map<String, Object> attributes; // 自定义属性
    
    // 省略getter/setter
}

3.2 Session实战代码

// 基于Spring Session的实现
@PostMapping("/login")
public String login(@RequestParam String username, 
                   @RequestParam String password,
                   HttpSession session) {
    User user = userService.authenticate(username, password);
    if (user != null) {
        // 将用户信息存入Session
        session.setAttribute("currentUser", user);
        session.setAttribute("loginTime", new Date());
        return "redirect:/dashboard";
    }
    return "login?error=true";
}

@GetMapping("/dashboard")
public String dashboard(HttpSession session) {
    // 从Session获取用户信息
    User user = (User) session.getAttribute("currentUser");
    if (user == null) {
        return "redirect:/login";
    }
    return "dashboard";
}

3.3 Session的存储方案

1. 内存存储(默认)

# application.yml
server:
  servlet:
    session:
      timeout: 1800 # 30分钟过期时间

2. Redis分布式存储

@Configuration
@EnableRedisHttpSession // 启用Redis Session存储
public class SessionConfig {
    @Bean
    public LettuceConnectionFactory connectionFactory() {
        return new LettuceConnectionFactory();
    }
}

3. Session集群同步问题

image

四、Token:去中心化的身份令牌

4.1 什么是Token?

Token是一种自包含的身份凭证,服务器不需要在服务端存储会话状态,所有必要信息都包含在Token本身中。

Token vs Session 核心区别
image

4.2 Token实战代码

// 生成Token
public String generateToken(User user) {
    long currentTime = System.currentTimeMillis();
    return JWT.create()
            .withIssuer("myapp") // 签发者
            .withSubject(user.getId()) // 用户ID
            .withClaim("username", user.getUsername())
            .withClaim("role", user.getRole())
            .withIssuedAt(new Date(currentTime)) // 签发时间
            .withExpiresAt(new Date(currentTime + 3600000)) // 过期时间
            .sign(Algorithm.HMAC256(secret)); // 签名密钥
}

// 验证Token
public boolean validateToken(String token) {
    try {
        JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret))
                .withIssuer("myapp")
                .build();
        DecodedJWT jwt = verifier.verify(token);
        return true;
    } catch (JWTVerificationException exception) {
        return false;
    }
}

五、JWT:现代化的Token标准

5.1 什么是JWT?

JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间安全地传输信息作为JSON对象。

这种信息可以被验证和信任,因为它是数字签名的。

JWT结构

header.payload.signature

解码示例

// Header
{
  "alg": "HS256",
  "typ": "JWT"
}

// Payload
{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022,
  "exp": 1516242622
}

// Signature
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

5.2 JWT实战代码

// 创建JWT
public String createJWT(User user) {
    return Jwts.builder()
            .setHeaderParam("typ", "JWT")
            .setSubject(user.getId())
            .setIssuer("myapp")
            .setIssuedAt(new Date())
            .setExpiration(new Date(System.currentTimeMillis() + 3600000))
            .claim("username", user.getUsername())
            .claim("role", user.getRole())
            .signWith(SignatureAlgorithm.HS256, secret.getBytes())
            .compact();
}

// 解析JWT
public Claims parseJWT(String jwt) {
    return Jwts.parser()
            .setSigningKey(secret.getBytes())
            .parseClaimsJws(jwt)
            .getBody();
}

// 在Spring Security中使用JWT
@Component
public class JwtFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                  HttpServletResponse response, 
                                  FilterChain chain) {
        String token = resolveToken(request);
        if (token != null && validateToken(token)) {
            Authentication auth = getAuthentication(token);
            SecurityContextHolder.getContext().setAuthentication(auth);
        }
        chain.doFilter(request, response);
    }
}

5.3 JWT的最佳实践

1. 安全存储

// 前端安全存储方案
// 不推荐:localStorage(易受XSS攻击)
// 推荐:HttpOnly Cookie(防XSS)或内存存储

2. 令牌刷新机制

// 双Token机制:Access Token + Refresh Token
public class TokenPair {
    private String accessToken;  // 短期有效:1小时
    private String refreshToken; // 长期有效:7天
}

// 刷新令牌接口
@PostMapping("/refresh")
public ResponseEntity refresh(@RequestBody RefreshRequest request) {
    String refreshToken = request.getRefreshToken();
    if (validateRefreshToken(refreshToken)) {
        String userId = extractUserId(refreshToken);
        String newAccessToken = generateAccessToken(userId);
        return ResponseEntity.ok(new TokenPair(newAccessToken, refreshToken));
    }
    return ResponseEntity.status(401).build();
}

六、OAuth 2.0:授权框架之王

6.1 什么是OAuth 2.0?

OAuth 2.0是一个授权框架,允许第三方应用在获得用户授权后,代表用户访问受保护的资源。

OAuth 2.0角色

  • 资源所有者(Resource Owner):用户
  • 客户端(Client):第三方应用
  • 授权服务器(Authorization Server):颁发访问令牌
  • 资源服务器(Resource Server):托管受保护资源

6.2 OAuth 2.0授权码流程

image

6.3 OAuth 2.0实战代码

// Spring Security OAuth2配置
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
    @Autowired
    private AuthenticationManager authenticationManager;
    
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("clientapp")
                .secret(passwordEncoder.encode("123456"))
                .authorizedGrantTypes("authorization_code", "refresh_token")
                .scopes("read", "write")
                .redirectUris("http://localhost:8080/callback");
    }
    
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.authenticationManager(authenticationManager)
                .tokenStore(tokenStore())
                .accessTokenConverter(accessTokenConverter());
    }
}

// 资源服务器配置
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/api/public/**").permitAll()
                .antMatchers("/api/private/**").authenticated()
                .antMatchers("/api/admin/**").hasRole("ADMIN");
    }
}

七、五大概念对比

为了让大家更清晰地理解这五个概念的关系和区别,我准备了以下对比表格:

7.1 功能定位对比

概念 本质 存储位置 主要用途 特点
Cookie HTTP状态管理机制 浏览器 维持会话状态 自动携带,有大小限制
Session 服务端会话信息 服务器 存储用户状态 服务端状态,需要存储管理
Token 访问凭证 客户端/服务端 身份认证 自包含,可验证
JWT Token的一种实现标准 客户端/服务端 安全传输信息 标准化,自包含,可签名
OAuth2 授权框架 不直接存储 第三方授权 标准化授权流程

7.2 应用场景对比

场景 推荐方案 原因说明
传统Web应用 Session + Cookie 简单易用,生态成熟
前后端分离应用 JWT 无状态,适合API认证
第三方登录 OAuth 2.0 标准化授权,安全可靠
微服务架构 JWT 分布式认证,无需会话同步
移动端应用 Token 轻量级,适合移动网络

7.3 安全考虑对比

安全威胁 Cookie方案防护 Token/JWT方案防护
XSS攻击 HttpOnly Cookie 避免localStorage存储
CSRF攻击 SameSite Cookie 自定义Header+CSRF Token
令牌泄露 短期有效+HTTPS 短期有效+HTTPS+刷新机制
数据篡改 服务端验证 签名验证

总结

通过今天的深入探讨,我们可以得出以下结论:

  1. Cookie是载体:HTTP协议的状态管理机制,是Session和Token的传输媒介之一。
  2. Session是状态:服务端维护的会话状态,需要借助Cookie或URL重写来实现。
  3. Token是凭证:认证授权的凭证,可以放在Cookie、Header或URL中。
  4. JWT是标准:Token的一种标准化实现,自包含、可验证、可信任。
  5. OAuth2是框架:授权框架,定义了完整的第三方授权流程。

最终建议

  • 简单Web应用:Session + Cookie
  • 前后端分离:JWT + HTTP Header
  • 第三方授权:OAuth 2.0 + JWT

没有最好的方案,只有最合适的方案。

理解每个技术的本质和适用场景,才能做出正确的架构决策。

最后说一句(求关注,别白嫖我)

如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下我的同名公众号:苏三说技术,您的支持是我坚持写作最大的动力。

求一键三连:点赞、转发、在看。

关注公众号:【苏三说技术】,在公众号中回复:进大厂,可以免费获取我最近整理的10万字的面试宝典,好多小伙伴靠这个宝典拿到了多家大厂的offer。

本文收录于我的技术网站http://www.susan.net.cn

实体框架 - 北在北方 - 博客园

mikel阅读(257)

来源: 实体框架 – 北在北方 – 博客园

实体框架的使用分为Model First,Code First(代码生成模型),Database First。Model First和Database First会使用实体设计器(edmx文件)来创建实体数据模型。

DbContext所使用的连接字符串如果是常规连接字符串则使用Code First,如果使用的是特殊的实体框架连接字符串,则使用Database First或Model First。

 

Database First:

从现有数据库构建edmx文件,然后edmx会生成DbContext和poco模型(model类),并将实体框架专用的连接字符串保存到所在项目的app.config中,但配置文件中的连接字符串并不能直接使用,连接字符串中包含有指向csdl,ssdl,msl文件的路径,自动生成的连接字符串中这3条路径包含一个“*”号,需将edmx所在类库的程序集名称替换“*”号。

Model First:

建立空的edmx文件,手动构建数据库模型,再由根据模型创建数据库和生成DbContext以及poco模型(model类)

Code First:

手写POCO,再由DbContext生成对应的数据库。

POCO中外键和导航属性应用virtual修饰(详情见导航属性的加载),以代理类便于重写。集合类型的导航属性应在构造方法中初始化为HashSet集合。

IDatabaseInitializer<TContext>(TContext是DbContext类型的实例),用于指定DbContext初次使用时用于初始化数据库的策略,使用策略须调用DbContext的DataBase属性(System.Data.Entity.Database实例)的SetInitializer方法,此初始化数据库策略在Global.asax的Application_Start方法中直接调用Database.SetInitializer静态方法,或者在DbContext的构造方法中执行Database.SetInitializer静态方法,或者在配置文件的entityFramwork/contexts的子节点配置,示例如下:

<entityFramework>

<contexts>

<context type=”[DbContext类],[程序集名称]”>

<databaseInitializer type=”[IDatabaseInitializer实现类],[程序集名称]” />

</context>

</contexts>

</entityFramework>

可使用的初始化策略有DropCreateDatabaseIfModelChanged<TContext>,表示如果模型改变,则删除并重建数据库,Database的CompatibleWithModel方法可用于检查模型是否发生改变;DropCreateDatabaseAlways<TContext>,表示每次使用DbContext都重建数据库;CreateDatabaseIfNotExists<TContext>,表示如果数据库不存在则创建数据库。用户选择使用策略时最好继承者三个类中的一个并重写Seed方法,该方法会在数据库创建后执行,此方法称为种子方法。

System.Data.Entity.Database类,可通过DbContext的Database属性获取Database类的实例,该实例可用于检查数据库的状态或执行一些数据库操作,如数据库是否存在,模型是否改变,创建或删除数据库操作,执行SQL脚本等操作。

对模型的定义可通过System.Component.DataAnnotations命名空间的特性,也可以通过Fluent API,Fluent API 的使用工具是DbModelBuilder类,此类可在DbContext的OnModelCreating方法的参数获得,通过Fluent API对模型映射所做的修改如同使用Data Annotation对模型所作的修改一样都会在数据迁移DbMigration中做出相应的反应,即数据迁移支持Fluent API和Data Annotation对模型的映射设置和修改。

 

Code First约定:

注:

主表(主体):具有主键的表。

从表(依赖实体):使用主表的主键作为本表的外键。

主键约定:类的“ID”名称的属性或“<类名>ID”的属性默认为模型的主键。

关系约定:模型的外键关系通过导航属性来推断。建议显示指定外键属性,外键的属性名称为“<导航属性名称>_<主体主键属性>”(如果外键属性是自动生成的,则导航属性名称与主体主键属性之间有下划线,如果是手写的可有可无)、“<主体类名><主体主键名称>”、“<主体模型名称>”。外键属性如果可为null,则外键关系可选的,且外键关系为非级联删除,如果不可为null则为必选的,并设置为级联删除。

对于无法推断出主键的引用类型的属性将作为复杂数据类型,在数据库中的体现是<复杂数据类型的属性名>_<复杂数据类型的类属性>。

当模型的导航属性无法在导航属性对应的另一个模型中找到合适的或应该的导航属性时,可使用InversePropertyAttribute手动指定导航属性对应的另一个模型中的导航属性。

当需要手动指定导航属性在模型中对应的外键属性时,使用ForeignKeyAttribute。

注意:

通过DbContext的访问类集合,返回的结果包括指定的类以及派生类,此结果在TPT策略下,通过执行外联接实现,在TPC下通过union操作实现,而正因此,导致在同时使用数据库的标识列和TPC策略下,派生类(可能多个)对应的表和基类对应的表中的记录不能具有相同的主键值,否则会导致DbContext内部调用的ObjectContext会创建重复的实体键,详情见TPC说明。

 

Fluent API的表映射策略:

每个继承层次结构一张表(TPH)(Fluent API的默认策略),每个类型一张表(TPT),每个具体类一张表(TPC);

单个实体对应多个表(实体拆分),将多个实体映射到一个表(表拆分)

TPH  Table per Hierarachy:每个层次结构一张表,表示继承此结构中的所有类型使用同一张表,这张表使用鉴别器列区分每行对应的模型,这个鉴别器列是对应鉴别器表“Discrimination”的外键,鉴别器表中具有鉴别器值,此值即为模型的类型名称。

modelBuilder.Entity<FatherClass>()

.Map<SonClass1>(m=>m.Requires(“Type”).HasValue(“SonClass1”))

.Map<SonClass2>(m=>m.Requires(“Type”).HasValue(“SonClass2”))

如果要求多态关系或查询,并且子类声明了很少的属性甚至子类的不同在于行为的话,建议使用TPH。目标是尽可能的减少列,并且在长期需求中这种非标准的结构不会引发问题。

TPH策略中,基础类型的字段会被用设为可为空,这会导致不可为空的外键属性因成为可为空而引发问题。

 

TPT  Table per Type:每个类型一张表,表示继承结构中的基类和派生类分别对应一张表,表中只映射对应类中定义的属性,不映射继承的属性,派生类对应的表和基类对应的表通过外键进行关联,以表示模型的继承的关系。

modelBuilder.Entity<SonClass1>().ToTable(“SonClass1”);

modelBuilder.Entity<SonClass2>().ToTable(“SonClass2”);

如果要求多态关系或查询,并且子类定义了较多的属性的话,建议使用TPT,或者如果在继承结构较复杂的情况下,join查询比union查询较节省资源的话,使用TPT,反之使用TPC。

 

TPC  Table per Concrete Type:每个具体类一张表,表示继承层级中的基类和派生类各自对应一张表,基类对应的表与派生类对应的表没有外键关系,派生类的属性包括继承的属性均映射到数据表中。使用MapInheritedProperties方法指示将继承的属性映射到数据表。此策略与数据库的标识列存在矛盾的情况,这个表映射策略会因为多个派生类以及基类具有同名称的标识列类型的主键属性,导致DbContext内部使用的ObjectContext的跟踪机制创建了重复的实体键(EntityKey)从而导致了数据插入失败,可以关闭标识列或设置为其他类型主键来手动设置主键属性来规避这个异常。

modelBuilder.Entity<FatherClass>()

.Property(c => c.ID)

.HasDatabaeGeneratedOption(DatabaseGeneratedOption.None)//在同时保存父类和子类时虽数据表没有外键关联,但因为继承关系会引发主键相关的异常,修改为手动设置主键则不会有此问题。

modelBuilder.Entity<SonClass1>().Map(m => {

m.MapInheritedProperties();

m.ToTable(“SonClass1”);

})

modelBuilder.Entity<SonClass2>().Map(m=>{

m.MapInheritedProperties();

m.ToTable(“SonClass2”);

})

如果不要求多态关系或查询,建议使用TPC,当很少查询基类或者很少有其他类类与基类关联,并且基类很少改动的情况下建议对继承树的高层使用TPC。

注意:使用TPC的模型所继承的属性不能来自这样的基类,这个基类进行了实体拆分,或所处的表进行了表拆分。

 

实体拆分:将单个实体映射到多个表,通过多次调用Map方法,将不同的属性分配到不同的表中。

modelBulder.Entity<Department>()

.Map(m=>{

m.Properties(t=>new{t.DepartmentID,t.Name});

m.ToTable(“Department”);’

})

.Map(m=>{

m.Properties(t=>new{t.DepartmentID,t.Administrator,t.StartDate,t.Budget});

m.ToTable(“DepartmentDetails”);

});

表拆分:将多个实体映射到一个表,通过指定相同的主键将两个实体映射到同一个表

modelBuilder.Entity<OfficeAssignment>().HasKey(t => t.InstructorID);

modelBuilder.Entity<Instructor>().HasRequired(t=>t.OfficeAssignment).WithRequriedPrincipal(t=>t.Instructor);

modelBuilder.Entity<OfficeAssignment>().ToTable(“Instructor”);

modelBuilder.Entity<Instructor>().ToTable(“Instructor”);

 

表关系映射与导航属性:

使用HasMany、HasRequired、HasOptional方法设置设置模型的导航属性的类型为多、单个必须、单个可选,这些方法返回的OptionalNavigationPropertyConfiguration类中With开头的方法用于设置关系类中导航属性和主体与依赖对象的关系。

略:普通的一对一与一对多关系示例代码

多对多关系:

modelBuilder.Entity<Model1>().HasMany(t1=>t1.NaviPropertyCollection).WithMany(t2=>t2.NaviPropertiyCollection)

多对多关系的表之间会自动增加一个关系表。

modelBuilder.Entity<Model1>()

.HasMany(m1=>m1.Model2Collection)

.WithMany(m2=>m2.Model1Collection)

.Map(m=>{

m.ToTable(“Many1ToMany2”)//指定关系表的表名

m.MapLeftKey(“Many1ID”);//指定关系表中对应设置表的外键列名

m.MapRightKey(“Many2ID”);//指定关系表中对应设置表相关联的关系表的外键列名

});

 

级联删除:

如果外键不可为空,则默认级联删除,否则,不是级联删除,主体删除后,外键置为null

modelBuilder.Convertions.Remove<OneToManyCascadeDeleteConvertion>();//移除当一对多必选关系时默认的级联删除选项

modelBuilder.Convertions.Remove<ManyToManyCascadeDeleteConvertion>();//移除当多对多必选关系时默认的级联删除

使用WillCascadeOnDelete方法手动设置是否级联删除

modelBuilder.Entity<TModel>()

.HasRquired(t=>t.NaviProperty)

.WithMany(anotherT=>anotherT.NaviPropertyCollection)

.HasForeighKey(t=>t.ForeighId)

.WillCascadeOnDelete(false)//设置为不级联删除

 

DbContext编写:

添加公共的DbSet类型或IDbSet类型的属性会由DbContext默认调用设置DbSet实例,如:

public IDbSet<TModel> TModels{get;set;}

public DbSet<TModel> TModels{get;set;}

访问实例也可通过Set方法,下面两行代码等效:

DbSet<TModel> result = dbContext.TModels;

DbSet<TModel> result = dbContext.Set<TModel>();

在访问DbSet属性但又仅仅进行只读操作时,为了提高性能可禁用跟踪,通过DbSet实例的AsNoTracking方法禁用追踪。

 

实体关系的修改:

实体关系可通过外键属性或导航属性进行修改,但只有执行DbContext的SaveChange后,外键属性以及导航属性才会反应真是的情况,如,将实体的导航属性进行了设置,这是对应的外键属性(如果有的话)可能为0(或其他表示未指定的值),只有执行SaveChange方法后,导航属性对应的外键值才为关联的模型的主键值。外键属性与导航属性的同步会在DetectChanged方法来完成,以下方法会自动调用DetectChanges方法:

DbSet.Add,DbSet.Find,DbSet.Remove,DbSet.Local,DbContext.SaveChange,DbContext.Attach,DbContext.GetValidationErrors,DbContext.Entry,DbChagne.Tracher.Entries,DbSet执行的linq查询。

删除关系可通过将导航属性设置为null的方式进行,亦或执行如下代码:

dbContext.Entry(tModel).Reference(c=>c.NaviProperty).CurrentValue=null;

通过ObjectContext的ObjectStateManager.ChangeRelationshipState方法也可修改关系:

((IObjectContextAdapter)dbContext).ObjectContext.ObjectStateManager.ChangeRelationshipState(model,anotherModel,m=>m.AnotherModel,EntityState.Added);//EntityState.Deleted表示删除,如果是更新关系的话需要Entity.Added新关系并Deleted旧关系。

DbSet.Local属性说明:该属性包含所有上下文中的对象,包括那些Add的但未保存到数据库的对象;但不包括Remove的但仍在数据库中的对象。

导航属性数据的加载:

导航属性对应的数据默认是惰性加载(在访问时才加载),如果希望查询数据时一同加载关联属性,须调用DbSet的Include方法并指定要求预加载的导航属性数据。

Var result = dbContext.Models.Include(b=>b.NaviProperty);//单级加载

Var result = dbContext.Models.Include(“NaviProperty.SecondNaviProperty”);//多级加载,加载Model的导航属性NaviProperty以及导航属性的导航属性SecondNaviProperty。

导航属性的惰性加载在首次访问时自动进行加载,加载通过继承自模型类的代理类进行,该代理类会重写导航属性,因此,模型类的导航属性必须用virtual修饰,不适用virtual修饰意味着惰性加载的关闭,在DbContext的构造方法中调用Configuration属性(DbContextConfiguration实例)的LazyLoadingEnabled=false也可设置惰性加载为关闭。

显示加载通过DbReferenceEntry的Load方法执行:

dbContext.Entry(model).Reference(m=>m.NavProperty).Load();

在更改外键属性后,可通过Load的方法显示加载同步的导航属性数据。通过dbContext的Entry访问代表集合类型的导航属性的DbCollectionEntry的Query方法获取可操作的IQueryable,来实现对部分实体的导航数据进行加载的要求。

代理类:

在为POCO实体类型实例化时,实体框架会动态生成派生自POCO实体的类,成为代理类,这些代理类通过重写POCO实体的virtual成员来实现一些自动操作,如之前提到的惰性加载。DbContext的Configuration属性(DbContextConfiguration实例)的ProxyCreationEnabled=false可将关闭代理类的生成。

实体的管理:

添加实体可通过DbSet的Add方法,也可通过dbContext的Attach方法并更改返回的Entry的状态为Added的方式。

如果仅仅希望保存实体的某个字段的修改到数据库的话,可通过Entry方法获取的DbEntityEntry实例并通过Property方法获取DbPropertyEntry对象实例并修改IsModified属性为true,这样就可以在保存数据库时,仅仅将IsModified=true的属性保存到数据库。

在调用SaveChange时,可能会引发DbUpdateConcurrentException,这表示更新时的并发异常,可通过dbContext.Entry(model)获取修改实体的DbEntityEntry实例,修改OriginValue和CurrentValue的方式来尝试消除引发并发的数据,DbEntityEntry的GetDatabaseValue方法可用来获取当前数据库中的数据,在重写原始值(OriginValue)或当前值(CurrentValue)时可能会使用到。