MongoDB - C# 프로그래밍

MongoDB 공식 C# Driver

아래 예제 코드에서 사용할 클래스

public class DBBasic
{
    public string _id; // 유저ID
    public int Level;
    public int Exp;
    public Int64 Money;
    public List<int> Costume; // 캐릭터 복장 아이템ID. 개수는 무조건 12
}
public class DBUserItem
{
    public Int64 _id; // Unique ID

    public string UserID;

    public int ItemID;

    [MongoDB.Bson.Serialization.Attributes.BsonElement("AD")]
    public DateTime AcquireDateTime; // 아이템 입수 시간
}

collection 객체 얻기

public static IMongoCollection<T> GetMongoDBCollectionVer2<T>(string connectString, string dbName, string collectionName)
{
   var mongoClient = new MongoClient(connectString);
   if (mongoClient == null)
   {
       return null;
   }

   var collection = mongoClient.GetDatabase(dbName).GetCollection<T>(collectionName);
   return collection;
}

Find

데이터 하나만 가져온다

try
{
	var collection = MyExtensions.GetMongoDBCollectionVer2<DBBasic>("mongodb://172.20.60.221", "TestDB", "Basic");

	// 기본으로는 Find 메소드는 없다. Find는 확장 메소드로 사용하고 싶다면
 	//using MongoDB.Driver.Core.Misc;
 	//using MongoDB.Driver;
 	//을 선언해야 한다.

 	// 첫 번째 값 또는 null
 	var document = await collection.Find(x => x._id == "jacking").FirstOrDefaultAsync();
 	document.Dump();
}
catch (Exception ex)
{
	ex.Message.Dump();
}

BsonDocument를 사용한 조건 쿼리

try
{
	var userID = "jacking";
	int userLevel = 0;

	var collection = MyExtensions.GetMongoDBCollectionVer2<BsonDocument>("mongodb://172.20.60.221", "TestDB", "Basic");
 	var filter = new BsonDocument("_id", userID);
 	var documents = await collection.Find(filter).ToListAsync();

 	if (documents.Count > 0)
 	{
    	userLevel = documents[0]["Level"].AsInt32;
	}

	userLevel.Dump();
}
catch (Exception ex)
{
 	ex.Message.Dump();
}

BsonDocument를 사용한 조건 쿼리 2

try
{
 	var collection = MyExtensions.GetMongoDBCollectionVer2<BsonDocument>("mongodb://172.20.60.221", "TestDB", "Basic");

 	var filter = new BsonDocument("Level", new BsonDocument("$gte", 2));
 	var documents = await collection.Find(filter).ToListAsync();
 	documents.Dump();
}
catch (Exception ex)
{
 	ex.Message.Dump();
}

BsonDocument를 사용한 조건 쿼리 3

try
{
	// Builders를 사용할 때는 Collection은 BsonDocument를 사용해야 한다.

 	var collection = MyExtensions.GetMongoDBCollectionVer2<BsonDocument>("mongodb://172.20.60.221", "TestDB", "Basic");

 	var builder = Builders<BsonDocument>.Filter;
 	var filter = builder.Gte("Level", 2) & builder.Eq("Money", 1000);
 	var documents = await collection.Find(filter).ToListAsync();

 	var IDList = new List<string>();

 	foreach (var document in documents)
 	{
    	IDList.Add(document["_id"].AsString);
 	}

 	IDList.Dump();
}
catch (Exception ex)
{
 	ex.Message.Dump();
}

클래스를 사용한 조건 쿼리

try
{
 	var collection = MyExtensions.GetMongoDBCollectionVer2<DBBasic>("mongodb://172.20.60.221", "TestDB", "Basic");
 	var documents = await collection.Find(x=> x.Level >= 2).ToListAsync();
 	documents.Dump();
}
catch (Exception ex)
{
 	ex.Message.Dump();
}

정렬

try
{
 	var collection = MyExtensions.GetMongoDBCollectionVer2<DBBasic>("mongodb://172.20.60.221", "TestDB", "Basic");

	// 내림차순
	//var documents = await collection.Find(x=> x.Level >= 1).SortByDescending(d => d.Level).FirstOrDefaultAsync();
	// or
	//var documents = await collection.Find(x=> x.Level >= 1).SortByDescending(d => d.Level).FirstOrDefaultAsync();
	// or
	//var documents = await collection.Find(x=> x.Level >= 1).SortByDescending(d => d.Level).ToListAsync();
	// or 조건 없이
	var documents = await collection.Find(x => true).SortByDescending(d => d.Level).ToListAsync();
 	documents.Dump();
}
catch (Exception ex)
{
 	ex.Message.Dump();
}

정렬 2

try
{
 	var collection = MyExtensions.GetMongoDBCollectionVer2<BsonDocument>("mongodb://172.20.60.221", "TestDB", "Basic");

	// 올림 차순
	//var documents = await collection.Find(new BsonDocument()).Sort(new BsonDocument("$Level", 1)).ToListAsync();

	// 내림 차순
	//var documents = await collection.Find(new BsonDocument()).Sort(new BsonDocument("Level", -1)).ToListAsync();
	// or
	var documents = await collection.Find(new BsonDocument()).Sort("{Level: -1}").ToListAsync();
 	documents.Dump();
}
catch (Exception ex)
{
 	ex.Message.Dump();
}

정렬 3

var sort = Builders<BsonDocument>.Sort.Ascending("borough").Ascending("address.zipcode");
var result = await collection.Find(filter).Sort(sort).ToListAsync();

특정 필드의 데이터만 가져오기

try
{
 	var collection = MyExtensions.GetMongoDBCollectionVer2<BsonDocument>("mongodb://172.20.60.221", "TestDB", "Basic");

	// Level만
	var documents = await collection.Find(new BsonDocument()).Project(BsonDocument.Parse("{Level:1}")).ToListAsync();
	// Level, Money만
 	//var documents = await collection.Find(new BsonDocument()).Project(BsonDocument.Parse("{Level:1, Money:1}")).ToListAsync();

	documents.Dump();
}
catch (Exception ex)
{
 	ex.Message.Dump();
}

특정 필드를 가져오기

try
{
 	var collection = MyExtensions.GetMongoDBCollectionVer2<DBBasic>("mongodb://172.20.60.221", "TestDB", "Basic");

	var projection = Builders<DBBasic>.Projection.Include("Exp").Include("Level");
	var documents = await collection.Find(x => true).Project(projection).ToListAsync();
	documents.Dump();
}
catch (Exception ex)
{
 	ex.Message.Dump();
}

특정 필드를 제외한 데이터만 가져오기

try
{
 	var collection = MyExtensions.GetMongoDBCollectionVer2<BsonDocument>("mongodb://172.20.60.221", "TestDB", "Basic");

	// Level만
	var documents = await collection.Find(new BsonDocument()).Project(BsonDocument.Parse("{Level:0}")).ToListAsync();
	documents.Dump();
}
catch (Exception ex)
{
 	ex.Message.Dump();
}

Expression. 지정된 필드만, 필드는 다른 이름으로

try
{
 	var collection = MyExtensions.GetMongoDBCollectionVer2<DBBasic>("mongodb://172.20.60.221", "TestDB", "Basic");

	var projection = Builders<DBBasic>.Projection.Expression(x => new { X = x.Level, Y = x.Exp });
	var documents = await collection.Find(x => true).Project(projection).ToListAsync();
	documents.Dump();
}
catch (Exception ex)
{
 	ex.Message.Dump();
}

//var projection = Builders<Widget>.Projection.Expression(x => new { X = x.X, Y = x.Y });
//var projection = Builders<Widget>.Projection.Expression(x => new { Sum = x.X + x.Y });
//var projection = Builders<Widget>.Projection.Expression(x => new { Avg = (x.X + x.Y) / 2 });
//var projection = Builders<Widget>.Projection.Expression(x => (x.X + x.Y) / 2);

// sort + projection + skip + limt
try
{
	//var userID = "jacking";
	//int userLevel = 0;

	var projection = BsonDocument.Parse("{Level:1, Momey:1, Exp:1}");
    var sort = BsonDocument.Parse("{Level:1}");
    var options = new FindOptions
    {
        AllowPartialResults = true,
        BatchSize = 20,
        Comment = "funny",
        //CursorType = CursorType.TailableAwait,
        MaxTime = TimeSpan.FromSeconds(3),
        NoCursorTimeout = true
    };

    var collection = MyExtensions.GetMongoDBCollectionVer2<DBBasic>("mongodb://172.20.60.221", "TestDB", "Basic");
 	var documents = collection.Find(x => x.Exp >= 0, options).Project(projection)
												.Sort(sort)
												.Limit(30)
												.Skip(0).ToListAsync();

 	documents.Dump();
}
catch (Exception ex)
{
 	ex.Message.Dump();
}

Builders 사용

try
{
	var collection = MyExtensions.GetMongoDBCollectionVer2<DBBasic>("mongodb://172.20.60.221", "TestDB", "Basic");

	var builder = Builders<DBBasic>.Filter;
	var filter = builder.Eq(x => x._id, "jacking") & builder.Lt(x => x.Money, 1100);
	// or
	//var filter = builder.Eq("X", 10) & builder.Lt("Y", 20);
	// or
	//var filter = builder.Eq("x", 10) & builder.Lt("y", 20);
	// or
	//var filter = Builders<DBBasic>.Filter.Where(x => x._id == "jacking" && x.Money < 1100);

 	// 첫 번째 값 또는 null
 	var document = await collection.Find(filter).FirstOrDefaultAsync();
 	document.Dump();
}
catch (Exception ex)
{
	ex.Message.Dump();
}

배열 요소 조건 검색

try
{
 	var collection = MyExtensions.GetMongoDBCollectionVer2<DBBasic>("mongodb://172.20.60.221", "TestDB", "Basic");

	var filter = Builders<DBBasic>.Filter.AnyGt(x => x.Costume, 0);
	var documents = await collection.Find(filter).ToListAsync();
 	documents.Dump();
}
catch (Exception ex)
{
 	ex.Message.Dump();
}


// 비동기로 검색 후 데이터를 비동기로 바로 사용
var filter = new BsonDocument("x", new BsonDocument("$gte", 100));
await collection.Find(filter).ForEachAsync(async document =>
{
	await ProcessDocumentAsync(document);
});

FindAll

async Task FooAsync()
{
    var list = await collection.Find(_ => true).ToListAsync();
}

FindOneAndReplaceAsync

try
{
 	var collection = MyExtensions.GetMongoDBCollectionVer2<DBBasic>("mongodb://172.20.60.221", "TestDB", "Basic");

	var newData = new DBBasic()
	{
		_id = "jacking3",
		Money = 3333,
		Costume = new List<int>(Enumerable.Repeat(0, 12))
	};

	// 변경 되기 이전 값을 반환한다. 실패하면 null
	var documents = await collection.FindOneAndReplaceAsync(x => x._id == "jacking5", newData);
 	documents.Dump();
}
catch (Exception ex)
{
 	ex.Message.Dump();
}

FindOneAndReplaceAsync

try
{
 	var collection = MyExtensions.GetMongoDBCollectionVer2<BsonDocument>("mongodb://172.20.60.221", "TestDB", "Basic");

	var filter = new BsonDocument("_id", "jacking3");

	// _id와 Level 필드만 도큐먼트에 남게된다.
  	var replacement = BsonDocument.Parse("{Level: 12}");
  	//var projection = BsonDocument.Parse("{x: 1}");
  	//var sort = BsonDocument.Parse("{a: -1}");
  	var options = new FindOneAndReplaceOptions<BsonDocument, BsonDocument>()
  	{
    	IsUpsert = false,
      	//Projection = projection,
      	//ReturnDocument = returnDocument,
      	//Sort = sort,
      	MaxTime = TimeSpan.FromSeconds(2)
  	};

  	var documents = await collection.FindOneAndReplaceAsync<BsonDocument>(filter, replacement, options, CancellationToken.None);
	documents.Dump();
}
catch (Exception ex)
{
 	ex.Message.Dump();
}

FindOneAndUpdateAsync

try
{
 	var collection = MyExtensions.GetMongoDBCollectionVer2<BsonDocument>("mongodb://172.20.60.221", "TestDB", "Basic");

	var filter = new BsonDocument("_id", "jacking3");
  	var update = BsonDocument.Parse("{$set: {Level: 3}}");
  	//var projection = BsonDocument.Parse("{x: 1}");
  	//var sort = BsonDocument.Parse("{a: -1}");
  	var options = new FindOneAndUpdateOptions<BsonDocument, BsonDocument>()
  	{
      	//IsUpsert = isUpsert,
      	//Projection = projection,
      	//ReturnDocument = returnDocument,
      	//Sort = sort,
      	MaxTime = TimeSpan.FromSeconds(2)
  	};


  var document = await collection.FindOneAndUpdateAsync<BsonDocument>(filter, update, options, CancellationToken.None);
  document.Dump();
}
catch (Exception ex)
{
 	ex.Message.Dump();
}

Delete

삭제. 한번에 복수개를 지울 때는 DeleteManyAsync

try
{
 	var collection = MyExtensions.GetMongoDBCollectionVer2<DBBasic>("mongodb://172.20.60.221", "TestDB", "Basic");

	var userID = "jacking4";
 	var result = await collection.DeleteOneAsync(x=> x._id == userID);
 	result.Dump();
}
catch (Exception ex)
{
 	ex.Message.Dump();
}

Insert

기본

try
{
	var newData = new DBBasic()
	{
		_id = "jacking3",
		Level = 1,
		Exp = 0,
		Money = 1000,
		Costume = new List<int>(Enumerable.Repeat(0, 12)),
	};

	var collection = MyExtensions.GetMongoDBCollectionVer2<DBBasic>("mongodb://172.20.60.221", "TestDB", "Basic");
	await collection.InsertOneAsync(newData);
}
catch(Exception ex)
{
	ex.Message.Dump();
}

복수의 데이터를 넣기

try
{
	var newItemList = new List<DBUserItem>();

	var itemIDList = new List<int>() { 1001, 1002, 1003 };
	int i = 0;
 	foreach (var itemID in itemIDList)
 	{
		++i;

    	var newData = new DBUserItem()
     	{
        	_id = DateTime.Now.Ticks + i,
        	UserID = "jacking",
        	ItemID = itemID,
        	AcquireDateTime = DateTime.Now,
     	};

     	newItemList.Add(newData);
 	}

 	var collection = MyExtensions.GetMongoDBCollectionVer2<DBUserItem>("mongodb://172.20.60.221", "TestDB", "Item");
 	await collection.InsertManyAsync(newItemList);
}
catch (Exception ex)
{
	ex.Message.Dump();
}

동적 기능 사용하기

try
{
	dynamic person = new System.Dynamic.ExpandoObject();
	person.FirstName = "Jane";
	person.Age = 12;
	person.PetNames = new List<dynamic> { "Sherlock", "Watson" };

	var collection = MyExtensions.GetMongoDBCollectionVer2<dynamic>("mongodb://172.20.60.221", "TestDB", "Persion");
	await collection.InsertOneAsync(person);
}
catch (Exception ex)
{
	ex.Message.Dump();
}

UTC 시간 보정해서 데이터 넣기

try
{
	var newData = new DBTimeData()
	{
		CurTime = DateTime.Now.AddHours(9)
	};

	var collection = MyExtensions.GetMongoDBCollectionVer2<DBTimeData>("mongodb://172.20.60.221", "TestDB", "TimeData");
	await collection.InsertOneAsync(newData);
}
catch(Exception ex)
{
	ex.Message.Dump();
}

Update

기본 업데이트

// UpdateOneAsync 는 하나만, 복수는 UpdateManyAsync
try
{
	var collection = MyExtensions.GetMongoDBCollectionVer2<DBUserSkill>("mongodb://172.20.60.221", "TestDB", "Skill");

	var userID = "jacking";

	var result = await collection.UpdateOneAsync(x => x._id == userID,
									Builders<DBUserSkill>.Update.Set(x => x.Value, 14));

	result.Dump();
	// result.MatchedCount 가 1 보다 작으면 업데이트 한 것이 없음
}
catch (Exception ex)
{
 	ex.Message.Dump();
}

도큐먼트의 내부 도큐먼트를 변경할 때

try
{
	var collection = MyExtensions.GetMongoDBCollectionVer2<DBUserSkill>("mongodb://172.20.60.221", "TestDB", "Skill");

	var userID = "jacking";
	int skillItemID = 1;
	var newInfo = new SkillItemInfo() { Value = 101 };


	var filter = Builders<DBUserSkill>.Filter.And(Builders<DBUserSkill>.Filter.Eq(x => x._id, userID),
    											Builders<DBUserSkill>.Filter.ElemMatch(x => x.SkillItems, x => x.ID == skillItemID));

	var update = Builders<DBUserSkill>.Update.Set("SkillItems.$.Info", newInfo);
	//or
	//var update = Builders<DBUserSkill>.Update.Set(x => x.SkillItems.ElementAt(-1).Info, newInfo);

	collection.UpdateOneAsync(filter, update);
}
catch (Exception ex)
{
 	ex.Message.Dump();
}

Replace

try
{
 	var collection = MyExtensions.GetMongoDBCollectionVer2<DBBasic>("mongodb://172.20.60.221", "TestDB", "Basic");

	var userID = "jacking";
 	var documents = await collection.Find(x=> x._id == userID).SingleAsync();

	if( documents != null)
	{
		documents.Level = documents.Level + 3;
		var result = await collection.ReplaceOneAsync(x => x._id == documents._id, documents);
		result.Dump();
	}
	else
	{
		Console.WriteLine("없음");
	}
}
catch (Exception ex)
{
 	ex.Message.Dump();
}
var tom = await collection.Find(x => x.Id == ObjectId.Parse("550c4aa98e59471bddf68eef"))
	.SingleAsync();

tom.Name = "Thomas";
tom.Age = 43;
tom.Profession = "Hacker";

var result = await collection.ReplaceOneAsync(x => x.Id == tom.Id, tom);



아래 내용 수정해야한다.

접속 시 인증

// on the connection string
var connectionString = "mongodb://user:pwd@localhost/?safe=true";
var server = MongoServer.Create(connectionString);

// in code
var cs = "mongodb:localhost";
MongoClient cli = new MongoClient(connectString);

var settings = new MongoServerSettings();
var adminCredential = new MongoCredential("databaseName", "user", "pwd");
settings.Credentials.Add(adminCredential);

옵션 설정하기

  • connection pool 크기 설정
// on the connection string
ConnectString="mongodb://172.20.60.221:27018/?maxPoolSize=256"

// in code
MongoClientSettings.MaxConnectionPoolSize
MongoClientSettings.MinConnectionPoolSize Property

직렬화

class Person
{
	[BsonElement("fn")]
	public string FirstName { get; set; }

	[BsonElement("fn")]
	public string LastName { get; set; }
}

public class GroceryList : MongoEntity<ObjectId>
{
    public FacebookList Owner { get; set; }
    [BsonIgnore]
    public bool IsOwner { get; set; }
}

시간 사용

MongoDB에서 사용할 수 있는 현재 시간 변환.

MongoDB는 유니버셜 시간대를 사용하고 있으므로 시간을 그냥 사용하면 현재 시간 -9 시간으로 변환되어 버린다.

public static DateTime GetLocalTime()
{
	string strCurDateTime = DateTime.Now.ToString("yyyyMMddHHmmss");
	IFormatProvider culture = new System.Globalization.CultureInfo("", true);

	DateTime LocalDateTime =  DateTime.ParseExact(strCurDateTime, "yyyyMMddHHmmss", culture, System.Globalization.DateTimeStyles.AssumeUniversal);

	return LocalDateTime;
}

입력

  • 201308011121 라는 문자열로 DateTime 객체를 만든다.
  • 중요: MongoDB는 UTC date 타임대를 사용하므로
System.Globalization.DateTimeStyles.AssumeUniversal 로 조정한다
IFormatProvider culture = new System.Globalization.CultureInfo("", true);
DateTime date = DateTime.ParseExact(date_time, "yyyyMMddHHmm", culture, System.Globalization.DateTimeStyles.AssumeUniversal);


var collection = BasicLogDB.GetCollection<BsonDocument>("Login");

BsonDocument data = new BsonDocument()
{
   {"ID", id},
   {"UniqNum", UniqueNumber},
   {"date", date}
};

await collection.InsertOneAsync(data);

예제 Insert

비동기로 하나만 추가

using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Builders;

MongoClient cli = new MongoClient(textBoxAddress.Text);
MongoDatabase testdb = cli.GetServer().GetDatabase("test");

MongoCollection<BsonDocument> Customers = testdb.GetCollection<BsonDocument>("Account");

try
{
  for (int i = 0; i < Count; ++i)
  {
      BsonDocument data = new BsonDocument()
      {
          {"ID", string.Format("{0}{1}",textBoxPreFix.Text, (StartIndex+i).ToString())},
          {"PW", "1111"}
      };

      var result = Customers.Insert(data);

      if(result.Ok == false)
         return false;
  }
}
catch (Exception err)
{
   MessageBox.Show(err.ToString());
}

제너릭 데이터를 사용한 추가

...

userInfo = new UserInfo
{
    UserIndex = 1,
    ID          = id,
    PW          = pw,
};

var result = Coll.Insert(userInfo);

if (result.Ok == false)
{
    return null;
}

다수의 값을 한번에 추가할 때 InsertBatck

  • 복사할 곳(dest)에 복사대상(source)와 같은 index를 가진 것이 있으면 안 된다.
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);
var squadList = new List<CharData>();
squadList.Add(new CharData
                {
                    UserIndex = userIndex,
                    Slot = 0,
                });
squadList.Add(new CharData
                {
                    UserIndex = userIndex,
                    Slot = 1,
                });

var result = Coll.InsertBatch(squadList);

if (result.Count() != 1)
{
    return false;
}

문자열을 ObjectId로 변환하기

var query_id = Query.EQ("_id", ObjectId.Parse("50ed4e7d5baffd13a44d0153"));
// 몽고디비 툴에서 검색할 때
db.Users.find( {_id:ObjectId("546173b07c935c15942aa233")})

예제 Select

하나만 반환

  • 검색된 것이 없을 때는 null 반환. 그러나 하나 이상 있을 때는 예외 발생
public static bool Auth(string id, string pw)
{
   try
   {
      MongoClient cli = new MongoClient("mongodb://172.20.60.221");
      MongoDatabase database = cli.GetServer().GetDatabase("test");
      var Accounts = database.GetCollection<BsonDocument>("Account");

      IMongoQuery query = Query.EQ("ID", id);
      var result = Accounts.Find(query).SingleOrDefault();

      if (result == null)
      {
         return false;
      }

      if (result["PW"] != pw)
      {
         return false;
      }
   }
   catch(Exception ex)
   {
      return false;
   }

  return true;
}

여러개 반환

IMongoQuery query = Query.EQ("version", version);
var results = ItemInfo.Find(query);


List<ItemInfo> itemlist = new List<ItemInfo>();

foreach (var iteminfo in results)
{
   var item = new ItemInfo
   {
      version = version,
      ID = iteminfo["ID"].AsInt32,
      name = iteminfo["name"].AsString,
      Lv = iteminfo["Lv"].AsInt32
   };

   itemlist.Add(item);
}
//
public class ItemInfo
{
   public int version;
   public int ID;
   public string name;
   public int Lv;
}

Where절 사용

Query<Employee>.Where(d => d.EmployeeNumber == 1234)
Query<Employee>.Where(d => d.Name == "John Doe")
Query<Employee>.Where(d => d.EmployeeStatus != Status.Active)
Query<Employee>.Where(d => d.Salary > 100000)

####복합 쿼리

var query = Query.GTE("x", 1).LTE(3);
// becomes
var query = Query.And(Query.GTE("x", 1), Query.LTE("x", 3));

var query = Query.Not("x").GT(1);
// becomes
var query = Query.Not(Query.GT("x", 1));

var query = Query.Exists("x", true);
// becomes
var query = Query.Exists("x");

var query = Query.Exists("x", false);
// becomes
var query = Query.NotExists("x");

해당 collection 모든 데이터 가져오기

List<AccountData> lstAccount = new List<AccountData>();

MongoClient cli = new MongoClient("mongodb://172.20.60.221");
MongoDatabase database = cli.GetServer().GetDatabase("WebTest");
var Accounts = database.GetCollection<BsonDocument>("Account");

// query문 없이 FindAll 사용, 해당 collection에 모든 데이터를 가져올 수 있다.
var result = Accounts.FindAll();

foreach (var AccountInfo in result)
{
    var Account = new AccountData
    {
        AccountID = AccountInfo["ID"].AsString,
        PassWord = AccountInfo["PW"].AsDouble
    };
}
return lstAccount;

First, Last

  • First는 제일 첫 번째, Last는 제일 마지막 도큐먼트 반환한다. 그러나 컬렉션에 도큐먼트가 하나도 없는 경우 예외가 발생한다.
  • 그래서 안전하게 사용하고 싶다면 FirstOrDefault, LastOrDefult를 사용하는 것이 좋다.
    • Default로 만들기 위해 델리게이트 지정을 안하면 null이 반환된다

예제 Update, Delete

// http://www.csharpstudy.com/Tips/Tips-mongodb.aspx
// Mongo DB를 위한 Connection String
string connString = "mongodb://localhost";

// MongoClient 클라이언트 객체 생성
MongoClient cli = new MongoClient(connString);

// testdb 라는 데이타베이스 가져오기
// 만약 해당 DB 없으면 Collection 생성시 새로 생성함
MongoDatabase testdb = cli.GetServer().GetDatabase("testdb");

// testdb 안에 Customers 라는 컬렉션(일종의 테이블)
// 가져오기. 만약 없으면 새로 생성.
var customers = testdb.GetCollection<Customer>("Customers");

// INSERT - 컬렉션 객체의 Insert() 메서드 호출
// Insert시 _id 라는 자동으로 ObjectID 생성
// 이 _id는 해당 다큐먼트는 나타는 키.
Customer cust1 = new Customer { Name = "Kim", Age = 30 };
customers.Insert(cust1);
ObjectId id = cust1.Id;

// SELECT - id로 검색
IMongoQuery query = Query.EQ("_id", id);
var result = customers.Find(query).SingleOrDefault();
if (result != null)
{
   Console.WriteLine(result.ToString());
}

// UPDATE
//    Save() = 전체 다큐먼트 갱신.
//    Update() = 부분 수정
cust1.Age = 31;
customers.Save(cust1);

// DELETE
var qry = Query.EQ("_id", id);
customers.Remove(qry);

데이터가 없으면 추가하고, 있다면 업데이트 하기

static void SaveServerHeartBeatInfo_Callback(Object state)
{
	try
	{
		...
		var collection = database.GetCollection<SAVE_DATA>("컬렉션");
		var findData = collection.FindOne(MongoDB.Driver.Builders.Query.EQ("name", id));

		if (findData == null)
		{
			var saveData = new SAVE_DATA
			{
				Name = id,
				value = 1000
			};

			collection.Save(saveData);
		}
		else
		{
			findData.value = 1001;
			collection.Save(findData);
		}
	}
	catch (Exception ex)
	{
	}
}

예제 FindAndModify

var modifyArgs = new MongoDB.Driver.FindAndModifyArgs()
{
	Query = Query.EQ("_id", objID),
	Update = Update.Set("MaxBuild", maxBuild),
	Fields = Fields.Include("MaxBuild"), // 이걸 지정하지 않으면 모든 필드의 값을 가져온다.
	SortBy = SortBy.Null,
	VersionReturned = MongoDB.Driver.FindAndModifyDocumentVersion.Modified, // 변경된 값을 가져온다.
	Upsert = false, // 없으면 추가하지 않는다
};

var newResult = collection.FindAndModify(modifyArgs);

// 검색 실패이면 null
if (newResult.ModifiedDocument == null)
{
	return false;
}

return true;

증분으로 업데이트 하기

var modifyArgs = new MongoDB.Driver.FindAndModifyArgs()
{
	Query = Query.EQ("_id", objID),
	Update = Update.Inc("Money", change),
	Fields = Fields.Include("Money"),
	SortBy = SortBy.Null,
	VersionReturned = MongoDB.Driver.FindAndModifyDocumentVersion.Modified,
	Upsert = false,
};

복수 데이터 일괄 Update

var ub = new UpdateBuilder();
ub.Set("Money", inputData.Money);
ub.Set("Exp", inputData.Exp);
ub.Set("Level", inputData.Level);
collection.Update(Query.EQ("UserID", inputData.UserID), ub);

특정 필드 제외, 특정 필드만 쿼리하기

특정 필드 제외

MongoClient cli = new MongoClient(DB_ConnectString);
MongoDatabase db = cli.GetServer().GetDatabase(BasicLogDatabaseName);
var UniqueNumberCol = db.GetCollection<BsonDocument>("UniqueNumber");

// _id 필드만 제외하고 모든 필드 데이터를 가져온다
var newResult = UniqueNumberCol.FindAll().SetFields(Fields.Exclude("_id"));

특정 필드만 가져오기

MongoClient cli = new MongoClient(DB_ConnectString);
MongoDatabase db = cli.GetServer().GetDatabase(BasicLogDatabaseName);
var UniqueNumberCol = db.GetCollection<BsonDocument>("UniqueNumber");

// money, level 필드 데이터만 가져온다
var newResult = UniqueNumberCol.FindAll().SetFields(Fields.include("money", "level"));

날짜 쿼리

  • 특정날짜의 데이터 개수 알아내기
  • 시간은 mongoDB에서 자동으로 ISODate로 변경한다. 단 mongoDB에 있는 시간을 애플리케이션에서 사용할 때는 LocalDateTime으로 변경해야 OS의 시간과 일치한다.
    • DB에 저장된 시간 데이터를 로딩할 때 DateTime.MinValue로 저장된 값은 ToLocalTime()으로 변환하지 않는다. 변환하면 MinValue와 다른 값이 된다
// 어제 날짜
dateTime = DateTime.Now.AddDays(-1);
// 0시로 만들기 위해서
DateTime dateTime2 = new DateTime(dateTime.Year, dateTime.Month, dateTime.Day);
var count = DBWork.DBDataMining.DailyNewUserStatistics(db, dateTime2);

static public long DailyNewUserStatistics(MongoDBLib dbLib, DateTime dateTime)
{
	var collection = dbLib.BasicLogDB.GetCollection<BsonDocument>("NewUser");
	var query = Query.And(Query.GTE("date", dateTime), Query.LTE("date", dateTime.AddDays(1));
	var count = collection.Find(query).Count();
	return count;
}

중복 제외

//{
//    "_id" : ObjectId("5209d0507dbe2af068a42aec"),
//    "ID" : "jacking75",
//    "Uniq" : 5,
//    "date" : ISODate("2013-08-12T06:21:04Z")
//}

DateTime datetime = MongoDBLib.StringToDateTime(yyyyMMddHHmm);

// 로그인한 총 수를 얻는다
var collection = dbLib.BasicLogDB.GetCollection<BsonDocument>("LoginUser");
var query = Query.And(Query.GTE("date", datetime), Query.LTE("date", datetime.AddDays(1)));

// ID 필드 값 중 중복된 것을 제외하고 개수를 구한다
var UniqueLoginUserCount = collection.Distinct("ID", query).Count();

배열 추가

데이터가 배열인 경우

var mongoClient = MongoDBLib.GetGameClient(MongoDBLib.GAME_DATABASE, userIndex);

var database = mongoClient.GetServer().GetDatabase(MongoDBLib.GAME_DATABASE);
var collection = database.GetCollection<BsonDocument>(MongoDBLib.CLEARED_STAGE_LIST_COLLECTION);

var result = collection.Update(Query.EQ("UserIndex", userIndex), Update.PushWrapped("StageIDList", stageID));
isResult = result.UpdatedExisting;

List 자료구조를 추가하기

int[] defaultValue = {0, 0, 0, 0, 0, 0, 0, 0};
List<int> aaa = new List<int>(defaultValue);


var mongoClient = MongoDBLib.GetGameClient();
MongoDatabase db = mongoClient.GetServer().GetDatabase("GAME_DATABASE");
var Coll = db.GetCollection<BsonDocument>("collection");

BsonDocument data = new BsonDocument()
{
	{"UserIndex", userIndex},
	{"Squard1", new BsonArray(aaa)},
	{"Squard2", new BsonArray(aaa)},
	{"Squard3", new BsonArray(aaa)},
};
int[] 기본값 = { 0, 0, 0, 0, 0, 0, 0, 0 };

var Coll = db.GetCollection<CharInfo>(MongoDBLib.CHARACTER_SQUARD_COLLECTION);

var squadList = new List<CharInfo>();
squadList.Add(new CharInfo
				{
					UserIndex = userIndex,
					ItemIDs = 기본값.ToList(),
				});
squadList.Add(new CharInfo
				{
					UserIndex = userIndex,
					ItemIDs = 기본값.ToList(),
				});
squadList.Add(new CharInfo
				{
					UserIndex = userIndex,
					ItemIDs = 기본값.ToList(),
				});

var result = Coll.InsertBatch(squadList);

if (result.Count() != 1)
{
	return false;
}

배열 전체 업데이트

List<bool> usePosList;
Update = Update.Set("UseBPosList", new BsonArray(usePosList))

배열의 특정 요소만 업데이트

var aValue = new AA();

// AList의 1번 인덱스(두번째 위치)의 값을 바꾼다.
var query = Query.And(Query.EQ("UserID", userID), Query.EQ("Type", type));
var update = Update.Set("AList." + 1, new BsonDocumentWrapper(aValue));

collection.Update(query, update);

BSonDocument에서 배열 데이터 읽기

// 도큐먼트
//{
//    "Cash" : 100,
//    "UseList" : [ false, false, false ],
//}

var dataList = collection.Find(query);

foreach (var data in dataList)
{
	returnValue.Cash = data["Cash"].AsInt32;
	returnValue.UseList = data["UseBPosList"].AsBsonArray.Select(p => p.AsBoolean).ToList();
}

skip & limit

Console.WriteLine(collection.Find(query).SetSkip(0).SetLimit(1).Size());

제일 마지막 레코드 가져오기

...
MongoCollection<BsonDocument> table = db.GetCollection<BsonDocument>("Collection");
var tableCount = (int)table.Count();

var skipCount = tableCount - 1;
var result = table.FindAll().SetSkip(skipCount);

// result.Size() == 1 이면 제일 마지막 것만 가져온 것 이다.

SP 실행

SP(저장프로시저) 코드를 직접 사용

var mongoClient = MongoDBLib.GetAccountClient();
var database = mongoClient.GetServer().GetDatabase(MongoDBLib.ACCOUNT_DATABASE);

BsonValue bv1 = database.Eval("function(x, y) { return x + y; }", x, y);
return (int)bv1.AsDouble;

몽고DB에 저장된 SP를 가져와서 실행

// 몽고DB 데이터베이스의 Functions에 TestSumSP라는 이름의 SP("function(x, y) { return x + y; }")를 만들어 놓음
var mongoClient = MongoDBLib.GetAccountClient();
var database = mongoClient.GetServer().GetDatabase(MongoDBLib.ACCOUNT_DATABASE);

BsonValue bv = database.Eval("TestSumSP", x, y);
BsonValue bv1 = database.Eval(bv.AsBsonJavaScript.Code, x, y);
return (int)bv1.AsDouble;

SP 호출로 데이터 얻기

static string SP_GET_USER = @"function(userID, companyName)
                         {
                             var cursor = db.User.find( {$or:[{Name: companyName}, {ID: userID}]} )
                             var hasData = cursor.hasNext() ? cursor.next() : null;
                             return hasData;
                         }";
public static bool GetUser(string userID, string name)
{
	bool result = false;

	try
	{
		var mongoClient = MongoDBLib.GetAccountClient();
		MongoDatabase db = mongoClient.GetServer().GetDatabase("AccountDB");
		BsonValue bv = db.Eval(SP_GET_USER, userID, name);
		...

                result = true;
	}
	catch (Exception ex)
	{
		return false;
	}

	return result;
}

중복 금지

  • 인덱스 사용. 컬렉션에서 중복되면 안되는 필드에 인덱스(유니크)를 걸어서 중복으로 추가되지 않도록 한다. C#에서 위와 같은 이유로(데이터 중복에 의해) 추가가 실패하면 예외가 발생한다.
  • SP 사용
static string SP_ADD_USER = @"function(userID, companyName)
            {
                var cursor = db.User.find( {$or:[{Name: companyName}, {ID: userID}]} )
                var hasData = cursor.hasNext() ? cursor.next() : null;
                if(hasData)
                {
                    return false;
                }

                db.User.insert({ ID:userID, Name:companyName })
                return true;
             }";
public static bool AddUser(UserInfo userInfo)
{
    bool result = false;

    try
    {
        var mongoClient = MongoDBLib.GetAccountClient();
        MongoDatabase db = mongoClient.GetServer().GetDatabase("AccountDB");
        BsonValue bv = db.Eval(SP_ADD_USER, userInfo.ID, userInfo.Name);
        result = bv.AsBoolean;
    }
    catch (Exception ex)
    {
        return false;
    }

    return result;
}

정렬

  • 데이터 개수가 많고 정렬한 데이터를 각각 업데이트 해야 한다면 MongoDB가 아닌 프로그램의 메모리 상에서 직접 정렬하는 것이 좋다.
    • MongoDB가 느린 이유는 정렬자체 보다는 정렬 후 업데이트를 위해서 문서 하나하나를 업데이트 해야 하는 비용이 비싸기 때문이다.
    • 업데이트라서 InsertBatch를 사용할 수 없어서 업데이트에 많은 비용이 소비된다.
    • 그래서 프로그램에서 DB의 데이터를 가져온 후 직접 정렬한 후 기존 컬렉션의 데이터를 지우고 InserBatch로 새로 데이터를 넣는 것이 훨씬 더 좋다
  • 정렬에 제한이 있다. 정렬할 데이터가 메모리 상에 32MB를 넘으면 안 된다. 그래서 정렬에 사용되는 필드는 모두 인덱스에 걸어야 한다.
// 정렬 조건 TotalScore가 큰 수, Level이 큰 수, UID가 작은 수
// 복합 인덱스를 건다 db.WeekTotalNetProfit.ensureIndex({ TotalScore: -1, Level: -1, UID: 1 })
public class DBUserWeekTotalNetProfit
{
	public string _id;
	public int Level;
	public Int64 UID;
	public Int64 TotalScore;
	public int TotalTurn;
	public int PrevOrder;   // 전날 랭킹 순위
	public int Order;       // 순위
}

try
{
	var sortBy = SortBy<DBUserWeekTotalNetProfit>.Descending(m => m.TotalScore).Descending(m => m.Level).Ascending(m => m.UID);
	var userDataSources = MongoDBLib.GetUserWeekTotalNetProfitCollection<DBUserWeekTotalNetProfit>();

	int ord = 0;
	foreach (var userDataSource in userDataSources.FindAll().SetSortOrder(sortBy))
	{
		ord++;
		userDataSource.PrevOrder = userDataSource.Order;
		userDataSource.Order = ord;
		userDataSources.Save(userDataSource);
	}
}
catch (Exception ex)
{
}  
// BSonDocument 사용 시
var dbCollection = Collection<BsonDocument>();
var dataList = dbCollection.FindAll().SetSortOrder(SortBy.Descending("TotalScore"));

컬렉션 복사

var sourceCollection = MongoDBLib.GetUserWeekTotalNetProfitCollection<DBUserWeekTotalNetProfit>();
var resultCollection = MongoDBLib.GetUserWeekTotalNetProfitResultCollection<DBUserWeekTotalNetProfit>();

resultCollection.RemoveAll();
resultCollection.InsertBatch(sourceCollection.FindAll());

LINQ

  • (일어)mongoDB에서도 LINQ를 사용하자 http://cerberus1974.hateblo.jp/entry/2012/12/13/000245

기본

MongoDB.Driver
MongoDB.Driver.Builders
MongoDB.Driver.Linq
MongoDB.Bson
MongoDB.Bson.Serialization.Attributes
[BsonIgnoreExtraElements]
public class AddressEntry
{
  [BsonId]
  public BsonObjectId _id { get; set;}
  public string Name { get; set; }
  public DateTime Birthday { get; set; }
  public List<Email> EmailAddrs { get; set; }

  [BsonIgnore]
   public string Detail { get; set; }  // 맵핑하지 않는다
}

[BsonIgnoreExtraElements]
public class Email
{
  public string DisplayName { get; set; }
  public string MailAddress { get; set; }
}


// MongoDB에 접속
MongoClient cli = new MongoClient();
// Database를 선택
MongoDatabase db = cli.GetServer().GetDatabase("TestDB");
// 컬렉션 취득
MongoCollection<AddressEntry> col = db.GetCollection<AddressEntry>("AddressBook");
public void Order_Where_Member_1()
{
    var orderCollection = GetCollection(); // 컬렉션 취득을 함수화

    var orders = orderCollection.AsQueryable().Where( x => x.MemberId == 1 ).ToList();

    orders.ForEach( x =>
            {
                WriteOrder( x );
                x.OrderDetails.ToList().ForEach( y => WriteOrderDetail( y ) );
            }
        );
}

&&

var query = from e in collection.AsQueryable<Employee>()
      where
         e.EmployeeStatus == Status.Active &&
         e.Salary > 100000
      select e;

// translates to:
{ EmployeeStatus : 1, Salary : { $gt : 100000 } }

문자열에서 일부 문자 찾기

var query = from e in collection.AsQueryable<Employee>()
      where e.Name.Contains("oh")
      select e;

// translates to:
{ nm: /oh/s }


var query = from e in collection.AsQueryable<Employee>()
      where e.Name.StartsWith("John")
      select e;

// translates to:
{ nm: /^John/s }

string Length, IsNullOrEmpty

var query = from e in collection.AsQueryable<Employee>()
      where e.Name.Length == 4
      select e;

// translates to:
{ nm: /^.{4}$/s }


var query = from e in collection.AsQueryable<Employee>()
      where string.IsNullOrEmpty(e.Name)
      select e;

// translates to:
{ $or : [ { nm: { $type : 10 } }, { nm: "" } ] }

string ToLower

var query = from e in collection.AsQueryable<Employee>()
      where e.Name.ToLower() == "john macadam"
      select e;

// translates to:
{ nm: /^john macadam$/is }

array element, Length

var query = from e in collection.AsQueryable<Employee>()
      where e.Skills[0] == "Java"
      select e;

// translates to:
{ "Skills.0" : "Java" }


var query = from e in collection.AsQueryable<Employee>()
       where e.Skills.Length == 3
       select e;

// translates to:
{ Skills : { $size : 3 } }

dotted names

var query = from e in collection.AsQueryable<Employee>()
      where e.Address.City == "Hoboken"
      select e;

// translates to:
{ "Address.City" : "Hoboken" }

$in

var states = new [] { "NJ", "NY", "PA" };
var query = from e in collection.AsQueryable<Employee>()
      where states.Contains(e.Address.State)
      select e;

// translates to:
{ "Address.State" : { $in : [ "NJ", "NY", "PA" ] } }


// alternative syntax using C#/.NET driver "In" method
var query = from e in collection.AsQueryable<Employee>()
     where e.Address.State.In(states)
     select e;

$in/$all with arrays

var desiredSkills = new [] { "Java", "C#" };
var query = from e in collection.AsQueryable<Employee>()
      where e.Skills.ContainsAny(desiredSkills)
      select e;

// translates to:
{ "Skills" : { $in : [ "Java", "C#" ] } }


var query = from e in collection.AsQueryable<Employee>()
     where e.Skills.ContainsAll(desiredSkills)
     select e;

// translates to:
{ "Skills" : { $all : [ "Java", "C#" ] } }
// note: ContainsAny and ContainsAll are defined by the C#/.NET
driver and are not part of standard LINQ

$elemMatch

var query = from e in collection.AsQueryable<Employee>()
   where
      e.Addresses.Any(a =>
         a.City == "Hoboken" &&
         a.State == "NJ")
   select e;

// translates to:
{ "Addresses" : { $elemMatch :
   { City : "Hoboken", State : "NJ" } } }

Mixing LINQ with MongoDB queries

var query = from e in collection.AsQueryable()
      where
         e.Salary > 50000 &&
         Query.NotExists("EmployeeStatus").Inject()
      select e;

// translates to:
{
   Salary : { $gt : 50000 },
   EmployeeStatus : { $exists : false }
}

참고 문서

  • What’s new in the .NET Driver http://www.slideshare.net/mongodb/webinar-whats-new-in-the-net-driver
  • CSharp Driver LINQ Tutorial http://docs.mongodb.org/ecosystem/tutorial/use-linq-queries-with-csharp-driver/

Error Code

  • http://www.mongodb.org/about/contributors/error-codes/

이 글은 2019-04-24에 작성되었습니다.