MongoDB C# 驱动教程 - 微笑点燃希望 - 博客园

mikel阅读(1128)

来源: MongoDB C# 驱动教程 – 微笑点燃希望 – 博客园

C# 驱动版本 v1.6.x

本教程基于C#驱动 v1.6.x 。 Api 文档见此处: http://api.mongodb.org/csharp/current/.

简介

本教程介绍由10gen支持的,用于MongoDB的C#驱动。C# 驱动由两个类库组成:BSON Library和C# Driver。 BSON Library 可以独立于 C# Driver 使用。 C# Driver 则必须需要 BSON Library。

你还可能对 C# 驱动序列化教程 感兴趣。它是一个另外的教程因为它涵盖了很多资料。

下载

C# 驱动既有源代码也有二进制文件。BSON Library 和 C# Driver 都存在同一个知识库里,而BSON Library可以独立使用。

原文件可以从 github.com 进行下载。

我们使用 msysgit 作为我们的 Windows git 客户端。可以到这里进行下载: http://msysgit.github.com/.

要复制知识库的话,从git bash shell里运行以下命令:

$ cd <parentdirectory>
$ git config --global core.autocrlf true
$ git clone git://github.com/mongodb/mongo-csharp-driver.git
$ cd mongo-csharp-driver
$ git config core.autocrlf true

复制知识库之前,必须将core.autocrlf的全局设置设为true。当复制完毕后,我们建议你将core.autocrlf的本地设置设为true(如上所示),这样将来core.autocrlf的全局设置更改了也不会影响到这个知识库。如果你到时候想把全局设置的core.autocrlf改为false,则运行:

$ git config --global core.autocrlf false

core.autocrlf设置的典型问题是git 报告整个文件都被修改了(由于行结尾的差异)。在知识库创建后更改core.autocrlf的设置是相当没劲的,所以在开始时就设好它是很重要的。

你可以通过点击以下链接的Downloads按钮来下载源文件的 zip 文件 (不用复制知识库):

http://github.com/mongodb/mongo-csharp-driver

你可以在以下链接下载二进制文件(.msi 和 .zip 两种格式) :

http://github.com/mongodb/mongo-csharp-driver/downloads

生成

目前我们使用 Visual Studio 2010 来生成C# 驱动。解决方案的名称是 CSharpDriver-2010.sln.

依赖项

单元测试依赖 NUnit 2.5.9,它已包含在知识库的依赖项文件夹中。你可以不用安装NUnit就生成C#驱动,不过要运行单元测试则必须安装NUnit(除非你用别的测试运行器)

运行单元测试

有三个工程包含单元测试:

1. BsonUnitTests
2. DriverUnitTests
3. DriverUnitTestsVB

BsonUnitTests 不连接 MongoDB 服务端。DriverUnitTests 和 DriverUnitTestsVB 连接一个运行在localhost上默认端口的MongoDB实例。

运行单元测试的一个简单方法是将其中一个单元测试工程设为启动项目并遵照以下说明配置工程(用 BsonUnitTests 做例子):

  • 在”调试“页签里:
    1. 将”启动操作“设为”启动外部程序“
    2. 将外部程序设为: C:\Program Files (x86)\NUnit 2.5.9\bin\net-2.0\nunit.exe
    3. 将“命令行参数”设为: BsonUnitTests.csproj /config:Debug /run
    4. 将“工作目录”设为:  BsonUnitTest.csproj 所在的目录

如果还想为单元测试运行在Release模式的话,为Release配置重复以上步骤 (使用 /config:Release 代替) 。

nunit.exe的实际路径可能根据你的机器有轻微不同。

要运行 DriverUnitTests 和 DriverUnitTestsVB 执行相同的步骤 (如有必要适当修改).

安装

如果你想安装C#驱动到你的机器上,你可以用安装程序 (见上面的下载说明)。安装程序很简单,只需把DLL复制到指定安装目录即可。

如果你下载了二进制zip文件,只需简单地解压文件并把它们放到任意地方。

注意:如果你下载的是.zip 文件,Windows 可能要你 “解除锁定” 帮助文件。当你双击CSharpDriverDocs.chm文件时,如果 Windows 问你 “是否要打开此文件?” ,将“每次打开此文件时都询问”旁的复选框勾掉,然后再点击“打开”按钮。或者还可以在 CSharpDriverDocs.chm 文件上右键,选择“属性”,然后在“常规”页签的顶部点击“解除锁定”按钮。如果“解除锁定”按钮没显示的话,就没必要解除锁定了。

引用和命名空间

要使用 C# 驱动,需要添加以下DLL引用:

  1. MongoDB.Bson.dll
  2. MongoDB.Driver.dll

至少要在你的源文件里加上以下using语句:

using MongoDB.Bson;
using MongoDB.Driver;

另外还可能经常用到以下using语句:

using MongoDB.Driver.Builders;
using MongoDB.Driver.GridFS;
using MongoDB.Driver.Linq;

在某些情况下如果你要使用C#驱动的某些可选部分的话,还可能用上以下某些using语句:

using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson.Serialization.Conventions;
using MongoDB.Bson.Serialization.IdGenerators;
using MongoDB.Bson.Serialization.Options;
using MongoDB.Bson.Serialization.Serializers;
using MongoDB.Driver.Wrappers;

BSON Library

C# 驱动在 BSON Library之上创建的。BSON Library处理BSON格式的所有细节,包括:I/O,序列化以及BSON文档的内存中对象模型。

BSON对象模型的重要类有: BsonType, BsonValue, BsonElement, BsonDocument 和 BsonArray.

BsonType

这个枚举用来指定BSON值得类型,定义为:

public enum BsonType {
    Double = 0x01,
    String = 0x02,
    Document = 0x03,
    Array = 0x04,
    Binary = 0x05,
    Undefined = 0x06,
    ObjectId = 0x07,
    Boolean = 0x08,
    DateTime = 0x09,
    Null = 0x0a,
    RegularExpression = 0x0b,
    JavaScript = 0x0d,
    Symbol = 0x0e,
    JavaScriptWithScope = 0x0f,
    Int32 = 0x10,
    Timestamp = 0x11,
    Int64 = 0x12,
    MinKey = 0xff,
    MaxKey = 0x7f
}

BsonValue 及其子类

BsonValue 是一个抽象类,表示一个BSON类型的值。BsonType枚举中定义的每一个值都对应了一个具体的BsonValue子类。要获取一个BsonValue实例有几种方法:

  • 使用一个BsonValue的子类的公共构造函数 (如果有的话)
  • 使用BsonValue的静态 Create 方法
  • 使用BsonValue的子类的静态 Create 方法
  • 使用BsonValue的子类的静态属性
  • 使用隐式转换到 BsonValue

使用静态 Create 方法的好处是它们可以为经常使用的值返回预创建实例。它们还可以返回null(构造函数却不能),当使用函数式构造创建BsonDocument处理可选元素时将很有用。静态属性引用经常使用的值的预创建实例。隐式转换允许你使用原生的.NET值,不管BsonValue是否需要,而且.NET值会自动转换为BsonValue。

BsonType 属性

BsonValue 有一个名为 BsonType 的属性,可以用来查询BsonValue的实际类型。以下例子演示判断BsonValue类型的几种方法:

BsonValue value;
if (value.BsonType == BsonType.Int32) {
    // 知道值是BsonInt32的实例
}
if (value is BsonInt32) {
    // 另一种知道值是BsonInt32的方法
}
if (value.IsInt32) {
    // 最简单的知道值是BsonInt32的方法
}

As[Type] 属性

BsonValue 有大量将BsonValue投射(cast)成它的子类或原生.NET类型的属性。注意很重要的一点是,这些属性都只是投射(cast),而不是转换(conversion)。如果BsonValue不是对应的类型的话,将抛出一个 InvalidCastException 异常。 参见 To[Type] 方法,它进行的就是转换操作,以及 Is[Type] 属性,你可以在尝试使用其中一个As[Type]属性前用它查询BsonValue的类型。

BsonDocument document;
string name = document["name"].AsString;
int age = document["age"].AsInt32;
BsonDocument address = document["address"].AsBsonDocument;
string zip = address["zip"].AsString;

Is[Type] 属性

BsonValue 有以下布尔属性,用来测试它是什么类型的BsonValue。这些可以如下使用:

BsonDocument document;
int age = -1;
if (document.Contains["age"] && document["age"].IsInt32) {
    age = document["age"].AsInt32;
}

To[Type] 转换方法

不像 As[Type] 方法,To[Type] 方法在可转换类型间,如int和double,执行同样的有限转换。

ToBoolean 方法从不失败。它使用 JavaScript 的似真非真定义: false, 0, 0.0, NaN, BsonNull, BsonUndefined 和 “” 都是false, 而其余的都是 true (包括字符串 “false”).

ToBoolean 方法当处理的文档可能对记录true/false值时有不确定方法时就显得特别有用:

if (employee["ismanager"].ToBoolean()) {
    // we know the employee is a manager
    // works with many ways of recording boolean values
}

ToDoubleToInt32, 和 ToInt64 方法当在数字类型间转换时从不失败,但是值如果不适合目标类型的话可能会被截断。字符串可以转换为数字类型,但如果字符串不能解析为目标类型的值的话将抛出异常。

静态Create方法

由于 BsonValue 是一个抽象类,你不能创建BsonValue的实例(只能创建具体子类的实例)。 BsonValue 有一个静态 Create 方法,参数只有一个,是object类型的,并在运行时确定要创建的BsonValue的实际类型。BsonValue的子类也有静态的 Create 方法,由它们的需求而定制。

隐式转换

隐式转换是从以下.NET类型到BsonValue定义的:

  • bool
  • byte[]
  • DateTime
  • double
  • Enum
  • Guid
  • int
  • long
  • ObjectId
  • Regex
  • string

这就不用再调用BsonValue构造函数或者Create方法了。例如:

BsonValue b = true; // b是一个 BsonBoolean 的实例
BsonValue d = 3.14159; // d 是一个 BsonDouble 的实例
BsonValue i = 1; // i 是一个 BsonInt32 的实例
BsonValue s = "Hello"; // s 是一个 BsonString 的实例

BsonMaxKey, BsonMinKey, BsonNull 和 BsonUndefined

这几个类都是单例的,所以每个类只有一个实例存在。要引用这些实例,使用每个类的静态 Value 属性:

document["status"] = BsonNull.Value;
document["priority"] = BsonMaxKey.Value;

注意 C# 的 null 和 BsonNull.Value 是两个不同的东西。后者其实是一个 C# 对象,表示一个 BSON null 值 (这是很细微的区别,但在功能结构里却扮演很重要的角色).

ObjectId 和 BsonObjectId

ObjectId 是一个结构体,维持一个BSON ObjectId的原始值。 BsonObjectId 是 BsonValue 的一个子类,其 Value 属性是ObjectId类型。

这里是一些创建ObjectId值的常用方法:

var id1 = new ObjectId(); // 和 ObjectId.Empty 一样
var id2 = ObjectId.Empty; // 全是0
var id3 = ObjectId.GenerateNewId(); // 生成新的唯一Id
var id4 = ObjectId.Parse("4dad901291c2949e7a5b6aa8"); // 解析24位十六进制数字串

注意第一个例子C#与JavaScript有不同的表现。在C#里它创建了一个全是0的 ObjectId ,不过在 JavaScript 里它生成一个新的唯一Id。这个差异无法避免,因为在C#里值类型的构造函数总是把值初始化为全0。

BsonElement

BsonElement 是一个键值对,值是一个 BsonValue。它好比BsonDocument的积木,由0或者更多的元素组成。一般很少直接创建 BsonElements ,因为它们通常是不需要直接创建的。例如:

document.Add(new BsonElement("age", 21)); // 没问题,但下一行更简短
document.Add("age", 21); // 自动创建BsonElement

BsonDocument

BsonDocument 是键值对(BsonElement)的集合 。 它是BSON文档的内存中对象模型。有三种方法创建并填充 BsonDocument:

  1. 创建一个新的文档并调用Add和Set方法
  2. 创建一个新的文档并连续调用Add和Set方法
  3. 创建一个新的文档并使用C#的集合初始化语法(推荐)

BsonDocument 构造函数

BsonDocument 有以下构造函数:

  • BsonDocument()
  • BsonDocument(string name, BsonValue value)
  • BsonDocument(BsonElement element)
  • BsonDocument(Dictionary<string, object> dictionary)
  • BsonDocument(Dictionary<string, object> dictionary, IEnumerable<string> keys)
  • BsonDocument(IDictionary dictionary)
  • BsonDocument(IDictionary dictionary, IEnumerable<string> keys)
  • BsonDocument(IDictionary<string, object> dictionary)
  • BsonDocument(IDictionary<string, object> dictionary, IEnumerable<string> keys)
  • BsonDocument(IEnumerabe<BsonElement> elements)
  • BsonDocument(params BsonElement[] elements)
  • BsonDocument(bool allowDuplicateNames)

前两个是最可能用到的。第一个创建一个空的文档,第二个以一个元素创建文档(这两种情况当然都可以添加更多元素)。

所有构造函数(除了那个有allowDuplicateNames参数的之外)都简单地调用了同参数的Add方法,所以参考相应的Add方法以了解新文档是如何初始化填充的。

BsonDocument 一般不允许有重复的名称,但如果你想有重复的名称的话,就调用带allowDuplicateNames参数的构造函数并传入true。不建议你使用重复的名称,此选项只是为了处理已有的可能存在重复名称的BSON文档。 MongoDB 不保证是否支持带重复名称的文档,所以发送此类文档到服务器是务必小心。

创建新文档并调用Add和Set方法

这是传统的用多行C#语句一步步创建并填充文档的方法。例如:

BsonDocument book = new BsonDocument();
book.Add("author", "Ernest Hemingway");
book.Add("title", "For Whom the Bell Tolls");

创建新文档并连续使用Add和Set方法

这个和前一个方法很类似,不过连续调用Add方法只需一行C#语句即可。例如:

BsonDocument book = new BsonDocument()
    .Add("author", "Ernest Hemingway")
    .Add("title", "For Whom the Bell Tolls");

创建新文档并使用C#集合初始化语法(推荐使用)

这是在一行语句中创建和初始化BsonDocument的推荐方法。它使用C#集合初始化语法:

BsonDocument book = new BsonDocument {
    { "author", "Ernest Hemingway" },
    { "title", "For Whom the Bell Tolls" }
};

编译器将这行代码翻译为与之匹配的Add方法:

    BsonDocument book = new BsonDocument();
    book.Add("author", "Ernest Hemingway");
    book.Add("title", "For Whom the Bell Tolls");

常见的错误时忘了写里面的花括弧。这会导致一个编译错误。例如:

BsonDocument bad = new BsonDocument {
    "author", "Ernest Hemingway"
};

会被编译器翻译成:

BsonDocument bad = new BsonDocument();
bad.Add("author");
bad.Add("Ernest Hemingway");

会导致编译错误因为没有Add方法是只接受一个string参数的。

创建嵌套的 BSON 文档

嵌套 BSON 文档通过设置元素的值为BSON文档来进行创建。例如:

BsonDocument nested = new BsonDocument {
    { "name", "John Doe" },
    { "address", new BsonDocument {
        { "street", "123 Main St." },
        { "city", "Centerville" },
        { "state", "PA" },
        { "zip", 12345}
    }}
};

这个创建了一个顶级文档,有两个元素 (“name” 和 “address”)。  “address” 的值是一个嵌套的 BSON 文档。

Add 方法

BsonDocument 有如下重载的Add方法

  • Add(BsonElement element)
  • Add(Dictionary<string, object> dictionary)
  • Add(Dictionary<string, object> dictionary, IEnumerable<string> keys)
  • Add(IDictionary dictionary)
  • Add(IDictionary dictionary, IEnumerable<string> keys)
  • Add(IDictionary<string, object> dictionary)
  • Add(IDictionary<string, object> dictionary, IEnumerable<string> keys)
  • Add(IEnumerable<BsonElement> elements)
  • Add(string name, BsonValue value)
  • Add(string name, BsonValue value, bool condition)

要注意的很重要的一点是有时候Add方法不会添加新元素。如果提供的值是null(或者最后一个重载中提供的condition是false)的话那元素就不会被添加。这在处理可选元素时就不用写任何if语句或者条件表达式了。

例如:

BsonDocument document = new BsonDocument {
    { "name", name },
    { "city", city }, // 如果city是null就不添加
    { "dob", dob, dobAvailable } // 如果 dobAvailable是false则不添加
};

就比下面更简洁和可读性强:

BsonDocument document = new BsonDocument();
document.Add("name", name);
if (city != null) {
    document.Add("city", city);
}
if (dobAvailable) {
    document.Add("dob", dob);
}

如果你想在值缺失的情况下添加一个BsonNull,你可能会这么做。但更简单的方法是使用C#的??运算符:

BsonDocument = new BsonDocument {
    { "city", city ?? BsonConstants.Null }
};

IDictionary 重载从一个字典初始化一个 BsonDocument 。字典里的每一个键都变成元素的name,每一个值都映射为匹配的 BsonValue 并变成新元素的value。带keys参数的重载让你选择加载哪个字典入口(还可能用keys参数来控制从字典里加载元素的顺序)。

访问 BsonDocument 元素

访问 BsonDocument 元素的推荐做法是使用下面的索引:

  • BsonValue this[int index]
  • BsonValue this[string name]
  • BsonValue this[string name, BsonValue defaultValue]

注意索引的返回值是 BsonValue,而不是 BsonElement。这其实让 BsonDocuments 更容易使了 (如果你需要获取实际的 BsonElements ,那就用 GetElement方法).

我们已经看过访问 BsonDocument 元素的例子了。这里再来几个:

BsonDocument book;
string author = book["author"].AsString;
DateTime publicationDate = book["publicationDate"].AsDateTime;
int pages = book["pages", -1].AsInt32; // 默认值是 -1

BsonArray

这个类用来表示 BSON 数组。由于BSON文档(元素用特殊命名惯例)恰巧对外表示为数组, BsonArray 类跟 BsonDocument 类是无关的,因为它俩用起来很不一样。

构造函数

BsonArray 有以下构造函数:

  • BsonArray()
  • BsonArray(IEnumerable<bool> values)
  • BsonArray(IEnumerable<BsonValue> values)
  • BsonArray(IEnumerable<DateTime> values)
  • BsonArray(IEnumerable<double> values)
  • BsonArray(IEnumerable<int> values)
  • BsonArray(IEnumerable<long> values)
  • BsonArray(IEnumerable<ObjectId> values)
  • BsonArray(IEnumerable<string> values)
  • BsonArray(IEnumerable values)

所有带参数的构造函数都调用匹配的Add方法。由于C#不提供自动从IEnumerable<T> 到 IEnumerable<object>的转换,所以多重载是有必要的。

Add 和 AddRange 方法

BsonArray 具有以下的 Add 方法:

  • BsonArray Add(BsonValue value)
  • BsonArray AddRange(IEnumerable<bool> values)
  • BsonArray AddRange(IEnumerable<BsonValue> values)
  • BsonArray AddRange(IEnumerable<DateTime> values)
  • BsonArray AddRange(IEnumerable<double> values)
  • BsonArray AddRange(IEnumerable<int> values)
  • BsonArray AddRange(IEnumerable<long> values)
  • BsonArray AddRange(IEnumerable<ObjectId> values)
  • BsonArray AddRange(IEnumerable<string> values)
  • BsonArray AddRange(IEnumerable values)

注意Add方法只有一个参数。要创建并以多个值初始化一个 BsonArray ,请使用以下方法之一:

// 传统方法
BsonArray a1 = new BsonArray();
a1.Add(1);
a2.Add(2);

// 连续调用
BsonArray a2 = new BsonArray().Add(1).Add(2);

// values参数
int[] values = new int[] { 1, 2 };
BsonArray a3 = new BsonArray(values);

// 集合初始化语法
BsonArray a4 = new BsonArray { 1, 2 };

索引

数组元素用整型索引进行访问。比如 BsonDocument,元素的类型是 BsonValue。比如:

BsonArray array = new BsonArray { "Tom", 39 };
string name = array[0].AsString;
int age = array[1].AsInt32;

C# 驱动

知道现在我们讨论的都是 BSON 类库。剩下的我们来说一下 C# 驱动。

线程安全

只有一小部分C#驱动是线程安全的。它们是: MongoClient, MongoServer, MongoDatabase, MongoCollection 和 MongoGridFS。通常用的比较多的类都不是线程安全的,包括 MongoCursor 和所有BSON类库里的类 (除了 BsonSymbolTable 是线程安全的). 不特别标记为线程安全的都是线程非安全的。

所有类的所有静态属性和方法都是线程安全的。

MongoClient 类

这个类是操控MongoDB服务器的根对象。与服务器的连接是自动在后台处理的 (用了一个连接池来提升效率).

当连接到一个副本是,用的仍然只有一个MongoClient的实例,它代表一个完整的副本。驱动会自动找出所有副本里的成员并识别出当前的主服务器。

这个类的实例是线程安全的。

操作默认情况下,除非设置了,否则,所有的操作需要WriteConcern使用W = 1。换句话说,默认情况下,所有的写操作都会阻塞,直到服务器已经确认。

连接字符串

连接MongoDB服务器的最简单方法就是使用连接字符串。标准的连接字符串格式为:

mongodb://[username:password@]hostname[:port][/[database][?options]]

username 和 password 只有在MongoDB服务器使用了身份验证时才出现。这些凭证信息将是所有数据库的默认凭证。要验证admin数据库,在username里加上 “(admin)” 。如果要根据不同数据库使用不同的凭证,在GetDatabase方法里传入正确的凭证即可。

端口号是可选的,默认为 27017.

要连接多个服务器的话,用逗号分隔多个主机名(和端口号,如果有的话)。例如:

mongodb://server1,server2:27017,server2:27018

这个连接字段串指定了三个服务器 (其中两个在同一台机器上,但端口号不一样)。由于指定多个服务器会引起歧义,不知道到底是副本还是多个mongo(分片安装中),服务器会进入一个连接的发现阶段来确定它们的类型。这对于连接时间而言有点过头了,不过可以通过在连接字符串里指定连接模式来避免:

mongodb://server1,server2:27017,server2:27018/?connect=replicaset

可选的模式有:自动 automatic (默认), 直接 direct, 副本replica set, 和 分片路由 shardrouter。连接模式的规则如下:

1. 如果连接模式指定为自动以外的,则使用之。
2. 如果在连接字符串里指定了副本名称 (replicaset), 那么将使用副本模式。
3. 如果连接字符串里只列出了一个服务器,那么将使用直接模式。
4. 否则,将查找第一个响应的服务器,确定连接模式。

如果列出了多个服务器,而且其中一个是副本,别的不是,那么连接模式就不可确定了。确保别在连接字符串里混用服务器类型。

如果连接模式设为副本模式,驱动会去找主服务器,即使它不在字符串里列出,只要字符串里至少有一个服务器响应了(响应里将包含完整的副本和当前的主服务器名称)。另外,其它服务器也会被找到并自动添加(或移除),甚至在初始化连接之后。这样你就可以从副本里添加和移除服务器,驱动会自动处理这些变更。

正像上面所说的,连接字符串的可选部分是用来设置各种连接选项的。假设你想要直接连接到副本的一个成员不管它是不是当前主服务器(可能想监控它的状态或者仅仅读查询)。你可以这么写:

mongodb://server2/?connect=direct;readpreference=nearest

连接字符串的完整文档可以看下面的连接:

http://www.mongodb.org/display/DOCS/Connections

和:

http://docs.mongodb.org/manual/applications/replication/#replica-set-read-preference

对SSL 的支持

驱动里已经支持了SSL。可以通过在连接字符串里加上 “ssl=true” 选项来进行配置。

mongodb://server2/?ssl=true

默认情况下,服务器证书会对本地受信任证书存储进行验证。这经常会在测试服务器没有签名证书的测试环境里引起一些问题。要缓和这个问题,可以添加另一个连接字符串选项 “sslverifycertificate=false” 来忽略任何证书错误。

身份认证

MongoDB 支持简单直接的身份认证机制。你可以在 security and authentication docs page 了解更多。

C# 驱动有多种方法支持身份验证。上面提到的连接字符串,可以指定默认凭证信息。通常在没有提供其它凭证的时候都会用默认凭证。

有两种方法来提供凭证。第一种,可以在运行时通过特定方法提供。这些凭证就会被用来执行想要的功能。第二种,也是更健壮的方法,是把凭证存储在 MongoCredentialsStore 里。存在里面的 MongoCredentials 由数据库作为键值,所以如果不同的数据库需要不同的用户,那么凭证存储先去找第一个,如果没找着,就退而求其次,看连接字符串里有没有提供默认凭证,有则用之。

下面的例子使用了凭证存储来定义”foo”数据的管理员凭证。使用“admin”或者“foo”以外的凭证去访问数据将使用提供了默认凭证“test”的连接字符串。

var url = new MongoUrl("mongodb://test:user@localhost:27017");
var settings = MongoClientSettings.FromUrl(url);
var adminCredentials = new MongoCredentials("admin", "user", true);
settings.CredentialsStore.Add("admin", adminCredentials);
var fooCredentials = new MongoCredentials("foo", "user", false);
settings.CredentialsStore.Add("foo", fooCredentials);

var client = new MongoClient(settings);

GetServer 方法

要从 MongoClient 的实例取得 MongoServer 的实例可以使用 GetServer 方法。

MongoServer 类

MongoServer 类是用来对驱动进行更多的控制。包含了获取数据库和通过简单的socket发布一系列操作的高级方法,为的是保证一致性。

GetDatabase 方法

从 MongoServer 实例取得 MongoDatabase 实例(见下一节) 可以使用以下的 GetDatabase 方法或索引之一:

  • MongoDatabase GetDatabase(MongoDatabaseSettings settings)
  • MongoDatabase GetDatabase(string databaseName)
  • MongoDatabase GetDatabase(string databaseName, MongoCredentials credentials)
  • MongoDatabase GetDatabase(string databaseName, MongoCredentials credentials, WriteConcern writeConcern)
  • MongoDatabase GetDatabase(string databaseName, WriteConcern writeConcern)

样例代码:

MongoClient client = new MongoClient(); // 连接到 localhost
MongoServer server = client.GetServer();
MongoDatabase test = server.GetDatabase("test");
MongoCredentials credentials = new MongoCredentials("username", "password");
MongoDatabase salaries = server.GetDatabase("salaries", credentials);

大多数的数据设置都是从服务器对象继承来的, GetDatabase 提供的重载可以对经常使用的设置进行覆盖。要覆盖其它设置,调用 CreateDatabaseSettings 并在调用 GetDatabase 之前更改任何你想要的设置,像这样:

var databaseSettings = server.CreateDatabaseSettings("test");
databaseSettings.SlaveOk = true;
var database = server.GetDatabase(databaseSettings);

GetDatabase 维系了一个 MongoDatabase 之前返回过的 实例表,因此如果以同样的参数再次调用 GetDatabase 会再次得到相同的实例。

RequestStart/RequestDone 方法

有时候为了保证结果正确,需要在同一个连接里执行一系列操作。这比较少见,而且大多数时候没有必要去调用 RequestStart/RequestDone。有必要这么做的一个例子是在w=0的WriteConcern的快速会话中调用了一系列的Insert,然后紧接着马上查询出这些数据来(在w=0的WriteConcern下,服务器里的写操作会排队,而且可能不会马上对其它连接可见)。使用 RequestStart 可以在同一个连接里在写的时候强制查询,因此查询直到服务器捕获了写操作之后才会执行。

通过使用RequestStart 和 RequestDone,线程可以从连接池里暂时保留一个连接,例如:

using(server.RequestStart(database)) {
// 在同一个连接里需要执行一系列操作
}

database 参数只是简单地说明你要在这个请求期间要用哪些数据库。这使服务器能够对已经身份验证通过的数据库拿来就用 (如果没用身份验证那这个优化就没关系了)。在这个请求期间你可以任意地使用其它数据库了。

RequestStart (为这个线程)增加了一个计数,在完成后再减掉。保留的连接实际不是返回到连接池里,直到计数再次变为0。这说明嵌套调用 RequestStart 是没有问题的。

RequestStart 返回了一个 IDisposable。如果是在using块里用 RequestStart ,为了释放连接最好尽快调用 RequestDone 。

其它的属性和方法

参考其它的属性和方法,请参阅api文档。

MongoDatabase 类

这个类表示 MongoDB 服务器的数据库。通常每个数据库只有一个实例,除非你是用不同的设置来访问同一个数据库,这样就是每个设置都有一个实例。

这个类的实例是线程安全的。

GetCollection 方法

此方法返回一个表示数据库里集合的对象。当请求一个集合对象时,要同时制定集合的默认文档类型。例如:

MongoDatabase hr = server.GetDatabase("hr");
MongoCollection<Employee> employees =
    hr.GetCollection<Employee>("employees");

集合并不限于只有一种文档。默认的文档类型在处理那种文档时能更方便一点,但在需要时你完全可以指定另一种文档。

大多数的集合设置是从数据库继承的,GetCollection 提供的重载可以对常用的设置进行覆盖。要覆盖其它的设置,调用 CreateCollectionSettings 并在调用 GetCollection 之前更改任何你想要的设置,像这样:

var collectionSettings = database.CreateCollectionSettings<TDocument>("test");
collectionSettings.SlaveOk = true;
var collection = database.GetCollection(collectionSettings);

GetCollection 维系了之前返回过的一个实例表,因此如果以同样的参数再次调用 GetCollection 会得到同一个实例。

其它属性和方法

参考其它的属性和方法,请参阅api文档。

MongoCollection<TDefaultDocument> 类

此类表示 MongoDB 数据库里的集合。 <TDefaultDocument> 泛型参数指定了此集合默认文档的类型。

此类的实例是线程安全的。

Insert<TDocument> 方法

要在集合里插入一个文档,创建一个表示该文档的对象并调用 Insert。对象可以是BsonDocument 的实例或者是可以成功序列化为BSON文档的任何类的实例。例如:

MongoCollection<BsonDocument> books =
    database.GetCollection<BsonDocument>("books");
BsonDocument book = new BsonDocument {
    { "author", "Ernest Hemingway" },
    { "title", "For Whom the Bell Tolls" }
};
books.Insert(book);

如果有一个名为 Book 的类,代码如下:

MongoCollection<Book> books = database.GetCollection<Book>("books");
Book book = new Book {
    Author = "Ernest Hemingway",
    Title = "For Whom the Bell Tolls"
};
books.Insert(book);

InsertBatch 方法

使用InserBatch方法可以一次性插入多个文档,例如:

MongoCollection<BsonDocument> books;
BsonDocument[] batch = {
    new BsonDocument {
        { "author", "Kurt Vonnegut" },
        { "title", "Cat's Cradle" }
    },
    new BsonDocument {
        { "author", "Kurt Vonnegut" },
        { "title", "Slaughterhouse-Five" }
    }
};
books.InsertBatch(batch);

插入多个文档时,使用 InsertBatch 比 Insert 效率更高。

FindOne 和 FindOneAs 方法

要从集合里获取文档,使用Find方法之一。FindOne是最简单的一个。它返回找到的第一个文档(当有多个文档时你没法确定是哪一个)。例如:

MongoCollection<Book> books;
Book book = books.FindOne();

如果要读取一个类型不是 <TDefaultDocument> 的文档,就使用 FindOneAs 方法,可以覆盖其返回文档的类型,例如:

MongoCollection<Book> books;
BsonDocument document = books.FindOneAs<BsonDocument>();

这里集合的默认文档类型是 Book,但我们将其覆盖了,指定结果为 BsonDocument 的实例。

Find 和 FindAs 方法

Find 和 FindAs 方法通过接受一个查询,告诉服务器要返回那个文档。 query 参数是 IMongoQuery 类型的。 IMongoQuery 接口标记了类可以用来进行查询。构建查询的最常用方法是要么使用Query建造类,要么自己创建一个QueryDocument (QueryDocument 是BsonDocument 的子类,同时实现了 IMongoQuery 因此可以用作查询对象)。同时,通过使用 QueryWrapper 类,查询可以是任何能序列化为BSON文档的类型,不过这取决于你得保证序列化后的文档表示的是一个有效的查询对象。

其中一种查询方法是自己创建 QueryDocument 对象:

MongoCollection<BsonDocument> books;
var query = new QueryDocument("author", "Kurt Vonnegut");
foreach (BsonDocument book in books.Find(query)) {
    // do something with book
}

另一种方法是使用 Query Builder (推荐):

MongoCollection<BsonDocument> books;
var query = Query.EQ("author", "Kurt Vonnegut");
foreach (BsonDocument book in books.Find(query)) {
    // do something with book
}

还有另一种查询的方法是使用匿名类,不过这样我们得把匿名对象进行封装:

MongoCollection<BsonDocument> books;
var query = Query.Wrap(new { author = "Kurt Vonnegut" });
foreach (BsonDocument book in books.Find(query)) {
    // do something with book
}

如果想要读取不是默认类型的文档,则使用 FindAs 方法:

MongoCollection<BsonDocument> books;
var query = Query<Book>.EQ(b => b.Author, "Kurt Vonnegut");
foreach (Book book in books.FindAs<Book>(query)) {
    // do something with book
}

Save<TDocument> 方法

Save 方法是 Insert 和 Update的组合。如果文档的 Id 有值,那么就假定这是一个已经存在的文档,Save就会在文档上调用Update(设置Upsert标记以防止它实际上是个新文档)。否则就假定这是一个新文档,Save会在首先将新生成的唯一值设到Id上,然后调用Insert。

例如,要修正一本书的书名错误:

MongoCollection<BsonDocument> books;
var query = Query.And(
    Query.EQ("author", "Kurt Vonnegut"),
    Query.EQ("title", "Cats Craddle")
);
BsonDocument book = books.FindOne(query);
if (book != null) {
    book["title"] = "Cat's Cradle";
    books.Save(book);
}

调用Save方法的时候,TDocument 类必须要有Id。如果没有的话可以调用Insert来插入文档。

Update 方法

Update 方法用来更新已有文档。Save方法的示例代码还可以写成:

MongoCollection<BsonDocument> books;
var query = new QueryDocument {
    { "author", "Kurt Vonnegut" },
    { "title", "Cats Craddle" }
};
var update = new UpdateDocument {
    { "$set", new BsonDocument("title", "Cat's Cradle") }
};
BsonDocument updatedBook = books.Update(query, update);

或者使用 Query 和 Update builders:

MongoCollection<BsonDocument> books;
var query = Query.And(
    Query.EQ("author", "Kurt Vonnegut"),
    Query.EQ("title", "Cats Craddle")
);
var update = Update.Set("title", "Cat's Cradle");
BsonDocument updatedBook = books.Update(query, update);

FindAndModify 方法

当你想要查找一个文档并在一个原子操作里更新它时,就使用 FindAndModify。 FindAndModify 只更新一个文档,配合使用具有排序标准的多文档查询来确定到底要更新哪个文档。另外, FindAndModify 会返回符合条件的文档 (不管是在更新前还是更新后) 而且可以指定要返回文档的那些字段。

参考以下链接中的例子:

http://www.mongodb.org/display/DOCS/findAndModify+Command

对 FindAndModify 的调用如下:

var jobs = database.GetCollection("jobs");
var query = Query.And(
    Query.EQ("inprogress", false),
    Query.EQ("name", "Biz report")
);
var sortBy = SortBy.Descending("priority");
var update = Update.
    .Set("inprogress", true)
    .Set("started", DateTime.UtcNow);
var result = jobs.FindAndModify(
    query,
    sortBy,
    update,
    true // return new document
);
var chosenJob = result.ModifiedDocument;

MapReduce 方法

Map/Reduce 是从集合里汇总数据的一种方法。集合里的每一个文档(或者某些子集,如果可选查询提供了的话)都被传到map函数,该函数调用emit来产生中间值。然后中间值被传到reduce函数进行汇总。

下面的例子摘选自Kristina Chodorow 和 Michael Dirolf写的《MongoDB权威指南》第87页。它计算了集合里的每一个键值被找到了多少次。

var map =
    "function() {" +
    "    for (var key in this) {" +
    "        emit(key, { count : 1 });" +
    "    }" +
    "}";

var reduce =
    "function(key, emits) {" +
    "    total = 0;" +
    "    for (var i in emits) {" +
    "        total += emits[i].count;" +
    "    }" +
    "    return { count : total };" +
    "}";

var mr = collection.MapReduce(map, reduce);
foreach (var document in mr.GetResults()) {
    Console.WriteLine(document.ToJson());
}

其它属性和方法

参考其它的属性和方法,请参阅api文档。

MongoCursor<TDocument> 类

Find 方法(以及它的各个变种) 不是马上返回查询的实际结果。而是返回一个能获取到查询结果的可遍历的游标。查询实际上并不是传到服务器,直到尝试获取第一个结果(技术上而言,就是在由GetEnumerator返回的枚举器第一次调用MoveNext时)。这说明了我们可以在获取到结果之前以各种有趣的方式来控制查询的结果。

MongoCursor 的实例不是线程安全的,至少在它们冻结(见下面)前是不安全的。一旦它们冻结了,它们就是线程安全的了,因为它们是只读的(尤其是,GetEnumerator是线程安全的,所以同一个游标可以被多个线程使用)。

遍历游标

要使用查询结果最方便的方法就是用C#的foreach语句。例如:

var query = Query.EQ("author", "Ernest Hemingway");
var cursor = books.Find(query);
foreach (var book in cursor) {
    // do something with book
}

还可以用LINQ为IEnumerable<T>定义的扩展方法来遍历游标:

var query = Query.EQ("author", "Ernest Hemingway");
var cursor = books.Find(query);
var firstBook = cursor.FirstOrDefault();
var lastBook = cursor.LastOrDefault();
在上边的例子,查询实际上传到服务器两次(分别是 FirstOrDefault 和 LastOrDefault 调用的时候).

很重要的一点是游标将其引用的资源都释放干净了。要保证这一点的关键是确保调用了枚举器的Dispose方法。foreach语句和LINQ扩展方法都保证了Dispose会被调用。除非你手动遍历右边,那就得自己负责调用 Dispose。

遍历游标前修改它

游标有几个属性可以在遍历控制返回结果前进行修改。有两种修改游标的方法:

  1. 直接修改属性
  2. 使用平滑接口来设置属性

例如,如果想要取第101到110个结果,可以这样写:

var query = Query.EQ("status", "pending");
var cursor = tasks.Find(query);
cursor.Skip = 100;
cursor.Limit = 10;
foreach (var task in cursor) {
    // do something with task
}

或者使用平滑接口:

var query = Query.EQ("status", "pending");
foreach (var task in tasks.Find(query).SetSkip(100).SetLimit(10)) {
    // do something with task
}

平滑接口在只设置少部分值时用着很爽。当设置比较多时可能用属性方式更好一点。

一旦开始遍历游标,它就变成“冻结”,你就不能再更改任何属性了。所以要在遍历前就设置好所有的属性。

游标的可修改属性

以下是游标的可修改属性:

  • BatchSize (SetBatchSize)
  • Fields (SetFields)
  • Flags (SetFlags)
  • Limit (SetLimit)
  • Options (SetOption and SetOptions)
  • SerializationOptions (SetSerializationOptions)
  • Skip (SetSkip)
  • SlaveOk (SetSlaveOk)

括号里的方法名是对应的平滑接口方法。

平滑接口还支持额外的不常使用的选项,这些选项没有对应的属性:

  • SetHint
  • SetMax
  • SetMaxScan
  • SetMin
  • SetShowDiskLoc
  • SetSnapshot
  • SetSortOrder

其它方法

MongoCursor 有一些方法用于某些特殊操作目的:

  • Clone
  • Count
  • Explain
  • Size

WriteConcern 类

WriteConcern 有好几级,这个类就是用来表示这些级次的。 WriteConcern 只是应用在那些没有返回值的操作 (所以它不应用在查询和命令中)。它应用于这几个 MongoCollection 方法: Insert, Remove, Save 和 Update.

WriteConcern 的要领是在 Insert, Remove, Save 或者 Update之后,紧接着调用GetLastError命令将消息发送到服务器,这样驱动就可以操作成功了。另外,当使用副本时,有可能确认信息被复制到最少量的辅服务器上去。

【.net】未在本地计算机上注册“microsoft.ACE.oledb.12.0”提供程序解决办法 - willingtolove - 博客园

mikel阅读(1255)

来源: 【.net】未在本地计算机上注册“microsoft.ACE.oledb.12.0”提供程序解决办法 – willingtolove – 博客园

 

正文

#错误描述:

在开发.net项目中,通过microsoft.ACE.oledb读取excel文件信息时,报错:

  “未在本地计算机上注册“microsoft.ACE.oledb.12.0”提供程序”

#代码示例:

复制代码
 1      static void Main(string[] args)
 2         {
 3             readexcel("D:\\test\\xlsxtest.xlsx");
 4         }
 5         public static void readexcel(string _path)
 6         {
 7             DataTable dt = new DataTable();
 8             string connectionString = "Provider=Microsoft.ACE.OLEDB.12.0;" + "Data Source=" + _path + ";" + "Extended Properties=\"Excel 12.0;HDR=No\"";
 9 
10             using (OleDbConnection connection = new OleDbConnection(connectionString))
11             {
12                 string SQL = "select * from [sheet1$]";
13                 try
14                 {
15                     OleDbCommand comm = new OleDbCommand(SQL, connection);
16                     if (connection.State != ConnectionState.Open)
17                         connection.Open();
18                     OleDbDataAdapter Adpter = new OleDbDataAdapter(comm);
19                     Adpter.Fill(dt);
20                 }
21                 catch (Exception ex)
22                 {
23                     dt = null;
24                 }
25                 finally
26                 {
27                     if (connection.State == ConnectionState.Open)
28                         connection.Close();
29                 }
30 
31                 foreach (DataRow item in dt.Rows)
32                 {
33                     string sds = item[0].ToString();
34                     Console.WriteLine(item[0].ToString() + "//" + item[1].ToString() + "//" + item[2].ToString());
35                     if (item[1].ToString() == string.Empty)
36                     {
37                         break;
38                     }
39                 }
40                 Console.ReadKey();
41             }
42         }
复制代码

 

#报错原因:

主要有以下几种原因:

1、没有安装数据访问组件,需要安装相应版本的数据访问组件(AccessDatabaseEngine);

2、没有安装相应版本的Office客户端,需要安装相应版本的Office客户端;

3、没有在IIS应用程序池配置默认属性,需要在相应的IIS应用程序池启用32位应用程序;

4、连接字符串的问题。采用Microsoft.Jet.OleDb.4.0,可以读取excel2007以前的版本,在客户机上不需要部署office,采用Microsoft.Ace.OleDb.12.0的时候,需要安装引擎。

5、顺便说一下,在使用“Microsoft.Jet.OLEDB.4.0”,也会报类似错误,原因有可能是,Microsoft.Jet.OLEDB.4.0在64位系统上不支持,需要修改架构,从x64改为x86,无论是WinForm还是ASP.NET;或者修改连接字符串为Microsoft.ACE.OLEDB.12.0并且安装AccessDatabaseEngine x64数据访问组件;

#解决方案:

  1、安装数据访问组件:

  1)适用于office2007的

  Microsoft Access Database Engine 2007 Office system 驱动程序:数据连接组件
  https://download.csdn.net/download/willingtolove/12584343  (亲测没问题!xls和xlsx格式都可以成功读取!)

  2)适用于office2010的

  Microsoft Access Database Engine 2010 Redistributable
  https://www.microsoft.com/zh-CN/download/details.aspx?id=13255 (亲测没问题!注意x64还是x86!)

  2、在IIS应用程序池中,设置“”启用兼容32位应用程序”,此设置适用于web项目;

  如图:

  
注意:
在下载Microsoft Access Database Engine 2010 Redistributable时会让选择下载x86的还是x64的,如图:
前提是看服务器是x64的还是x86的,x64的服务器两个版本都能安装;

  如果下载安装的是x64的,那么你的桌面程序就要选择x64发布,而web项目是不兼容的,不管你是如何发布的;

如果下载安装的是x86的,那么你的桌面程序就要选择x86发布,而web项目选AnyCPU正常发布就好;

*总结:如果你是web项目,你就下载x86的,发布选anycpu就好了,然后设置应用程序池32位兼容就好了;

*最后说一句,用这个,是不需要安装office软件的;

—————————————————————————————————————————————————————————–

  3、连接字符串

  Provider=Microsoft.ACE.OLEDB.12.0;Data Source=c:\\myFolder\\myExcel2007file.xlsx;
  Extended Properties="Excel 12.0 Xml;HDR=YES";
    “HDR =Yes;” 表示第一行包含列名,而不是数据。“HDR =No;” 表明相反;
  Microsoft.ACE.OLEDB连接字符串参考地址:https://www.connectionstrings.com/ace-oledb-12-0/

Angular 之 ngStrictDi_大V的博客-CSDN博客

mikel阅读(582)

来源: Angular 之 ngStrictDi_大V的博客-CSDN博客

ngStrictDi主要是讲angular依赖注入的写法问题,如果写法不规范,可能会在加载压缩版Angular的时候导致Unknown provider的错误。

  1. var app = angular.module(‘ngAppStrictDemo’, []);
  2. 1. 不规范写法
  3. app.controller(‘BadController’, function($scope) {
  4. $scope.a = 1;
  5. $scope.b = 2;
  6. });
  7. 2.规范写法
  8. //使用了详细的注释,数组形式注入
  9. app.controller(‘GoodController1’, [‘$scope’, function($scope) {
  10. $scope.a = 1;
  11. $scope.b = 2;
  12. }]);
  13. //直接使用$inject服务,注入
  14. app.controller(‘GoodController2’, GoodController2);
  15. function GoodController2($scope) {
  16. $scope.name = ‘World’;
  17. }
  18. GoodController2.$inject = [‘$scope’];

在1.6.x之后,依赖注入的书写越来越严格,这是为了避免压缩代码后产生的unknown provider问题。尤其是小编使用了webpack打包Angular项目之后,这个问题尤为突出。

如果依赖过多,显示或者解读代码不时,推荐大家使用规范写法的第二种。

Meteor+AngularJS:超快速Web开发 - 侯振宇 - 博客园

mikel阅读(923)

来源: Meteor+AngularJS:超快速Web开发 – 侯振宇 – 博客园

   为了更好地描述Meteor和AngularJS为什么值得一谈,我先从个人角度来回顾一下这三年来WEB开发的变化:
    三年前,我已经开始尝试前后端分离,后端使用php的轻量业务逻辑框架。但当时前端还没有成熟且广泛流行的业务逻辑框架。所以在做产品开发时我仍然选用drupal等整体开发框架。开发时常常需要在JavaScript和php间切换,同时还要自己搞定数据库。此时的开发模型图是这样(红色箭头和红色块都表示工作重灾区):
    随着对用户体验的追求,我开始把业务逻辑往前端推移,于是马上遇到了前端数据与页面展示绑定的问题,业务逻辑简单时还能用JQuery搞定。越来越复杂后,开始尝试使用backbone等前框架来规范数据层和做数据与视图的绑定,用requireJS做模块化和延迟加载。同时异步处理等编程模型也都开始进入实战。后端采用RESTful接口规范。此时的开发模型图是这样:
    一年前左右,接触到knockout和AngularJS,感受到数据和视图自动绑定的美妙开发体验后,立即抛弃Backbone。此时的开发已经彻底前后分离、前端业务数据层和视图层分离。接下来又开始陆续使用coffeescript 、jade、less进一步减少代码量。用grunt做自动编译、测试、和检测文件改动自动刷新浏览器。一切都已经比较完美了,除了后端仍然要对数据持久化做不少工作,除了前端要想实时反映数据改变仍然要轮询或者用webSocket连接服务器。这时的开发模型图已经是这样了:
    只差一点就完美了,Meteor就是这一点。Meteor是一个基于nodejs、webSocket、mongoDB的整体开发框架,在它的实现中,前后端的数据模型已经几乎没有差别。
    意思就是,前端对数据模型进行任何改动,只要调用“save”方法,所有数据就自动存到服务器端的mongoDB中了——终于可以忘了主动发送请求给服务器,终于可以忘了服务器要和前端实现几乎一样的数据模型层
    而任何前端“订阅的”后端数据出现改动,前端数据模型也会马上自动得到了更新——终于可以忘了主动轮询,终于可以忘了拿到后端数据再解析成前端模型。除了前后端模型的双向自动绑定,Meteor同时还实现了数据到前端模板的自动更新。
    不过,Meteor的模板在处理视图到模型的改动时扩展性不如AngularJS。因此,将AngularJS和meteor结合是最好的选择。到这里,快速Web开发模型终于完成:
    除了模型的自动绑定与更新,meteor还提供了大量进一步加速开发的机制。如:
  • 前后端载入文件文件的自动化管理。只要将相应的文件扔到前后端相应的目录中,就会自动载入到页面,或者在后端自动运行。
  • “智能包”管理。Meteor提供了模块的机制,让第三方开发者可以写“智能包”来加强前后端的功能。如,加载了“coffeescript”智能包后。无论前后端用coffeescript写的代码都会自动编译成JavaScript后再载入。
  • 内置大量“智能包”,有进一步支持开发的包,如“less”、“underscore”、“coffeescript”,还有通用业务逻辑包。如“账户管理”,而且已经集成主流oauth账号登陆。
  • 自动检测文件改动,自动刷新浏览器。
  • 自动化部署。
    以下马上来看一个实际开发的例子,制作一个为公司录入应聘人员信息的系统。
    需求:
  • 能指派面试官,自动邮件通知。
  • 支持google邮箱登陆。
  • 体验流畅、单页应用。
    开始写业务逻辑之前,我们先开始为准备一些开发工具和环境。首先,我要求能用coffescript代替JavaScript,less代替css。安装完meteor之后,进入项目木文件夹。在命令行中输入如下代码
    meteor create myapp
    meteor add less
    meteor add coffeescript
    然后,我想在前端使用JQuery,和meteor提供的账户系统来支持google oauth授权。继续输入:
    meteor add  jQuery
    meteor add  account-ui
    meteor add  account-google
    最后,将angularJS整合进来:在项目文件夹中创建如下目录层级:
client中的内容会全部自动加载到页面上,具体加载顺序请参考官方文档。server中的文件会在应用启动时自动运行。public中文件将作为静态资源供外部访问。
    因为AngularJS对数据模型改动的检测是通过“dirty check”的方式(见Angular官方文档)。所以要使用插件来让Meteor数据改动能通知到AngularJS,以此触发视图变化。这个插件就是上图中的angular.meteor。
    接下来说用AngularJS的ui-route模块来管理页面路由,将应用变成单页:
    加入“使用google账号”登录的功能:
    当新增一个应聘者时,给面试官发邮件:
    这里应该注意到的是,Meteor对于数据的操作完全是标准的MongoDB语法。剩下的业务逻辑用AngularJS的视图与模型很快就可以实现了,这里不再赘述。
    最后看看系统的效果截图,添加新的应聘人信息:
    面试结果记录:
    总结整个开发过程,可以都看到的是,几乎没有后端开发,只要前端完成,应用基本上就完成了。并且一步就可以使用coffeescript、less等,不再需要自己搭建复杂的开发环境。这样的开发体验,在目前来说,几乎已经到极致了。
    当然,要进入真正产品级开发,Meteor还有一些问题要克服,如“支持多种数据库”,“如何部署到集群”,“实时数据增加了服务器负载”等。好在Meteor目前收到的关注已经特别高,并且有了大量的第三方开发者,进入产品级开发的步伐越来越快。我们完全可以期待,这块拼图成熟之后,将给整个web开发新注入一股强大动力。

Angular $injector:unpr Unknown provider 问题原因汇总_大V的博客-CSDN博客

mikel阅读(606)

来源: Angular $injector:unpr Unknown provider 问题原因汇总_大V的博客-CSDN博客

Unknown provider: tProvider <- t 对于很多Angular 1.x 的使用者来说,是非常头疼的一件事情,因为使用者根本不知道原因在哪里。

本文总结了迄今为止所有导致这一个问题的可能原因,希望大家遇到此问题时,不要慌!跟着下面内容一步步排查:

问题描述:

Unknown provider 一般是由于无法解析注入的一个依赖所导致的。原因一般有如下这几种可能:

1. 依赖没有定义

  1. angular.module(‘myApp’, [])
  2. .controller(‘MyController’, [‘myService’, function (myService) {
  3. // Do something with myService
  4. }]);

上述代码,由于之前没有定义myService这个依赖,会报错 [$injector:unpr] Unknown provider。如果发现有没定义的依赖,首先要提前定义好这个依赖。

  1. angular.module(‘myApp’, [])
  2. .service(‘myService’, function () { /* … */ })
  3. .controller(‘MyController’, [‘myService’, function (myService) {
  4. // Do something with myService
  5. }]);

2. 重新使用了angular.module

  1. angular.module(‘myModule’, [])
  2. .service(‘myCoolService’, function () { /* … */ });
  3. angular.module(‘myModule’, [])
  4. // myModule has already been created! This is not what you want!
  5. .directive(‘myDirective’, [‘myCoolService’, function (myCoolService) {
  6. // This directive definition throws unknown provider, because myCoolService
  7. // has been destroyed.
  8. }]);

上述代码中,重复使用了angular.module(‘myModule’, []),会让angular重新定义myModule这个模块,导致报错。angular.module(‘myModule’, []) 这个语法一般是再次定义module,为了避免这个问题,在定义module之后直接使用angular.module(‘myModule’)或者使用一个变量代替之即可。

  1. angular.module(‘myModule’, []);
  2. .service(‘myCoolService’, function () { /* … */ });
  3. angular.module(‘myModule’)
  4. .directive(‘myDirective’, [‘myCoolService’, function (myCoolService) {
  5. // This directive definition does not throw unknown provider.
  6. }]);
  7. 或者
  8. var app = angular.module(‘myModule’, []);
  9. app.service(‘myCoolService’, function () { /* … */ });
  10. app.directive(‘myDirective’, [‘myCoolService’, function (myCoolService) {
  11. // This directive definition does not throw unknown provider.
  12. }]);

3. 把一个controller当依赖注入到另外一个controller中

  1. angular.module(‘myModule’, [])
  2. .controller(‘MyFirstController’, function() { /* … */ })
  3. .controller(‘MySecondController’, [‘MyFirstController’, function(MyFirstController) {
  4. // This controller throws an unknown provider error because
  5. // MyFirstController cannot be injected.
  6. }]);

其实如果要实例化controller,可以使用$controller服务(后续博客会更新)

4. 把$scope注入到不是controller或者directive的组件中

  1. angular.module(‘myModule’, [])
  2. .service(‘MyController’, [‘$scope’, function($scope) {
  3. // This controller throws an unknown provider error because
  4. // a scope object cannot be injected into a service.
  5. }]);

发生这个情况,也很好排查,只要牢记,只有controller和directive才能注入$scope作为依赖。

5. 使用angular压缩版导致报错

可以使用ngStrictDi

好了,希望本文能帮助到大家,遇此问题千万别慌了神!

MongoDB had an unspecified uncaught exception. 报错_异度社区

mikel阅读(581)

来源: MongoDB had an unspecified uncaught exception. 报错_异度社区

MongoDB had an unspecified uncaught exception. 报错

MongoDB had an unspecified uncaught exception.
This can be caused by MongoDB being unable to write to a local database.
Check that you have permissions to write to .meteor/local. MongoDB does
not support filesystems like NFS that do not allow file locking.

在启动前type:

export LC_ALL="en_US.UTF-8"

meteor报错之:MongoDB had an unspecified uncaught exception._weixin_30407613的博客-CSDN博客

mikel阅读(559)

来源: meteor报错之:MongoDB had an unspecified uncaught exception._weixin_30407613的博客-CSDN博客

今天测试的时候meteor报了个错

如下:

  1. MongoDB had an unspecified uncaught exception.
  2. This can be caused by MongoDB being unable to write to a local database.
  3. Check that you have permissions to write to .meteor/local. MongoDB does
  4. not support filesystems like NFS that do not allow file locking.

纠结了好久终于知道答案了,请确保你的硬盘有大于50G的预留空间,然后不要在内存小的磁盘分区里测试,要在内存充足的磁盘测试,(如果你报了这个错误,尝试换个磁盘去测试)

转载于:https://www.cnblogs.com/millent/p/5371322.html

Angular 源码解析系列 - 掘金

mikel阅读(1275)

来源: Angular 源码解析系列 – 掘金

加载应用依赖模块以及内置的ng模块等,就像之前说的类似这样:['ng', [$provide, function($provide){...}], 'xx']
执行每个模块的_runBlocks,可以理解injector创建完后模块的初始化(通过myModule.run(...)注册的)

  function loadModules(modulesToLoad){
    var runBlocks = [], moduleFn;

    // 循环加载每个module,
    // 1. 注册每个模块上挂载的service(也就是_invokeQueue)
    // 2. 执行每个模块的自身的回调(也就是_configBlocks)
    // 3. 通过递归搜集所有(依赖)模块的_runBlocks,并返回
    forEach(modulesToLoad, function(module) {

      // 判断模块是否已经加载过
      if (loadedModules.get(module)) return;

      // 设置模块已经加载过
      loadedModules.put(module, true);

      function runInvokeQueue(queue) {
        var i, ii;
        for(i = 0, ii = queue.length; i < ii; i++) {

          var invokeArgs = queue[i],
              provider = providerInjector.get(invokeArgs[0]);

          // 通过providerInjector获取指定服务(类),传递参数并执行指定方法
          provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
        }
      }

      // 模块可以是以下三种情况:
      // 1. 字符串表示模块名(注册过的模块),如:'ng'模块
      // 2. 普通函数(也可以是隐式声明依赖的函数),如:function($provider) {...}
      // 3. 数组(即声明依赖的函数)如:[$provide, function($provide){...}
      try {
        if (isString(module)) {
          // 获取通过模块名获取模块对象
          moduleFn = angularModule(module);
          // 通过递归加载所有依赖模块,并且获取所有依赖模块(包括自身)的_runBlocks
          runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
          // 遍历_invokeQueue数组依次执行$provider服务的指定方法(如:factory,value等)
          runInvokeQueue(moduleFn._invokeQueue);
          // 遍历_configBlocks数组依次执行$injector服务的invoke方法(即依赖注入并执行回调)
          runInvokeQueue(moduleFn._configBlocks);

        // 如果module是函数或者数组(可认为是匿名模块),那么依赖注入后直接执行
        // 并将返回值保存到runBlocks(可能是函数,又将继续执行)
        } else if (isFunction(module)) {
            runBlocks.push(providerInjector.invoke(module));
        } else if (isArray(module)) {
            runBlocks.push(providerInjector.invoke(module));
        } else {
          assertArgFn(module, 'module');
        }
      } catch (e) {
        if (isArray(module)) {
          module = module[module.length - 1];
        }
        if (e.message && e.stack && e.stack.indexOf(e.message) == -1) {
          e = e.message + '\n' + e.stack;
        }
        throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}",
                  module, e.stack || e.message || e);
      }
    });
    return runBlocks;
  }
复制代码

到这里在注册ng模块时的回调,在runInvokeQueue(moduleFn._configBlocks);已经执行过了,也就意味着许许多多的内置模块已经存入providerCache中了,所以在后面的依赖注入中我们可以随意调用。

最后

如果不对的地方,欢迎留言指正,新浪微博 – Lovesueee

作者:小鱼二
链接:https://juejin.im/post/6844903440217276423
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Angular系列学习笔记(一)—— 聊聊angular的模块化 - 简书

mikel阅读(791)

来源: Angular系列学习笔记(一)—— 聊聊angular的模块化 – 简书

前言

近来换工作了,由于团队技术需要,不得不从vue.js迁移到angular.js,不过这也是一个学习angular的机会,顺便也将这两个框架做一些对比,为了方便其他小伙伴,将学习过程中的内容记录下来。由开始的不太习惯到现在也能够习惯angular的写法,着实在思维上有很大的改变,所以这个系列的文章会记录一下自己的学习过程,本文会由浅及深一步步去解读vue.js和angular.js的区别。由于时间关系,这篇文章断断续续耗时几周,不求什么,只希望在这个过程中将angular相关的一些特性去梳理一下,由于是系列开篇,文章并没有深入探讨源码和原理,后续会写更多个人的思考和探索。

vuejs vs angular之初相见

对于vue.js我们都需要通过创建一个Vue实例指定作用范围,对于angular只需要ng-app指令就可以。

vue.js的姿势:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
</head>
<body>
<div id="app">
    {{ message }}
    <button v-on:click="changeView">changeView</button>
</div>
<script src="//cdn.bootcss.com/vue/2.1.10/vue.js"></script>
<script type="text/javascript">
    var app = new Vue({
        el: '#app',
        data: {
            message: 'Hello world!'
        },
        methods: {
            changeView: function(){
                this.message = 'hello vuejs!';
                // 或 app.$data.message = 'hello vuejs!'
            }
        }
    })
</script>
</body>
</html>

angular的姿势:

<!DOCTYPE html>
<html ng-app="app">
<head>
    <meta charset="utf-8">
</head>
<body>
    <div ng-controller="helloCtrl">
        {{message}}
        <button ng-click="changeView()">changeView</button>
    </div>
    <script src="//cdn.bootcss.com/angular.js/1.6.0/angular.js"></script>
    <script type="application/javascript">
        var app = angular.module('app', []);
        app.controller('helloCtrl', function ($scope) {
            $scope.message = 'hello world';
            $scope.changeView = function () {
                $scope.message = 'hello angular.js!';
            }
        })
    </script>
</body>
</html>

这个例子虽然再简单不过,对于项目开发没有半点用处,但是对于理解vue.js和angular.js的区别有一定的借鉴意义,vue.js中总体的思路是实例化Vue对象,我们通过el属性指定控制的dom范围,data是数据模型,可以通过实例化后的对象app.$data访问数据,在方法中或者生命周期钩子如mounted中可以使用this获取实例上下文,vuejs中没有控制器的概念,属于典型的MVVM架构。然而我们这篇文章不是讲vue.js,因为如果你使用vue.js,那么直接看文档很好理解,无需我多言,这里我们想重点说说angular。

经常听人说vue.js语法优雅,我想应该是由于vue.js结构更加清晰吧,数据模型和操作分得更清晰,就从命名出发更简单吧,$scope是什么鬼。好吧,查了一下:scope(范围),我们姑且先从字面含义这么理解吧,但是从这例子中发现$scope多次使用,另外不能被改名,不然不能用,我们姑且认为是个神奇的玩意。angular中ng-click的指令和vue.js无大的区别,这里不多言,这里重点想谈谈模块化和angular的依赖注入。

vuejs vs angular之数据模型共享

在vue.js中是通过组件树实现模块化的,而angular通过作用域$scope实现模块化。对于vue.js中模块化的最佳实践是:

** .vue单文件组件 + 模块化标准(common.js或者es6 module) + webpack(或Browserify)。**

而其中核心部分就是组件,用官方的话说就是,组件(Component)是 Vue.js 最强大的功能之一,组件可以扩展 HTML 元素,封装可重用的代码,在较高层面上,组件是自定义元素, Vue.js 的编译器为它添加特殊功能,在有些情况下,组件也可以是原生 HTML 元素的形式,以 is 特性扩展。

如何构建一个组件,组件之前数据如何共享,这是开发中必须要学习的内容。vue.js组件实例的作用域是孤立的,这意味着不能并且不应该在子组件的模板内直接引用父组件的数据,可以使用 props 把数据传给子组件。另外vue.js中提倡单向数据流:当父组件的属性变化时,将传导给子组件,但是不会反过来。这是为了防止子组件无意修改了父组件的状态——这会让应用的数据流难以理解。当子组件需要更新父组件的状态时,我们可以通过事件触发。下面是一个利用props传递数据和使用事件触发父组件状态的典型例子:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
</head>
<body>
    <div id="app">
        {{ message }}
        <button v-on:click="parentAtion">parentAtion</button>
        <child v-bind:param="message"  v-on:childfn="fromChild"></child>
    </div>
    
    <script type="text/x-template" id="child-tpl">
        <div>
            <span>{{param}}</span>
            <button v-on:click="childAtion">childAtion</button>
        </div>
    </script>
    
    <script src="//cdn.bootcss.com/vue/2.1.10/vue.js"></script>
    <script type="text/javascript">
        Vue.component('child', {
            template: '#child-tpl',
            props: ['param'],
            methods: {
                childAtion: function(){
                    // 触发事件
                    this.$emit('childfn', 'child component');
                }
            }
        });
        
        var app = new Vue({
            el: '#app',
            data: {
                message: 'Hello vuejs!'
            },
            methods: {
                parentAtion: function(){
                    this.message = 'parent component';
                },
                fromChild: function(msg){
                    this.message = msg;
                }
            }
        })
    </script>
</body>
</html>

每个 Vue 实例都实现了事件接口(Events interface),即:

  • 使用 $on(eventName) 监听事件
  • 使用 $emit(eventName) 触发事件

Vue的事件系统分离自浏览器的EventTarget API。尽管它们的运行类似,但是$on和 $emit 不是addEventListener和 dispatchEvent的别名。

父组件可以在使用子组件的地方直接用 v-on 来监听子组件触发的事件。对于非父子组件通信的通信,在简单的场景下,使用一个空的 Vue 实例作为中央事件总线:

var bus = new Vue()
// 触发组件 A 中的事件
bus.$emit('id-selected', 1)
// 在组件 B 创建的钩子中监听事件
bus.$on('id-selected', function (id) {
  // ...
})

在更多复杂的情况下,你应该考虑使用专门的状态管理

对于angular 而言,ng-controller指令指定了作用范围,通过$scope对象控制作用域,$scope层层嵌套形成父子关系或者兄弟并列关系,而父子作用域是通过原型继承实现,子作用域可以访问父作用域的数据模型,反过来不行,同理如果父级作用域中的状态改变会影响子作用域,反过来子作用域中的状态改变不会影响父级作用域。

<!DOCTYPE html>
<html ng-app="app">
<head>
    <meta charset="utf-8">
</head>
<body>
    <div ng-controller="parentCtrl">
        {{message}}
        <button ng-click="parentAtion()">parentAtion</button>
        <div ng-controller="childCtrl">
            {{message}}
            <button ng-click="childAtion()">childAtion</button>
        </div>
    </div>
    <script src="js/angular.min.js"></script>
    <script type="application/javascript">
        var app = angular.module('app', []);
        app.controller('parentCtrl', function ($scope) {
            $scope.message = 'hello angular';
            $scope.parentAtion = function () {
                $scope.message = 'parent scope';
            }
        })
        
        app.controller('childCtrl', function ($scope) {
            $scope.childAtion = function () {
                $scope.message = 'child scope';
            }
        })
    </script>
</body>
</html>

由于原型继承的关系,修改父级对象中的message会同时修改子对象中的值,但反之则不行。如果我们想在子级控制器中改变父对象中的值,则需要通过引用进行共享。

<!DOCTYPE html>
<html ng-app="app">
<head>
    <meta charset="utf-8">
</head>
<body>
    <div ng-controller="parentCtrl">
        {{dataModal.message}}
        <button ng-click="parentAtion()">parentAtion</button>
        <div ng-controller="childCtrl">
            {{dataModal.message}}
            <button ng-click="childAtion()">childAtion</button>
        </div>
    </div>
    <script src="js/angular.min.js"></script>
    <script type="application/javascript">
        var app = angular.module('app', []);
        app.controller('parentCtrl', function ($scope) {
            $scope.dataModal = {
                message: 'hello angular'
            }
            $scope.parentAtion = function () {
                $scope.dataModal.message = 'parent scope';
            }
        })
        
        app.controller('childCtrl', function ($scope) {
            $scope.childAtion = function () {
                $scope.dataModal.message = 'child scope';
            }
        })
    </script>
</body>
</html>

问题的本质在于字符串、数字和布尔型变量是值复制,数组、对象和函数则是引用复制,通过引用共享可以在子级作用域内部改变它会影响父级作用域的数据模型状态。

另外我们通过chrome的一个插件AngularJS Batarang可以很清晰的看出$scope对象的继承关系,会发现每个$scope对象都会有个$id,以及对于作用域$scope下的数据模型,如下:

AngularJS Batarang插件效果

需要说明的是ng-app对应的作用域是全局的,我们一般使用$rootScope对象表示,对应的$id等于1。

除了通过引用复制实现数据共享,我们也可以使用类似vue.js中的事件机制。

angular事件广播:

  • $broadcast:只能向child controller传递event与data
  • $emit:只能向parent controller传递event与data

$scope.$broadcast(name, data);
$scope.$emit(name, data);

angular事件监听:

  • $on:接收event与data

$scope.$on(eventName, function(event, data){
  // 这里写监听逻辑...
})

在$on的方法中的event事件参数,其对象的属性和方法如下:

事件属性 含义
event.targetScope 发出或者传播原始事件的作用域
event.currentScope 目前正在处理的事件的作用域
event.name 事件名称
event.stopPropagation() 一个防止事件进一步传播(冒泡/捕获)的函数(这只适用于使用$emit发出的事件)
event.preventDefault() 这个方法实际上不会做什么事,但是会设置defaultPrevented为true。直到事件监听器的实现者采取行动之前它才会检查defaultPrevented的值。
event.defaultPrevented 如果调用了preventDefault则为true

上述的例子可以改写为:

var app = angular.module('app', []);
app.controller('parentCtrl', function ($scope) {
    $scope.message = 'hello angular';
    $scope.parentAtion = function () {
        $scope.message = 'parent scope';
    }
    
    // 监听事件
    $scope.$on('sendMsg', function(event, data){    
        $scope.message = data;
    })
})

app.controller('childCtrl', function ($scope) {
    $scope.childAtion = function () {
        // 触发事件
        $scope.$emit('sendMsg', 'child scope');
    }
})

angular 依赖注入

前面我们对比了angular和vue对于数据模型共享的处理方式,发现两个框架在整体上大致相同,在细微上有所区别。对于一个大型模块化工程而言,除了需要处理数据模型数据流的问题,插件和依赖的处理同样也是实现模块化必不可少的一部分。我们如何将我们的应用分成一个个小模块,互不干扰高度自治,但是却又能够相互依赖。在vue中我们是通过构建工具例如webpack+模块化标准来实现,在angular中是通过依赖注入(Dependency Injection)实现的。

这里我们先来看一下知乎上的一个问题:AngularJS中的依赖注入实际应用场景?依赖注入是一个设计模式,遵循依赖倒转原则(Dependence Inversion Priciple, DIP)。关于依赖注入的实现原理后面再做深入探讨,我们先可以认为angular提供了一套机制可以很好的解决模块之间相互依赖的问题。

angular是用$provider对象来实现自动依赖注入机制,注入机制通过调用一个provide的$get()方法,把得到的对象作为参数进行相关调用。例如:

<div ng-controller="serveCtrl">
    {{message}}
</div>

<script type="application/javascript">
    var app = angular.module('app', []);    
    //  使用provider方法创建服务
    app.provider('dataServe',{  
        $get: function() {  
            return {  
                message:"dataServe Provide"  
            };  
        }  
    }); 
    
    app.controller('serveCtrl', function ($scope, dataServe) {
        // 调用服务
        $scope.message = dataServe.message;
    })
</script>

这里我们演示了angular中依赖注入的一个简单例子,在serveCtrl控制器中我们通过将dataServe作为函数参数名写入,就可以访问dataServe服务的内容,从而实现模块解耦。

上面是一种实现方法,但不是最佳实践,我们一步步说明其他写法。

首先我们需要知道在angular中声明和引用一个模块的方法:
声明模块的写法如下:

angular.module(name, [requires], [configFn]);

name:字符串类型,代表模块的名称;
requires:字符串的数组,代表该模块依赖的其他模块列表,如果不依赖其他模块,用空数组即可;
configFn:用来对该模块进行一些配置。

函数返回模块的引用

使用模块的方法:

angular.module(name)

上面创建服务,我们可以通过$provide服务实现:

angular.module('app', [], function($provide){
    $provide.provider('dataServe', function(){
        this.$get = function(){
            return {
                message:"dataServe Provide"  
            }
        }
    })
}).controller('serveCtrl', function ($scope, dataServe) {
    // 调用服务
    $scope.message = dataServe.message;
})var app = angular.module('app', []);
app.config(function($provide){
    $provide.provider('dataServe', function(){
        this.$get = function(){
            return {
                message:"dataServe Provide"  
            }
        }
    })
})
app.controller('serveCtrl', function ($scope, dataServe) {
    $scope.message = dataServe.message;
})

创建服务的几种常用方法

上述我们使用provider和$provide.provider两种不同的写法,但是都是需要写$get函数,为了简化创建服务的过程,angualr提供了其他的五种方法:

constant:定义常量,定义的值不能被改变,可以被注入到任何地方,但是不能被装饰器(decorator)装饰;

<div ng-controller="serveCtrl">
    {{message}}
</div>

<script type="application/javascript">
    var app = angular.module('app', []);
    app.config(function($provide) {
        $provide.constant('constantServe', 'constant serve');
    })
    app.controller('serveCtrl', function($scope, constantServe) {
        $scope.message = constantServe;
    }) 
</script>

语法糖:

app.constant('constantServe', 'constant serve');

value:可以是string,number甚至function,和constant的不同之处在于,它可以被修改,不能被注入到config中,可以被decorator装饰

<div ng-controller="serveCtrl">
    <button ng-click="showTips()">show tips</button>
</div>

<script type="application/javascript">
    var app = angular.module('app', []);    
    app.value('valueServe', function(){
        alert("哈哈");
    })
    app.controller('serveCtrl', function ($scope, valueServe) {
        $scope.showTips = valueServe;
    })
</script>

factory:factory为自定义工厂,是注册服务的最常规方式,它可返回任何对象,包括基本的数据类型。

<div ng-controller="serveCtrl">
    {{message}}
</div>

<script type="application/javascript">
    var app = angular.module('app', []);
    app.config(function($provide){
        $provide.factory('factoryServe', function(){
            return {
                message: 'factory serve'
            }
        })
    })
    app.controller('serveCtrl', function ($scope, factoryServe) {
        $scope.message = factoryServe.message
    })
</script>

语法糖:

app.factory('factoryServe', function(){
    return {
        message: 'factory serve'
    }
})

factory就是通过provider第二个参数为对象的方法实现,factory底层通过调用$provide.provider(name, {$get: $getFn}),而$getFn就是自定义factory的参数,即factory所传的方法需返回一个对象,这个对象会绑定到provider的$get属性上。

service:当使用service创建服务的时候,相当于使用new关键词进行了实例化。因此,你只需要在this上添加属性和方法,然后,服务就会自动的返回this。当把这个服务注入控制器的时候,控制器就可以访问在那个对象上的属性了。

<div ng-controller="serveCtrl">
    {{message}}
</div>

<script type="application/javascript">
    var app = angular.module('app', []);
    app.config(function($provide){
        $provide.service('serviceServe', function(){
            this.message = 'factory serve';
        })
    })
    app.controller('serveCtrl', function ($scope, serviceServe) {
        $scope.message = serviceServe.message
    })
</script>

语法糖:

app.service('serviceServe', function(){
    this.message = 'factory serve';
})

decorator:provider里面的装饰器——锦上添花神器

为什么使用decorator?
我们经常在使用某些Service的时候,更希望它能具备一些额外的功能,这时我们难道改这个Service吗?如果是系统自带的呢,改吗?这当然不现实吧。所以我们的装饰器decorator就发挥作用了,它能让已有的功能锦上添花。我们在config里面使用装饰器。

<div ng-controller="serveController">
    {{message}}
</div>

<script type="application/javascript">
    var app = angular.module("app", []);
    
    app.config(['$provide', function($provide) {
        $provide.decorator('infoService', function($delegate) {
            $delegate.email = "1028317108@qq.com";
            return $delegate;
        })
    }]);

    app.service('infoService', function() {
        this.name = 'zhaomenghuan';
    })

    app.controller('serveController', ['$scope', 'infoService', function($scope, infoService) {
        $scope.message = 'name:'+infoService.name + '<=>Emil:'+infoService.email;
    }]);
</script>

我们开始写的infoService没有Emil,我们通过装饰器decorator在不修改infoService代码的情况下添加了email新属性。

源码部分:

function provider(name, provider_) {
    if(isFunction(provider_)) {
        provider_ = providerInjector.instantiate(provider_);
    }
    if(!provider_.$get) {
        throw Error('Provider ' + name + ' must define $get factory method.');
    }
    return providerCache[name + providerSuffix] = provider_;
}

function factory(name, factoryFn) {
    return provider(name, { $get: factoryFn });
}

function service(name, constructor) {
    return factory(name, ['$injector', function($injector) {
        return $injector.instantiate(constructor);
    }]);
}

function value(name, value) { return factory(name, valueFn(value)); }

function constant(name, value) {
    providerCache[name] = value;
    instanceCache[name] = value;
}

function decorator(serviceName, decorFn) {
    var origProvider = providerInjector.get(serviceName + providerSuffix),
        orig$get = origProvider.$get;
    origProvider.$get = function() {
        var origInstance = instanceInjector.invoke(orig$get, origProvider);
        return instanceInjector.invoke(decorFn, null, { $delegate: origInstance });
    };
}

注意:所有的供应商都只被实例化一次,也就说他们都是单例的,除了constant,所有的供应商都可以被装饰器(decorator)装饰。模块的config方法只能注入 provider 和 constant;模块的run方法中可被注入的依赖包括服务(service)、值(value)和参量(constant),不能注入”provider”类型的依赖。

注入依赖的几种方法

通过数组标注在方法的参数中声明依赖(优先考虑)

app.controller('serveCtrl', ['$scope', 'factoryServe', function ($scope, factoryServe) {
    $scope.message = factoryServe.message;
}])

定义在控制器构造方法的$inject属性中

app.$inject = ['$scope', 'factoryServe']
app.controller('serveCtrl', function ($scope, factoryServe) {
    $scope.message = factoryServe.message;
})

隐式声明依赖

app.controller('serveCtrl', function ($scope, factoryServe) {
    $scope.message = factoryServe.message;
})

注入器可以从函数的参数名中推断所依赖的服务。上面的函数中声明了$scope和factoryServe服务作为依赖。 这种方式可是代码更加简洁,但这种方式不能和JavaScript的代码混淆器一起使用。可以通过ng-annotate在minifying之前隐式的添加依赖。

通过在ng-app所在的DOM元素中添加ng-strict-di切换到严格的依赖注入模式,strict模式下使用隐式的标注会报错,如果用angular.bootstrap手动启动angular应用,我们可以通过设置config中的strictDi属性,启动strict模式。

angular.bootstrap(document, ['myApp'], {
  strictDi: true
});

注入器($injector)

angular注入器($injector类似于spring容器)负责创建、查找注入依赖, 每个module都会有自己的注入器。注入器负责从我们通过$provide创建的服务中创建注入的实例。只要你编写了一个带有可注入性的参数,你都能看到注入器是如何运行的。每一个AngularJS应用都有唯一一个$injector,当应用启动的时候它被创造出来,你可以通过将$injector注入到任何可注入函数中来得到它($injector知道如何注入它自己!)。

一旦你拥有了$injector,你可以动过调用get函数来获得任何一个已经被定义过的服务的实例。例如:

<div ng-controller="serveController">
    {{message}}
</div>

<script type="application/javascript">
    var app = angular.module('app', []);
    
    app.factory('factoryServe', function(){
        return {
            message: 'factory serve'
        }
    })
    
    var injector = angular.injector(['app']);
    app.controller('serveController', ['$scope', function ($scope) {
        var factoryServe = injector.get('factoryServe');
        $scope.message = factoryServe.message;
    }])
</script>

这是因为Angular在编译模板阶段为ng-controller指令以及实例化serveController注入相关的依赖。

injector.instantiate(serveController);

总结

react,angular,vue这三大框架带给前端变化,远远不只是让我们放弃JQuery这种直接操作dom的方式,也不仅仅是解决数据绑定的问题,我想更多的是改变我们的思维方式,用数据驱动的方式去更新视图,通过划分组件或者作用域,来实现整体的颗粒化,实现模块间分治,同时也改变了前后端协作的工作模式。

参考

vue.js组件
AngularJS的学习–$on、$emit和$broadcast的使用
前端需要知道的 依赖注入(Dependency Injection, DI)
理解AngularJS中的依赖注入
AngularJs中的provide

作者:技术宅小青年
链接:https://www.jianshu.com/p/0fc26c75f089
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Git 克隆分支 - 简书

mikel阅读(858)

来源: Git 克隆分支 – 简书

  1. 首先,你需要使用$ git clone这个命令克隆一个本地库。

e:
cd ProxSpace/pm3
git clone https://github.com/iceman1001/proxmark3.git "iceman"

这将远端的proxmark3项目的master分支克隆到 E:\ProxSpace\pm3下的iceman文件夹,
注意要克隆的目录(比如这里的iceman)要先创建好,且里面不能有文件(必须是空目录)。

之后不会克隆任何一个分支下来的。

  1. 查看本地与远端分支情况: git branch -a
    假定你现在的命令行正处于已经在当前克隆好的文件夹中。

git branch -a
image.png

绿色代表本地已经有的分支。remotes是远端分支情况

  1. 假定你需要一个autoBuild(此处假定远程库中已经存在此分支,也就是你需要克隆的)分支用于开发的话,你需要在autoBuild分支上开发,就必须创建远程origin的autoBuild分支到本地,于是用checkout这个命令创建本地autoBuild分支(并不会在本地创建分支文件夹的,只是在本地建立了个和远端同步的分支):

$ git checkout -b autoBuild origin/autoBuild

再同步下:

$ git pull

不执行这步的话,新checkout的分支并不会同步内容下来,以后获取分支更新内容也是用这个命令.

image.png

这样就实现了克隆autoBuild分支。

再输入git branch -a 来看看分支情况:

image.png
  1. 切换分支:

git checkout autoBuild

这样就在本地切换到了autoBuild分支了。

作者:Thresh0ld
链接:https://www.jianshu.com/p/c261867cf49a
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。