小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

MongoDB via Dotnet Core數(shù)據(jù)映射詳解

 頭號碼甲 2021-03-26

用好數(shù)據(jù)映射,MongoDB via Dotnet Core開發(fā)變會成一件超級快樂的事。

一、前言

MongoDB這幾年已經(jīng)成為NoSQL的頭部數(shù)據(jù)庫。

由于MongoDB free schema的特性,使得它在互聯(lián)網(wǎng)應(yīng)用方面優(yōu)于常規(guī)數(shù)據(jù)庫,成為了相當(dāng)一部分大廠的主數(shù)據(jù)選擇;而它的快速布署和開發(fā)簡單的特點,也吸引著大量小開發(fā)團(tuán)隊的支持。

關(guān)于MongoDB快速布署,我在15分鐘從零開始搭建支持10w+用戶的生產(chǎn)環(huán)境(二)里有寫,需要了可以去看看。

作為一個數(shù)據(jù)庫,基本的操作就是CRUD。MongoDB的CRUD,不使用SQL來寫,而是提供了更簡單的方式。

方式一、BsonDocument方式

BsonDocument方式,適合能熟練使用MongoDB Shell的開發(fā)者。MongoDB Driver提供了完全覆蓋Shell命令的各種方式,來處理用戶的CRUD操作。

這種方法自由度很高,可以在不需要知道完整數(shù)據(jù)集結(jié)構(gòu)的情況下,完成數(shù)據(jù)庫的CRUD操作。

方式二、數(shù)據(jù)映射方式

數(shù)據(jù)映射是最常用的一種方式。準(zhǔn)備好需要處理的數(shù)據(jù)類,直接把數(shù)據(jù)類映射到MongoDB,并對數(shù)據(jù)集進(jìn)行CRUD操作。

下面,對數(shù)據(jù)映射的各個部分,我會逐個說明。

    為了防止不提供原網(wǎng)址的轉(zhuǎn)載,特在這里加上原文鏈接:https://www.cnblogs.com/tiger-wang/p/13185605.html

二、開發(fā)環(huán)境&基礎(chǔ)工程

這個Demo的開發(fā)環(huán)境是:Mac + VS Code + Dotnet Core 3.1.2。

建立工程:

% dotnet new sln -o demo
The template "Solution File" was created successfully.
cd demo 
% dotnet new console -o demo
The template "Console Application" was created successfully.

Processing post-creation actions...
Running 'dotnet restore' on demo/demo.csproj...
  Determining projects to restore...
  Restored demo/demo/demo.csproj (in 162 ms).

Restore succeeded.
% dotnet sln add demo/demo.csproj 
Project `demo/demo.csproj` added to the solution.

建立工程完成。

下面,增加包mongodb.driver到工程:

cd demo
% dotnet add package mongodb.driver
  Determining projects to restore...
info : Adding PackageReference for package 'mongodb.driver' into project 'demo/demo/demo.csproj'.
info : Committing restore...
info : Writing assets file to disk. Path: demo/demo/obj/project.assets.json
log  : Restored /demo/demo/demo.csproj (in 6.01 sec).

項目準(zhǔn)備完成。

看一下目錄結(jié)構(gòu):

% tree .
.
├── demo
│   ├── Program.cs
│   ├── demo.csproj
│   └── obj
│       ├── demo.csproj.nuget.dgspec.json
│       ├── demo.csproj.nuget.g.props
│       ├── demo.csproj.nuget.g.targets
│       ├── project.assets.json
│       └── project.nuget.cache
└── demo.sln

mongodb.driver是MongoDB官方的數(shù)據(jù)庫SDK,從Nuget上安裝即可。

三、Demo準(zhǔn)備工作

創(chuàng)建數(shù)據(jù)映射的模型類CollectionModel.cs,現(xiàn)在是個空類,后面所有的數(shù)據(jù)映射相關(guān)內(nèi)容會在這個類進(jìn)行說明:

public class CollectionModel
{

}

并修改Program.cs,準(zhǔn)備Demo方法,以及連接數(shù)據(jù)庫:

class Program
{

    private const string MongoDBConnection = "mongodb://localhost:27031/admin";

    private static IMongoClient _client = new MongoClient(MongoDBConnection);
    private static IMongoDatabase _database = _client.GetDatabase("Test");
    private static IMongoCollection<CollectionModel> _collection = _database.GetCollection<CollectionModel>("TestCollection");

    static async Task Main(string[] args)
    
{
        await Demo();
        Console.ReadKey();
    }

    private static async Task Demo()
    
{
    }
}

四、字段映射

從上面的代碼中,我們看到,在生成Collection對象時,用到了CollectionModel

IMongoDatabase _database = _client.GetDatabase("Test");
IMongoCollection<CollectionModel> _collection = _database.GetCollection<CollectionModel>("TestCollection");

這兩行,其實就完成了一個映射的工作:把MongoDB中,Test數(shù)據(jù)庫下,TestCollection數(shù)據(jù)集(就是SQL中的數(shù)據(jù)表),映射到CollectionModel這個數(shù)據(jù)類中。換句話說,就是用CollectionModel這個類,來完成對數(shù)據(jù)集TestCollection的所有操作。

保持CollectionModel為空,我們往數(shù)據(jù)庫寫入一行數(shù)據(jù):

private static async Task Demo()
{
    CollectionModel new_item = new CollectionModel();
    await _collection.InsertOneAsync(new_item);
}

執(zhí)行,看一下寫入的數(shù)據(jù):


    "_id" : ObjectId("5ef1d8325327fd4340425ac9")
}

OK,我們已經(jīng)寫進(jìn)去一條數(shù)據(jù)了。因為映射類是空的,所以寫入的數(shù)據(jù),也只有_id一行內(nèi)容。

但是,為什么會有一個_id呢?

1. ID字段

MongoDB數(shù)據(jù)集中存放的數(shù)據(jù),稱之為文檔(Document)。每個文檔在存放時,都需要有一個ID,而這個ID的名稱,固定叫_id。

當(dāng)我們建立映射時,如果給出_id字段,則MongoDB會采用這個ID做為這個文檔的ID,如果不給出,MongoDB會自動添加一個_id字段。

例如:

public class CollectionModel
{

    public ObjectId _id { get; set; }
    public string title { get; set; }
    public string content { get; set; }
}

public class CollectionModel
{

    public string title { get; set; }
    public string content { get; set; }
}

在使用上是完全一樣的。唯一的區(qū)別是,如果映射類中不寫_id,則MongoDB自動添加_id時,會用ObjectId作為這個字段的數(shù)據(jù)類型。

ObjectId是一個全局唯一的數(shù)據(jù)。

當(dāng)然,MongoDB允許使用其它類型的數(shù)據(jù)作為ID,例如:string,intlong,GUID等,但這就需要你自己去保證這些數(shù)據(jù)不超限并且唯一。

例如,我們可以寫成:

public class CollectionModel
{

    public long _id { get; set; }
    public string title { get; set; }
    public string content { get; set; }
}

我們也可以在類中修改_id名稱為別的內(nèi)容,但需要加一個描述屬性BsonId

public class CollectionModel
{

    [BsonId]
    public ObjectId topic_id { get; set; }
    public string title { get; set; }
    public string content { get; set; }
}

這兒特別要注意:BsonId屬性會告訴映射,topic_id就是這個文檔數(shù)據(jù)的ID。MongoDB在保存時,會將這個topic_id轉(zhuǎn)成_id保存到數(shù)據(jù)集中。

在MongoDB數(shù)據(jù)集中,ID字段的名稱固定叫_id。為了代碼的閱讀方便,可以在類中改為別的名稱,但這不會影響MongoDB中存放的ID名稱。

修改Demo代碼:

private static async Task Demo()
{
    CollectionModel new_item = new CollectionModel()
    {
        title = "Demo",
        content = "Demo content",
    };
    await _collection.InsertOneAsync(new_item);
}

跑一下Demo,看看保存的結(jié)果:


    "_id" : ObjectId("5ef1e1b1bc1e18086afe3183"), 
    "title" : "Demo"
    "content" : "Demo content"
}

2. 簡單字段

就是常規(guī)的數(shù)據(jù)字段,直接寫就成。

public class CollectionModel
{

    [BsonId]
    public ObjectId topic_id { get; set; }
    public string title { get; set; }
    public string content { get; set; }
    public int favor { get; set; }
}

保存后的數(shù)據(jù):


    "_id" : ObjectId("5ef1e9caa9d16208de2962bb"), 
    "title" : "Demo"
    "content" : "Demo content"
    "favor" : NumberInt(100)
}

3. 一個的特殊的類型 - Decimal

說Decimal特殊,是因為MongoDB在早期,是不支持Decimal的。直到MongoDB v3.4開始,數(shù)據(jù)庫才正式支持Decimal。

所以,如果使用的是v3.4以后的版本,可以直接使用,而如果是以前的版本,需要用以下的方式:

[BsonRepresentation(BsonType.Double, AllowTruncation = true)]
public decimal price { get; set; }

其實就是把Decimal通過映射,轉(zhuǎn)為Double存儲。

4. 類字段

把類作為一個數(shù)據(jù)集的一個字段。這是MongoDB作為文檔NoSQL數(shù)據(jù)庫的特色。這樣可以很方便的把相關(guān)的數(shù)據(jù)組織到一條記錄中,方便展示時的查詢。

我們在項目中添加兩個類ContactAuthor

public class Contact
{

    public string mobile { get; set; }
}
public class Author
{

    public string name { get; set; }
    public List<Contact> contacts { get; set; }
}

然后,把Author加到CollectionModel中:

public class CollectionModel
{

    [BsonId]
    public ObjectId topic_id { get; set; }
    public string title { get; set; }
    public string content { get; set; }
    public int favor { get; set; }
    public Author author { get; set; }
}

嗯,開始變得有點復(fù)雜了。

完善Demo代碼:

private static async Task Demo()
{
    CollectionModel new_item = new CollectionModel()
    {
        title = "Demo",
        content = "Demo content",
        favor = 100,
        author = new Author
        {
            name = "WangPlus",
            contacts = new List<Contact>(),
        }
    };

    Contact contact_item1 = new Contact()
    {
        mobile = "13800000000",
    };
    Contact contact_item2 = new Contact()
    {
        mobile = "13811111111",
    };
    new_item.author.contacts.Add(contact_item1);
    new_item.author.contacts.Add(contact_item2);

    await _collection.InsertOneAsync(new_item);
}

保存的數(shù)據(jù)是這樣的:


    "_id" : ObjectId("5ef1e635ce129908a22dfb5e"), 
    "title" : "Demo"
    "content" : "Demo content"
    "favor" : NumberInt(100),
    "author" : {
        "name" : "WangPlus"
        "contacts" : [
            {
                "mobile" : "13800000000"
            }, 
            {
                "mobile" : "13811111111"
            }
        ]
    }
}

這樣的數(shù)據(jù)結(jié)構(gòu),用著不要太爽!

5. 枚舉字段

枚舉字段在使用時,跟類字段相似。

創(chuàng)建一個枚舉TagEnumeration

public enum TagEnumeration
{
    CSharp = 1,
    Python = 2,
}

加到CollectionModel中:

public class CollectionModel
{

    [BsonId]
    public ObjectId topic_id { get; set; }
    public string title { get; set; }
    public string content { get; set; }
    public int favor { get; set; }
    public Author author { get; set; }
    public TagEnumeration tag { get; set; }
}

修改Demo代碼:

private static async Task Demo()
{
    CollectionModel new_item = new CollectionModel()
    {
        title = "Demo",
        content = "Demo content",
        favor = 100,
        author = new Author
        {
            name = "WangPlus",
            contacts = new List<Contact>(),
        },
        tag = TagEnumeration.CSharp,
    };
    /* 后邊代碼略過 */
}

運行后看數(shù)據(jù):


    "_id" : ObjectId("5ef1eb87cbb6b109031fcc31"), 
    "title" : "Demo"
    "content" : "Demo content"
    "favor" : NumberInt(100), 
    "author" : {
        "name" : "WangPlus"
        "contacts" : [
            {
                "mobile" : "13800000000"
            }, 
            {
                "mobile" : "13811111111"
            }
        ]
    }, 
    "tag" : NumberInt(1)
}

在這里,tag保存了枚舉的值。

我們也可以保存枚舉的字符串。只要在CollectionModel中,tag聲明上加個屬性:

public class CollectionModel
{

    [BsonId]
    public ObjectId topic_id { get; set; }
    public string title { get; set; }
    public string content { get; set; }
    public int favor { get; set; }
    public Author author { get; set; }
    [BsonRepresentation(BsonType.String)]
    public TagEnumeration tag { get; set; }
}

數(shù)據(jù)會變成:


    "_id" : ObjectId("5ef1ec448f1d540919d15904"), 
    "title" : "Demo"
    "content" : "Demo content"
    "favor" : NumberInt(100), 
    "author" : {
        "name" : "WangPlus"
        "contacts" : [
            {
                "mobile" : "13800000000"
            }, 
            {
                "mobile" : "13811111111"
            }
        ]
    }, 
    "tag" : "CSharp"
}

6. 日期字段

日期字段會稍微有點坑。

這個坑其實并不源于MongoDB,而是源于C#的DateTime類。我們知道,時間根據(jù)時區(qū)不同,時間也不同。而DateTime并不準(zhǔn)確描述時區(qū)的時間。

我們先在CollectionModel中增加一個時間字段:

public class CollectionModel
{

    [BsonId]
    public ObjectId topic_id { get; set; }
    public string title { get; set; }
    public string content { get; set; }
    public int favor { get; set; }
    public Author author { get; set; }
    [BsonRepresentation(BsonType.String)]
    public TagEnumeration tag { get; set; }
    public DateTime post_time { get; set; }
}

修改Demo:

private static async Task Demo()
{
    CollectionModel new_item = new CollectionModel()
    {
        /* 前邊代碼略過 */
        post_time = DateTime.Now, /* 2020-06-23T20:12:40.463+0000 */
    };
    /* 后邊代碼略過 */
}

運行看數(shù)據(jù):


    "_id" : ObjectId("5ef1f1b9a75023095e995d9f"), 
    "title" : "Demo"
    "content" : "Demo content"
    "favor" : NumberInt(100), 
    "author" : {
        "name" : "WangPlus"
        "contacts" : [
            {
                "mobile" : "13800000000"
            }, 
            {
                "mobile" : "13811111111"
            }
        ]
    }, 
    "tag" : "CSharp"
    "post_time" : ISODate("2020-06-23T12:12:40.463+0000")
}

對比代碼時間和數(shù)據(jù)時間,會發(fā)現(xiàn)這兩個時間差了8小時 - 正好的中國的時區(qū)時間。

MongoDB規(guī)定,在數(shù)據(jù)集中存儲時間時,只會保存UTC時間。

如果只是保存(像上邊這樣),或者查詢時使用時間作為條件(例如查詢post_time < DateTime.Now的數(shù)據(jù))時,是可以使用的,不會出現(xiàn)問題。

但是,如果是查詢結(jié)果中有時間字段,那這個字段,會被DateTime默認(rèn)設(shè)置為DateTimeKind.Unspecified類型。而這個類型,是無時區(qū)信息的,輸出顯示時,會造成混亂。

為了避免這種情況,在進(jìn)行時間字段的映射時,需要加上屬性:

[BsonDateTimeOptions(Kind = DateTimeKind.Local)]
public DateTime post_time { get; set; }

這樣做,會強(qiáng)制DateTime類型的字段為DateTimeKind.Local類型。這時候,從顯示到使用就正確了。

但是,別高興的太早,這兒還有一個但是。

這個但是是這樣的:數(shù)據(jù)集中存放的是UTC時間,跟我們正常的時間有8小時時差,如果我們需要按日統(tǒng)計,比方每天的銷售額/點擊量,怎么搞?上面的方式,解決不了。

當(dāng)然,基于MongoDB自由的字段處理,可以把需要統(tǒng)計的字段,按年月日時分秒拆開存放,像下面這樣的:

class Post_Time
{

    public int year { get; set; }
    public int month { get; set; }
    public int day { get; set; }
    public int hour { get; set; }
    public int minute { get; set; }
    public int second { get; set; }
}

能解決,但是Low哭了有沒有?

下面,終極方案來了。它就是:改寫MongoDB中對于DateTime字段的序列化類。當(dāng)當(dāng)當(dāng)~~~

先創(chuàng)建一個類MyDateTimeSerializer

public class MyDateTimeSerializer : DateTimeSerializer
{
    public override DateTime Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
    
{
        var obj = base.Deserialize(context, args);
        return new DateTime(obj.Ticks, DateTimeKind.Unspecified);
    }
    public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, DateTime value)
    
{
        var utcValue = new DateTime(value.Ticks, DateTimeKind.Utc);
        base.Serialize(context, args, utcValue);
    }
}

代碼簡單,一看就懂。

注意,使用這個方法,上邊那個對于時間加的屬性[BsonDateTimeOptions(Kind = DateTimeKind.Local)]一定不要添加,要不然就等著哭吧:P

創(chuàng)建完了,怎么用?

如果你只想對某個特定映射的特定字段使用,比方只對CollectionModelpost_time字段來使用,可以這么寫:

[BsonSerializer(typeof(MyDateTimeSerializer))]
public DateTime post_time { get; set; }

或者全局使用:

BsonSerializer.RegisterSerializer(typeof(DateTime), new MongoDBDateTimeSerializer());

BsonSerializer是MongoDB.Driver的全局對象。所以這個代碼,可以放到使用數(shù)據(jù)庫前的任何地方。例如在Demo中,我放在Main里了:

static async Task Main(string[] args)
{
    BsonSerializer.RegisterSerializer(typeof(DateTime), new MyDateTimeSerializer());

    await Demo();
    Console.ReadKey();
}

這回看數(shù)據(jù),數(shù)據(jù)集中的post_time跟當(dāng)前時間顯示完全一樣了,你統(tǒng)計,你分組,可以隨便霍霍了。

7. Dictionary字段

這個需求很奇怪。我們希望在一個Key-Value的文檔中,保存一個Key-Value的數(shù)據(jù)。但這個需求又是真實存在的,比方保存一個用戶的標(biāo)簽和標(biāo)簽對應(yīng)的命中次數(shù)。

數(shù)據(jù)聲明很簡單:

public Dictionary<stringint> extra_info { get; set; }

MongoDB定義了三種保存屬性:Document、ArrayOfDocumentsArrayOfArrays,默認(rèn)是Document。

屬性寫法是這樣的:

[BsonDictionaryOptions(DictionaryRepresentation.ArrayOfDocuments)]
public Dictionary<stringint> extra_info { get; set; }

這三種屬性下,保存在數(shù)據(jù)集中的數(shù)據(jù)結(jié)構(gòu)有區(qū)別。

DictionaryRepresentation.Document


    "extra_info" : {
        "type" : NumberInt(1), 
        "mode" : NumberInt(2)
    }
}

DictionaryRepresentation.ArrayOfDocuments


    "extra_info" : [
        {
            "k" : "type"
            "v" : NumberInt(1)
        }, 
        {
            "k" : "mode"
            "v" : NumberInt(2)
        }
    ]
}

DictionaryRepresentation.ArrayOfArrays


    "extra_info" : [
        [
            "type"
            NumberInt(1)
        ], 
        [
            "mode"
            NumberInt(2)
        ]
    ]
}

這三種方式,從數(shù)據(jù)保存上并沒有什么區(qū)別,但從查詢來講,如果這個字段需要進(jìn)行查詢,那三種方式區(qū)別很大。

如果采用BsonDocument方式查詢,DictionaryRepresentation.Document無疑是寫著最方便的。

如果用Builder方式查詢,DictionaryRepresentation.ArrayOfDocuments是最容易寫的。

DictionaryRepresentation.ArrayOfArrays就算了。數(shù)組套數(shù)組,查詢條件寫死人。

我自己在使用時,多數(shù)情況用DictionaryRepresentation.ArrayOfDocuments

五、其它映射屬性

上一章介紹了數(shù)據(jù)映射的完整內(nèi)容。除了這些內(nèi)容,MongoDB還給出了一些映射屬性,供大家看心情使用。

1. BsonElement屬性

這個屬性是用來改數(shù)據(jù)集中的字段名稱用的。

看代碼:

[BsonElement("pt")]
public DateTime post_time { get; set; }

在不加BsonElement的情況下,通過數(shù)據(jù)映射寫到數(shù)據(jù)集中的文檔,字段名就是變量名,上面這個例子,字段名就是post_time。

加上BsonElement后,數(shù)據(jù)集中的字段名會變?yōu)?code style="font-size: inherit; line-height: inherit; word-wrap: break-word; padding: 2px 4px; border-radius: 4px; margin: 0 2px; color: rgba(233, 105, 0, 1); background-color: rgba(248, 248, 248, 1)">pt。

2. BsonDefaultValue屬性

看名稱就知道,這是用來設(shè)置字段的默認(rèn)值的。

看代碼:

[BsonDefaultValue("This is a default title")]
public string title { get; set; }

當(dāng)寫入的時候,如果映射中不傳入值,則數(shù)據(jù)庫會把這個默認(rèn)值存到數(shù)據(jù)集中。

3. BsonRepresentation屬性

這個屬性是用來在映射類中的數(shù)據(jù)類型和數(shù)據(jù)集中的數(shù)據(jù)類型做轉(zhuǎn)換的。

看代碼:

[BsonRepresentation(BsonType.String)]
public int favor { get; set; }

這段代表表示,在映射類中,favor字段是int類型的,而存到數(shù)據(jù)集中,會保存為string類型。

前邊Decimal轉(zhuǎn)換和枚舉轉(zhuǎn)換,就是用的這個屬性。

4. BsonIgnore屬性

這個屬性用來忽略某些字段。忽略的意思是:映射類中某些字段,不希望被保存到數(shù)據(jù)集中。

看代碼:

[BsonIgnore]
public string ignore_string { get; set; }

這樣,在保存數(shù)據(jù)時,字段ignore_string就不會被保存到數(shù)據(jù)集中。

六、總結(jié)

數(shù)據(jù)映射本身沒什么新鮮的內(nèi)容,但在MongoDB中,如果用好了映射,開發(fā)過程從效率到爽的程度,都不是SQL可以相比的。正所謂:

一入Mongo深似海,從此SQL是路人。

謝謝大家!

(全文完)

本文的配套代碼在https://github.com/humornif/Demo-Code/tree/master/0015/demo




本文版權(quán)歸作者所有,轉(zhuǎn)載請保留此聲明和原文鏈接

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多