C#實(shí)現(xiàn)自由組合本地緩存、分布式緩存和數(shù)據(jù)查詢
一、背景介紹:
我們?cè)谶M(jìn)行數(shù)據(jù)存儲(chǔ)的時(shí)候,有時(shí)候會(huì)加入本地緩存、分布式緩存以及數(shù)據(jù)庫(kù)存儲(chǔ)三級(jí)的結(jié)構(gòu),當(dāng)我們?nèi)≈档臅r(shí)候經(jīng)常是像下面這樣的流程:
1.先取本地緩存,如果值存在直接返回
2.本地緩存不存在,獲取分布式緩存,存在直接返回,并更新本地緩存
3.分布式緩存不存在,查詢數(shù)據(jù)庫(kù),更新分布式緩存、更新本地緩存,最后返回
但如果對(duì)于一些場(chǎng)景,可能只有本地緩存、只有分布式緩存或者說(shuō)上面三種的幾種組合,我們?cè)趺匆獞?yīng)對(duì)這樣的變化,怎么能抽象出一套方式,能夠應(yīng)對(duì)各種不同數(shù)據(jù)存儲(chǔ)方式造成的變化。
二、設(shè)計(jì)思路:
首先我們分析一下上面這個(gè)過(guò)程的模型,可以抽象出5個(gè)方法:
- 1.GetDataFromLocalCache
- 2.GetDataFromDistributeCache
- 3.GetDataFromDB
- 4.SetDataToLocalCache
- 5.SetDataToDistributeCache
其實(shí),不同的場(chǎng)景無(wú)非就是這幾個(gè)方法的組合,只不過(guò)里面的內(nèi)容不同罷了,說(shuō)到這里我們應(yīng)該已經(jīng)有思路了,可以利用委托來(lái)實(shí)現(xiàn)。
三、詳細(xì)設(shè)計(jì):
①定義一個(gè)類,包含上面五個(gè)方法的委托;
public class DataOperateInput<T>
{
public Func<T> GetDataFromLocalCache { get; set; } = null; //獲取本地緩存數(shù)據(jù)
public Func<T> GetDataFromDistributeCache { get; set; } = null; //獲取分布式緩存數(shù)據(jù)
public Func<T> GetDataFromDb { get; set; } = null; //獲取DB數(shù)據(jù)
public Action<T> SetDataTolocalCache { get; set; } = null; //設(shè)置本地緩存數(shù)據(jù)
public Action<T> SetDataToDistributeCache { get; set; } = null; //設(shè)置分布式緩存數(shù)據(jù)
}②實(shí)現(xiàn)一個(gè)方法,組合這五個(gè)方法。
public class DataOperate
{
/// <summary>
/// 獲取數(shù)據(jù)入口
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="input"></param>
/// <returns></returns>
public T GetData<T>(DataOperateInput<T> input) where T : class, new()
{
T result = null;
//需要從本地緩存取
if (input.GetDataFromLocalCache != null)
{
//調(diào)用本地緩存委托方法獲取值
result = input.GetDataFromLocalCache();
if (result != null)
{
return result;
}
}
//獲取值為空或者不從本地緩存獲取,調(diào)用下面的方法,從分布式緩存和Db中獲取數(shù)據(jù)
return GetDataFromDistributeAndDB(input);
}
/// <summary>
/// 從分布式緩存和Db中獲取數(shù)據(jù)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="input"></param>
/// <returns></returns>
private T GetDataFromDistributeAndDB<T>(DataOperateInput<T> input) where T : class, new()
{
T result = null;
if (input.GetDataFromDistributeCache != null)
{
//從緩存中取值
result = input.GetDataFromDistributeCache();
//如果需要設(shè)置會(huì)本地緩存,那么設(shè)置
if (result != null)
{
//如果設(shè)置本地緩存的委托存在,調(diào)用它設(shè)置本地緩存
input.SetDataTolocalCache?.Invoke(result);
}
}
//獲取值為空或者不從分布式緩存獲取,調(diào)用下面的方法,從Db中獲取數(shù)據(jù)
return GetDataFromDB(input);
}
/// <summary>
/// 從數(shù)據(jù)庫(kù)中獲取數(shù)據(jù)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="input"></param>
/// <returns></returns>
private T GetDataFromDB<T>(DataOperateInput<T> input) where T : class, new()
{
T result = null;
if (input.GetDataFromDb != null)
{
//從DB中取值
result = input.GetDataFromDb();
//如果需要設(shè)置會(huì)分布式緩存和本地緩存,那么設(shè)置
if (result != null)
{
input.SetDataToDistributeCache?.Invoke(result);
input.SetDataTolocalCache?.Invoke(result);
}
}
return result;
}
}③ 具體實(shí)現(xiàn)一個(gè)服務(wù)類,和各種GetData、SetData方法;
A.定義一個(gè)枚舉類,通過(guò)這個(gè)枚舉可以自由組合數(shù)據(jù)源
/// <summary>
/// 數(shù)據(jù)源類別
/// </summary>
[Flags]
public enum DataSourceKind
{
/// <summary>
/// 本地緩存
/// </summary>
LocalCache = 1,
/// <summary>
/// 分布式緩存
/// </summary>
DistributeCache = 2,
/// <summary>
/// 數(shù)據(jù)庫(kù)
/// </summary>
DataBase = 4
}B.定義一個(gè)具體的實(shí)體類,舉例我這里定義了一個(gè)User類
public class User : IUser
{
public long UserId { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public int Sex { get; set; }
}C.實(shí)現(xiàn)一個(gè)獲取用戶信息的方法
/// <summary>
/// 獲取用戶數(shù)據(jù)
/// </summary>
/// <param name="userId">用戶Id(可以是自己相關(guān)的業(yè)務(wù)代碼)</param>
/// <param name="dataSources">數(shù)據(jù)源類型(調(diào)用方可以自己組合)</param>
/// <param name="needUpdatelocal">是否需要更新本地緩存</param>
/// <param name="needUpdateDistribute">是否需要更新分布式緩存</param>
/// <returns></returns>
public User GetUserInfo(long userId,
DataSourceKind dataSources = DataSourceKind.LocalCache ,
bool needUpdatelocal = false,
bool needUpdateDistribute = false)
{
Console.WriteLine($"======數(shù)據(jù)源:{dataSources.ToString()} 是否更新本地:{needUpdatelocal} 是否更新Redis:{needUpdateDistribute}======");
//初始化一個(gè)輸入?yún)?shù)類
var input = new DataOperateInput<User>();
//如果包含從本地緩存取值
if (dataSources.HasFlag(DataSourceKind.LocalCache))
{
input.GetDataFromLocalCache = () =>
{
//?。∵@里可以寫具體的 獲取本地緩存的處理邏輯
return GetUserFromLocalCache(userId);
};
}
//如果包含從分布式緩存取值
if (dataSources.HasFlag(DataSourceKind.DistributeCache))
{
input.GetDataFromDistributeCache = () =>
{
//??!這里可以寫具體的獲取分布式緩存的處理邏輯
return GetUserFromRedisCache(userId);
};
if (needUpdatelocal)
{
input.SetDataTolocalCache = (value) =>
{
//!!這里可以寫具體的設(shè)定本地緩存的處理邏輯
SetUserToLocalCache(value);
};
}
}
//如果包含從數(shù)據(jù)庫(kù)緩存取值
if (dataSources.HasFlag(DataSourceKind.DataBase))
{
input.GetDataFromDb = () =>
{
//!!這里可以寫具體的獲取數(shù)據(jù)庫(kù)數(shù)據(jù)的處理邏輯
return GetUserFromDB(userId);
};
if (needUpdateDistribute)
{
//!!這里可以寫具體的設(shè)定分布式緩存的處理邏輯
input.SetDataToDistributeCache = (value) =>
{
SetUserToRedisCache(value);
};
}
if (needUpdatelocal)
{
//??!這里可以寫具體的設(shè)定本地緩存的處理邏輯
input.SetDataTolocalCache = (value) =>
{
SetUserToLocalCache(value);
};
}
}
//執(zhí)行我們組合好的input
var result = new DataOperate().GetData(input);
Console.WriteLine("=============================================\n");
return result;
}上面的代碼描述了使用封裝好的GetData的方法的使用,其中有些委托的方法是需要具體實(shí)現(xiàn)的,這里我沒(méi)有詳細(xì)寫。下面列出用于測(cè)試的GetUserFromLocalCache、GetUserFromRedisCache、GetUserFromDB、SetUserToLocalCache以及SetUserToRedisCache的代碼。
/// <summary>
/// 從本地緩存獲取用戶信息
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
private User GetUserFromLocalCache(long userId)
{
User user = null;
if (userId == 1 )
{
user = new User
{
UserId = userId,
Age = 10,
Name = $"BigOrange_{userId}",
Sex = 1
};
}
if (user == null)
{
Console.WriteLine($"從本地緩存取值 未查詢到 UserId={userId}");
}
else
{
Console.WriteLine($"從本地緩存取值 UserId={user.UserId} Name={user.Name} ");
}
return user;
}
/// <summary>
/// 從Redis緩存獲取用戶信息
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
private User GetUserFromRedisCache(long userId )
{
User user = null;
if (userId == 1 || userId == 2 )
{
user = new User
{
UserId = userId,
Age = 10,
Name = $"BigOrange_{userId}",
Sex = 1
};
}
if (user == null)
{
Console.WriteLine($"從Redis緩存取值 未查詢到 UserId={userId}");
}
else
{
Console.WriteLine($"從Redis緩存取值 UserId={user.UserId} Name={user.Name}");
}
return user;
}
/// <summary>
/// 從DB獲取用戶信息
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
private User GetUserFromDB(long userId)
{
Console.WriteLine("從數(shù)據(jù)庫(kù)中取值");
User user = null;
if (userId == 1 || userId == 2 || userId == 3)
{
user = new User
{
UserId = userId,
Age = 10,
Name = $"BigOrange_{userId}",
Sex = 1
};
}
if (user == null)
{
Console.WriteLine($"從DB取值 未查詢到 UserId={userId}");
}
else
{
Console.WriteLine($"從DB取值 UserId={user.UserId} Name={user.Name}");
}
return user;
}
/// <summary>
/// 設(shè)置用戶信息到本地緩存
/// </summary>
/// <param name="userInfo"></param>
/// <returns></returns>
private bool SetUserToLocalCache(User userInfo)
{
Console.WriteLine($"設(shè)置值到本地緩存:useId = {userInfo.UserId}");
return true;
}
/// <summary>
/// 設(shè)置用戶信息到Redis緩存
/// </summary>
/// <param name="userInfo"></param>
/// <returns></returns>
private bool SetUserToRedisCache(User userInfo)
{
Console.WriteLine($"設(shè)置值到Redis緩存:useId = {userInfo.UserId}");
return true;
}④測(cè)試一下
根據(jù)上面的代碼,寫了一些測(cè)試用的條目:
static void Main(string[] args)
{
var userInfoService = new UserInfoService();
/*
* 測(cè)試用例
數(shù)據(jù)庫(kù)中存在 User1、User2、User3
分布式緩存 User1、User2
本地緩存 User1
*/
//1.只從本地緩存取值
userInfoService.GetUserInfo(1, DataSourceKind.LocalCache);
userInfoService.GetUserInfo(2, DataSourceKind.LocalCache);
//2.只從Redis緩存取值
userInfoService.GetUserInfo(2, DataSourceKind.DistributeCache);
userInfoService.GetUserInfo(3, DataSourceKind.DistributeCache);
//3.只從DB取值
userInfoService.GetUserInfo(3, DataSourceKind.DataBase);
userInfoService.GetUserInfo(4, DataSourceKind.DataBase);
//4.從本地緩存和Redis取值
userInfoService.GetUserInfo(1, DataSourceKind.LocalCache | DataSourceKind.DistributeCache);
//不更新到本地
userInfoService.GetUserInfo(2, DataSourceKind.LocalCache | DataSourceKind.DistributeCache, false);
//更新到本地
userInfoService.GetUserInfo(2, DataSourceKind.LocalCache | DataSourceKind.DistributeCache, true);
//5.從Redis和DB取值
userInfoService.GetUserInfo(2, DataSourceKind.DistributeCache | DataSourceKind.DataBase);
userInfoService.GetUserInfo(3, DataSourceKind.DistributeCache | DataSourceKind.DataBase, false, false);
userInfoService.GetUserInfo(3, DataSourceKind.DistributeCache | DataSourceKind.DataBase, false, true);
//6.從本地和DB取值
userInfoService.GetUserInfo(1, DataSourceKind.LocalCache | DataSourceKind.DataBase);
userInfoService.GetUserInfo(3, DataSourceKind.LocalCache | DataSourceKind.DataBase, false,false);
userInfoService.GetUserInfo(3, DataSourceKind.LocalCache | DataSourceKind.DataBase, true, false);
//7.三者都使用
userInfoService.GetUserInfo(1, DataSourceKind.LocalCache | DataSourceKind.DistributeCache | DataSourceKind.DataBase,false,false);
userInfoService.GetUserInfo(2, DataSourceKind.LocalCache | DataSourceKind.DistributeCache | DataSourceKind.DataBase,false,false);
userInfoService.GetUserInfo(2, DataSourceKind.LocalCache | DataSourceKind.DistributeCache | DataSourceKind.DataBase, true,false);
userInfoService.GetUserInfo(3, DataSourceKind.LocalCache | DataSourceKind.DistributeCache | DataSourceKind.DataBase,false,false);
userInfoService.GetUserInfo(3, DataSourceKind.LocalCache | DataSourceKind.DistributeCache | DataSourceKind.DataBase, true, false);
userInfoService.GetUserInfo(3, DataSourceKind.LocalCache | DataSourceKind.DistributeCache | DataSourceKind.DataBase, false, true);
userInfoService.GetUserInfo(3, DataSourceKind.LocalCache | DataSourceKind.DistributeCache | DataSourceKind.DataBase, true,true);
Console.ReadKey();
}執(zhí)行結(jié)果:
======數(shù)據(jù)源:LocalCache 是否更新本地:False 是否更新Redis:False======
從本地緩存取值 UserId=1 Name=BigOrange_1
===================================================數(shù)據(jù)源:LocalCache 是否更新本地:False 是否更新Redis:False======
從本地緩存取值 未查詢到 UserId=2
===================================================數(shù)據(jù)源:DistributeCache 是否更新本地:False 是否更新Redis:False======
從Redis緩存取值 UserId=2 Name=BigOrange_2
===================================================數(shù)據(jù)源:DistributeCache 是否更新本地:False 是否更新Redis:False======
從Redis緩存取值 未查詢到 UserId=3
===================================================數(shù)據(jù)源:DataBase 是否更新本地:False 是否更新Redis:False======
從DB取值 UserId=3 Name=BigOrange_3
===================================================數(shù)據(jù)源:DataBase 是否更新本地:False 是否更新Redis:False======
從DB取值 未查詢到 UserId=4
===================================================數(shù)據(jù)源:LocalCache, DistributeCache 是否更新本地:False 是否更新Redis:False======
從本地緩存取值 UserId=1 Name=BigOrange_1
===================================================數(shù)據(jù)源:LocalCache, DistributeCache 是否更新本地:False 是否更新Redis:False======
從本地緩存取值 未查詢到 UserId=2
從Redis緩存取值 UserId=2 Name=BigOrange_2
===================================================數(shù)據(jù)源:LocalCache, DistributeCache 是否更新本地:True 是否更新Redis:False======
從本地緩存取值 未查詢到 UserId=2
從Redis緩存取值 UserId=2 Name=BigOrange_2
設(shè)置值到本地緩存:useId = 2
===================================================數(shù)據(jù)源:DistributeCache, DataBase 是否更新本地:False 是否更新Redis:False======
從Redis緩存取值 UserId=2 Name=BigOrange_2
從DB取值 UserId=2 Name=BigOrange_2
===================================================數(shù)據(jù)源:DistributeCache, DataBase 是否更新本地:False 是否更新Redis:False======
從Redis緩存取值 未查詢到 UserId=3
從DB取值 UserId=3 Name=BigOrange_3
===================================================數(shù)據(jù)源:DistributeCache, DataBase 是否更新本地:False 是否更新Redis:True======
從Redis緩存取值 未查詢到 UserId=3
從DB取值 UserId=3 Name=BigOrange_3
設(shè)置值到Redis緩存:useId = 3
===================================================數(shù)據(jù)源:LocalCache, DataBase 是否更新本地:False 是否更新Redis:False======
從本地緩存取值 UserId=1 Name=BigOrange_1
===================================================數(shù)據(jù)源:LocalCache, DataBase 是否更新本地:False 是否更新Redis:False======
從本地緩存取值 未查詢到 UserId=3
從DB取值 UserId=3 Name=BigOrange_3
===================================================數(shù)據(jù)源:LocalCache, DataBase 是否更新本地:True 是否更新Redis:False======
從本地緩存取值 未查詢到 UserId=3
從DB取值 UserId=3 Name=BigOrange_3
設(shè)置值到本地緩存:useId = 3
===================================================數(shù)據(jù)源:LocalCache, DistributeCache, DataBase 是否更新本地:False 是否更新Redis:False======
從本地緩存取值 UserId=1 Name=BigOrange_1
===================================================數(shù)據(jù)源:LocalCache, DistributeCache, DataBase 是否更新本地:False 是否更新Redis:False======
從本地緩存取值 未查詢到 UserId=2
從Redis緩存取值 UserId=2 Name=BigOrange_2
從DB取值 UserId=2 Name=BigOrange_2
===================================================數(shù)據(jù)源:LocalCache, DistributeCache, DataBase 是否更新本地:True 是否更新Redis:False======
從本地緩存取值 未查詢到 UserId=2
從Redis緩存取值 UserId=2 Name=BigOrange_2
設(shè)置值到本地緩存:useId = 2
從DB取值 UserId=2 Name=BigOrange_2
設(shè)置值到本地緩存:useId = 2
===================================================數(shù)據(jù)源:LocalCache, DistributeCache, DataBase 是否更新本地:False 是否更新Redis:False======
從本地緩存取值 未查詢到 UserId=3
從Redis緩存取值 未查詢到 UserId=3
從DB取值 UserId=3 Name=BigOrange_3
===================================================數(shù)據(jù)源:LocalCache, DistributeCache, DataBase 是否更新本地:True 是否更新Redis:False======
從本地緩存取值 未查詢到 UserId=3
從Redis緩存取值 未查詢到 UserId=3
從DB取值 UserId=3 Name=BigOrange_3
設(shè)置值到本地緩存:useId = 3
===================================================數(shù)據(jù)源:LocalCache, DistributeCache, DataBase 是否更新本地:False 是否更新Redis:True======
從本地緩存取值 未查詢到 UserId=3
從Redis緩存取值 未查詢到 UserId=3
從DB取值 UserId=3 Name=BigOrange_3
設(shè)置值到Redis緩存:useId = 3
===================================================數(shù)據(jù)源:LocalCache, DistributeCache, DataBase 是否更新本地:True 是否更新Redis:True======
從本地緩存取值 未查詢到 UserId=3
從Redis緩存取值 未查詢到 UserId=3
從DB取值 UserId=3 Name=BigOrange_3
設(shè)置值到Redis緩存:useId = 3
設(shè)置值到本地緩存:useId = 3
=============================================
四、總結(jié)一下
類似上面的用戶信息,可能對(duì)于不同系統(tǒng)、不同性能要求,獲取方式會(huì)有所不同。
打個(gè)比方:對(duì)于一個(gè)后臺(tái)管理系統(tǒng),用戶信息獲取是一個(gè)低頻操作,可能只需要從數(shù)據(jù)庫(kù)中獲取,此時(shí)一般后臺(tái)系統(tǒng)不會(huì)設(shè)置本地緩存和分布式緩存,而對(duì)于一個(gè)接口系統(tǒng),可能每天有幾百萬(wàn)的訪問(wèn)量,此時(shí)如果只從數(shù)據(jù)庫(kù)獲取,很難承受,所以要利用到分布式緩存和本地緩存。層次越多那么變化和組合也就越多,但是每個(gè)實(shí)體的存取如果都各自實(shí)現(xiàn)自己的方式,又比較浪費(fèi),所以如果能抽象出一套方法,只需要告訴方法存取的方式,然后得到自己想要的數(shù)據(jù),或許這樣是比較好的方式,而具體怎么拿、怎么存,還是由調(diào)用的人去給出,這樣可以應(yīng)對(duì)復(fù)雜的規(guī)則。這也是為什么要使用這么多委托的原因,由于像上面獲取和設(shè)定User緩存的方式多種多樣,這么做可以把具體的獲取和設(shè)置緩存的操作開放給使用者。在系統(tǒng)重構(gòu)方面上,可以將一些通用的方法抽象出來(lái),相對(duì)成本較低,擴(kuò)展性好一些。
五、題外話
上面的代碼中對(duì)于更新數(shù)據(jù),沒(méi)有做線程安全處理,多個(gè)進(jìn)程去更新分布式緩存、同一進(jìn)程的多個(gè)線程去更新本地緩存,可能都需要進(jìn)行鎖操作。
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。如果你想了解更多相關(guān)內(nèi)容請(qǐng)查看下面相關(guān)鏈接
相關(guān)文章
C#實(shí)現(xiàn)ArrayList動(dòng)態(tài)數(shù)組的示例
ArrayList是一個(gè)動(dòng)態(tài)數(shù)組,可以用來(lái)存儲(chǔ)任意類型的元素,本文就來(lái)介紹一下C#實(shí)現(xiàn)ArrayList動(dòng)態(tài)數(shù)組的示例,具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12
C# IDE VS2005中的Hosting Process (vshost.exe)作用介紹
這篇文章主要介紹了C# IDE VS2005中的Hosting Process (vshost.exe)作用介紹,vshost.exe是一個(gè)宿主進(jìn)程,主要用來(lái)提高調(diào)試效率,需要的朋友可以參考下2015-01-01
C# WebApi Get請(qǐng)求方式傳遞實(shí)體參數(shù)的方法示例
這篇文章主要給大家介紹了關(guān)于C# WebApi Get請(qǐng)求方式傳遞實(shí)體參數(shù)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用C#具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
c# 兩個(gè)數(shù)組比較,將重復(fù)部分去掉,返回不重復(fù)部分的實(shí)現(xiàn)
下面小編就為大家?guī)?lái)一篇c# 兩個(gè)數(shù)組比較,將重復(fù)部分去掉,返回不重復(fù)部分的實(shí)現(xiàn)方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-12-12
分享一個(gè)C#編寫簡(jiǎn)單的聊天程序(詳細(xì)介紹)
這是一篇基于Socket進(jìn)行網(wǎng)絡(luò)編程的入門文章,我對(duì)于網(wǎng)絡(luò)編程的學(xué)習(xí)并不夠深入,這篇文章是對(duì)于自己知識(shí)的一個(gè)鞏固,同時(shí)希望能為初學(xué)的朋友提供一點(diǎn)參考。文章大體分為四個(gè)部分:程序的分析與設(shè)計(jì)、C#網(wǎng)絡(luò)編程基礎(chǔ)(篇外篇)、聊天程序的實(shí)現(xiàn)模式、程序?qū)崿F(xiàn)2015-12-12
一個(gè)可攜帶附加消息的增強(qiáng)消息框MessageBoxEx
一個(gè)可攜帶附加消息的增強(qiáng)消息框MessageBoxEx分享給大家,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04
C#用Activex實(shí)現(xiàn)Web客戶端讀取RFID功能的代碼
由于要在Web項(xiàng)目中采用RFID讀取功能,所以有必要開發(fā)Activex,一般情況下開發(fā)Activex都采用VC,VB等,但對(duì)這兩塊不是很熟悉,所以采用C#編寫Activex的方式實(shí)現(xiàn)2011-05-05

