Aoite 系列(03) - 一起來 Redis 吧!


Aoite 是一個適於任何 .Net Framework 4.0+ 項目的快速開發整體解決方案。Aoite.Data 適用於市面上大多數的數據庫提供程序,通過統一封裝,可以在日常開發中簡單便捷的操作數據庫。

趕緊加入 Aoite GitHub 的大家庭吧!!

插幾句話:開源對我來講,是一種分享。沒有人可以從中獲取金錢上的利益。每一套框架都有不足和亮點所在。Aoite 目的是讓園友多一種可嘗試的選擇。對我來說的根本目的在於讓 Aoite 真正的成為一個快速開發整體解決方案

1. 快速入門

Redis 在 .NET 上有非常多成熟的框架。Aoite.Redis 僅僅目前只是實現其中的一員。實現 Aoite.Redis 的根本目的是為了迎合 Aoite.CommandModel 模塊,並且減少對外部框架的依賴。

using(var client = new RedisClient(6379, null /* password*/))
{
    client.FlushAll();
    client.Set("Int32Value", 1);
    client.Set("StringValue", "a");
    client.Set("DoubleValue", 1.0);
    client.Set("DecimalValue", 1.0m);
    client.Set("AnonymousValue", new BinaryValue(new { A = "a" }));

    Console.WriteLine((Int32)client.Get("Int32Value"));
    Console.WriteLine((String)client.Get("StringValue"));
    Console.WriteLine((Double)client.Get("DoubleValue"));
    Console.WriteLine((Decimal)client.Get("DecimalValue"));
    Console.WriteLine(client.Get("AnonymousValue").ToModel());
}

1.1 二進制值 BinaryValue

System.BinaryValue 是一個非常有趣的類。它提供了從 decimal、Guid、string、DateTime、DateTimeOffset、TimeSpan、bool、ulong、char、uint、double、ushort、short、float、int 和 long 與 byte[] 之間的隱式轉換。

比如你可以這么寫:

BinaryValue value1 = 5;
BinaryValue value2 = "abc";

int x = value1;
string y = value2;

當然了,對於復雜的類型,就必須通過構造函數來創建。

 BinaryValue value = new BinaryValue(new { Username = "username", Passowrd = "passowrd" });
 Console.WriteLine(value.ToModel());

ToModel 還提供了一個泛型,可以執行返回值的數據類型。

不過,當遇見一個你無法預期判斷出具體類型時,你可以這么做:

static void Print<T>(T obj)
{
    BinaryValue value = BinaryValue.Create(obj);
    Console.WriteLine(value.Parse(typeof(T)));
}

然后你就可以像這樣調用:

Print("a");
Print(1);
Print(new { Username = "username", Passowrd = "passowrd" });

相信看到這里的你,應該就明白 System.BinaryValue 在 Redis 中可發揮的重大作用了。

1.2 事務

Redis 的事務不同於關系型數據庫的事務。在事務范圍內的任何命令,都只會返回一個內容“QUENED”,表示此命令已成功加入命令列表(但不代表執行一定會成功)。所以在 Redis 上的事務,我們要一種稍微奇怪的方式進行。

你看見的代碼,可能是這樣的:

using(var client = new RedisClient(6379, null /* password*/))
{
    using(var tran = client.BeginTransaction())
    {
        tran.On(tran.Set("key1", "value1"), r =>
        {
            Console.WriteLine("設置 Key1 為 value1 的結果是 {0}", r);
        });
        tran.On(tran.IncrBy("key2"), r =>
        {
            Console.WriteLine(r);
        });
        tran.Commit();
    }
    Console.WriteLine((string)client.Get("key1"));
    Console.WriteLine((string)client.Get("key2"));//- 根據 Redis 的協議,返回應該是 String 類型,而不是 Int64
}

1.3 Hash 的改進

如果有一個對象,你想存儲在 Redis 中時,是以字段進行存儲的,那就需要用到 Redis 的 HASH。

class MyModel
{
    public string Username { get; set; }
    public string Password { get; set; }
}

以下代碼將 MyModel 存儲到 Hash 中、從 Hash 獲取所有域和獲取部分域:

using(var client = new RedisClient(6379, null /* password*/))
{
    client.FlushAll();
    string key = "model001";
    client.HMSet(key, new MyModel() { Username = "admin", Password = "123456" });
    var model = client.HGetAll<MyModel>(key);
    Console.WriteLine("Model -> {0}\t{1}", model.Username, model.Password);

    Console.WriteLine("Username : {0}", client.HGet(key, "Username"));
    Console.WriteLine("Password : {0}", client.HGet(key, "Password"));
}

上面的代碼輸出:

Model -> admin  123456
Username : admin
Password : 123456

1.4 Scan Redis 的掃描

Redis 中有 SCAN、HSCAN、SSCAN 和 ZSCAN 四個掃描的命令。Aoite.Redis 針對掃描的命令進行封裝,返回一個 IEnumerable<T> 類型。掃描的過程並不是一次性全部加載。而是充分利用 yield 進行懶加載,第一次掃描至多返回 10 條數據(可以干涉需要返回的數量),再填充到 Queue 先進先出的隊列中。等到隊列中沒有數據時,再重新掃描至多 10 條數據。直至掃描的隊列為空並且下一批掃描的數據,整個掃描結束。

Scan:返回類型 IEnumerable<string>,以下代碼輸出內容: 11(1,10,11,12...18,19)。

static void Code7()
{
    using(var client = new RedisClient(6379, null /* password*/))
    {
        client.FlushAll();
        for(int i = 0; i < 20; i++)
        {
            client.Set("key" + i, "Value" + i);
        }
        Console.WriteLine(client.Scan(pattern: "key1*").Count());
    }
}

HScan:返回類型 IEnumerable<RedisFieldItem>

using(var client = new RedisClient(6379, null /* password*/))
{
    client.FlushAll();
    for(int i = 0; i < 20; i++)
    {
        client.HSet("Key", "Field" + i, "Value" + i);
    }
    Console.WriteLine("[Field]\t|\t[Value]");
    foreach(var item in client.HScan("Key", pattern: "Field1*"))
    {
        Console.WriteLine("{0}\t|\t{1}",item.Field, item.Value);
    }
}

以上代碼輸出:

[Field] |       [Value]
Field1  |       Value1
Field10 |       Value10
Field...|       Value...
Field18 |       Value18
Field19 |       Value19

SScan:返回類型 IEnumerable<BinaryValue>,以下代碼輸出內容 5

using(var client = new RedisClient(6379, null /* password*/))
{
    client.FlushAll();
    for(int i = 0; i < 20; i++)
    {
        client.SAdd("Key", "Member" + i % 5);
    }
    Console.WriteLine(client.SScan("Key").Count());
}

ZAdd:返回類型 IEnumerable<RedisScoreItem>,代碼可以參考 HScan

1.5 哪些命令還沒有被實現?

String 未實現命令

Key 未實現命令

Pub/Sub 未實現命令

  • 所有命令等找到合適的方式再去實現。

Connection

Server

隱式實現的命令

  • Transaction,事務性命令通過 IRedisClient.BeginTransaction 來隱士完成。
  • Connection
    • AUTH:初始化 RedisClient 就應該提供 Redis 的密碼,來簡化整體的流程。

3. 序列化

Aoite.Serialization 里有包含兩個部分內容。

  • 繼承 System.Serializer 的序列化器。比如已實現好的:System.Serializer.BinarySystem.Serializer.JsonSystem.Serializer.Xml,當然還有本節的主角 `System.Serializer.Quickly'。
  • 一套二進制的序列化組件 Aoite.Serialization.ObjectWriterObjectReader。這是一套針對字段級別的序列化,支持的類型非常多,Aoite.Tests 里有一個非常復雜的測試對象,想要了解它支持的類型,可以通過那個單元測試進行了解。

3.1 快速入門

我們定義一個稍微有一絲絲復雜的對象:

class Dict2 : Dictionary<string, object>
{
    public string MyProperty { get; set; }
}

然后我們可以這么序列化和反序列化:

Dict2 dict = new Dict2() { MyProperty = "Hello" };
dict.Add("1", new { A = "a" });
dict.Add("2", "haha");
dict.Add("3", 3);
dict.Add("4", new[] { 1, 2, 3, 4 });
var bytes = Serializer.Quickly.FastWriteBytes(dict);
Console.WriteLine("bytes length {0}", bytes.Length);

var dict2 = Serializer.Quickly.FastReadBytes(bytes) as Dict2;

foreach(var item in dict2)
{
    Console.WriteLine("{0}\t\t{1}", item.Key, item.Value);
    if(item.Value is Array)
    {
        foreach(var value in item.Value as Array)
        {
            Console.Write(value);
            Console.Write(",");     
        }
    }
}

3.2 序列化特殊對象

Aoite.Redis.RedisSessionState 這個特殊類,有一個數據類型為 SessionStateItemCollection 的特殊屬性,這個通過 Quickly 的序列化是沒有問題的,但是在使用時,便會拋出內存錯誤,具體什么原因我是沒有深入研究。所以,針對無法序列化的對象,特性 SerializableUsage 便排上用場。

class RedisSessionState
{
    //...
    [SerializableUsage(typeof(Serializable))]
    public SessionStateItemCollection Items { get; set; }
	//...

    class Serializable : Aoite.Serialization.ICustomSerializable
    {
        public object Deserialize(ObjectReader reader)
        {
            SessionStateItemCollection items = new SessionStateItemCollection();
            var count = reader.ReadInt32();
            for(int i = 0; i < count; i++)
            {
                var name = (string)reader.Deserialize();
                var value = reader.Deserialize();
                items[name] = value;
            }
            return items;
        }

        public void Serialize(ObjectWriter writer, object value)
        {
            var items = value as SessionStateItemCollection;
            writer.InnerWrite(items.Count);
            foreach(string name in items.Keys)
            {
                writer.Serialize(name);
                writer.Serialize(items[name]);
            }
        }
    }

}

SerializableUsage 特性指定一個類型,這樣序列化器在處理過程中,將會初始化這個類型的實例,並調用 SerializeDeserialize 方法。

這樣,理論上任何對象都可以序列化為文本。當然了,如果你想序列化 System.Windows.Forms.Form(這個還是有辦法的) 或者 System.Web.Page 這就比較難了……

4. 結束

關於 Aoite.Ioc 和 Aoite.Serialization 的簡單介紹,就到此結束了,如果你喜歡這個框架,不妨點個推薦吧!如果你非常喜歡這個框架,那請順便到Aoite GitHub Star 一下 :)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM