mongoDB: C# driver V2 如何更新嵌套集合中的项目
mongoDB: C# driver V2 How to update item in a nested collection
我有以下Class设计:
public class UserDayStore
{
public Guid Id { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public DateTime EndOfSubscription { get; set; }
public bool active { get; internal set; }
public DateTime LastModified;
public List<DbDayRecord> Days { get; set; }
}
和
public class DbDayRecord
{
public Guid StoreId { get; set; }
public DateTime LastModified { get; set; }
public DateTime DateOfDay { get; set; }
public String Quote { get; set; }
}
添加 UserDayStore 没有问题,也可以将嵌套项添加到 List<Days>
。当我尝试更新 DayRecord 时,更新已完成,正如我在 Robomongo 中看到的那样。但是,当我尝试在此之后在 UserStore 集合中搜索文档时,我得到了这个异常:
System.FormatException
An error occurred while deserializing the Days property of class
QuoteMyDayServer.ServerLogic.UserDayStore: Cannot deserialize a
'List' from BsonType 'Document'.
bei MongoDB.Driver.Linq.MongoQueryProviderImpl1.Execute(Expression
expression) bei
MongoDB.Driver.Linq.MongoQueryProviderImpl
1.Execute[TResult](Expression
expression) bei System.Linq.Queryable.First[TSource](IQueryable1
source) bei
QuoteMyDayServer.ServerLogic.QuoteDatabase.<SetUserState>d__10.MoveNext()
in
C:\Entwicklung\Apps\QuoteMyDay\Server\QuoteMyDayServer\QuoteMyDayServer\ServerLogic\QuoteDatabase.cs:Zeile
147.
--- Ende der Stapelüberwachung vom vorhergehenden Ort, an dem die Ausnahme ausgelöst wurde --- bei
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task
task) bei
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task
task) bei System.Runtime.CompilerServices.TaskAwaiter
1.GetResult()
bei NancyTests.MongoTests.d__8.MoveNext() in
C:\Entwicklung\Apps\QuoteMyDay\Server\QuoteMyDayServer\NancyTests\MongoTests.cs:Zeile
148.
--- Ende der Stapelüberwachung vom vorhergehenden Ort, an dem die Ausnahme ausgelöst wurde --- bei
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task
task) bei
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task
task) bei
Xunit.Sdk.TestInvoker`1.<>c__DisplayClass46_1.<b__1>d.MoveNext()
in
C:\BuildAgent\work\cb37e9acf085d108\src\xunit.execution\Sdk\Frameworks\Runners\TestInvoker.cs:Zeile
227.
--- Ende der Stapelüberwachung vom vorhergehenden Ort, an dem die Ausnahme ausgelöst wurde --- bei
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task
task) bei
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task
task) bei Xunit.Sdk.ExecutionTimer.d__4.MoveNext()
in
C:\BuildAgent\work\cb37e9acf085d108\src\xunit.execution\Sdk\Frameworks\ExecutionTimer.cs:Zeile
48.
--- Ende der Stapelüberwachung vom vorhergehenden Ort, an dem die Ausnahme ausgelöst wurde --- bei
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task
task) bei
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task
task) bei Xunit.Sdk.ExceptionAggregator.d__9.MoveNext()
in
C:\BuildAgent\work\cb37e9acf085d108\src\xunit.core\Sdk\ExceptionAggregator.cs:Zeile
90.
System.FormatException
Cannot deserialize a 'List' from BsonType 'Document'.
bei
MongoDB.Bson.Serialization.Serializers.EnumerableSerializerBase2.Deserialize(BsonDeserializationContext
context, BsonDeserializationArgs args) bei
MongoDB.Bson.Serialization.Serializers.SerializerBase
1.MongoDB.Bson.Serialization.IBsonSerializer.Deserialize(BsonDeserializationContext
context, BsonDeserializationArgs args) bei
MongoDB.Bson.Serialization.IBsonSerializerExtensions.Deserialize(IBsonSerializer
serializer, BsonDeserializationContext context) bei
MongoDB.Bson.Serialization.BsonClassMapSerializer`1.DeserializeMemberValue(BsonDeserializationContext
context, BsonMemberMap memberMap)
如果存在相同"DateOfDay"的DayRecord,这是更新DayRecord的方法,否则会插入一个新的。
public async Task<bool> UpdateDayRecord(Guid storeID, DbDayRecord dayRecord)
{
var stores = GetUserStores();
var builder = Builders<UserDayStore>.Filter;
var filter = builder.Eq("Id", storeID);
var result = await stores.FindAsync(filter);
if (!await result.AnyAsync()) // Check is a Store with that Id exists
{
return false;
}
dayRecord.StoreId = storeID;
filter = builder.ElemMatch("Days", Builders<DbDayRecord>.Filter.Eq("DateOfDay", dayRecord.DateOfDay));
result = await stores.FindAsync(filter);
if (await result.AnyAsync())
{
var update = Builders<UserDayStore>.Update.Set("Days", dayRecord).CurrentDate("LastModified");
var updateResult = await stores.UpdateOneAsync(filter, update);
return (updateResult.ModifiedCount == 1);
}
else
{
filter = Builders<UserDayStore>.Filter.Eq("Id", storeID);
var update = Builders<UserDayStore>.Update.AddToSet("Days", dayRecord).CurrentDate("LastModified");
var updateResult = await stores.UpdateOneAsync(filter, update);
return (updateResult.ModifiedCount == 1);
}
}
调用该方法并更新现有的 DayRecord 后,我在尝试访问 UserDayStore 时遇到上述异常:
public async Task<Guid> GetStoreId (string username)
{
var stores = GetUserStores();
var filter = Builders<UserDayStore>.Filter.Eq("UserName", username);
var result = await stores.FindAsync(filter);
return result.First().Id;
}
FindAsync 调用失败。
这是 JSON 文档更新后的样子
{
"_id" : LUUID("e858f1cc-c81d-7244-b8a0-8beec3c8e10d"),
"LastModified" : ISODate("2016-04-23T10:43:17.293Z"),
"UserName" : "TestUser",
"Password" : "4242",
"EndOfSubscription" : ISODate("2016-06-22T22:00:00.000Z"),
"active" : true,
"Days" : {
"StoreId" : LUUID("e858f1cc-c81d-7244-b8a0-8beec3c8e10d"),
"LastModified" : ISODate("2016-04-24T00:00:00.000Z"),
"DateOfDay" : ISODate("2016-04-23T00:00:00.000Z"),
"Quote" : "Testquote1"
}
}
好吧,我看到了几个错误(很可能是打字错误),所以我通过了。但是,您的 GetStoreId
实施有点冒险。如果给定用户名没有匹配的商店怎么办。您假设总会存在错误的文档。
我已经稍微更改了 GetStoreId
实现,所以请将它与您的实现交换一下,看看它是否有效。
public async Task<Guid> GetStoreId (string username)
{
var cursor = await collection.FindAsync(x => x.UserName == username);
var userDayStore = await cursor.FirstOrDefaultAsync();
return userDayStore != null ? userDayStore.Id: Guid.Empty;
}
感谢@Saleem 的帮助,我找到了问题。
更新前 JSON 文档如下所示:
{
"_id" : LUUID("f7379cb0-bace-0442-9942-4452f8646522"),
"LastModified" : ISODate("2016-04-23T12:59:31.358Z"),
"UserName" : "TestUser",
"Password" : "4242",
"EndOfSubscription" : ISODate("2016-06-22T22:00:00.000Z"),
"active" : true,
"Days" : [
{
"StoreId" : LUUID("f7379cb0-bace-0442-9942-4452f8646522"),
"LastModified" : ISODate("2016-04-24T00:00:00.000Z"),
"DateOfDay" : ISODate("2016-04-23T00:00:00.000Z"),
"Quote" : "Testquote1"
}
]
}
所以问题出在更新上。它将 List 更改为 DayRecord 对象。
一旦我有了可行的解决方案,我就会更新这个问题
我认为您的问题是您的 Update 语句在一种情况下使用 Set 而在另一种情况下使用 AddToSet。 AddToSet是基于数组的操作,Set直接赋值。您应该在这两种情况下都使用 AddToSet 以确保数组存在于 MongoDB.
FormatException 是因为我们期待一个数组(因为类型是列表),而不是我们得到一个文档。
经过反复尝试,我想我找到了正确的方法:
filter = builder.ElemMatch("Days", Builders<DbDayRecord>.Filter.Eq("DateOfDay", dayRecord.DateOfDay));
result = await stores.FindAsync(filter);
if (await result.AnyAsync()) // Record already exists, update it
{
var update = Builders<UserDayStore>.Update.Set("Days.$", dayRecord).CurrentDate("LastModified");
var updateResult = await stores.UpdateOneAsync(filter, update);
return (updateResult.ModifiedCount == 1);
}
else // Add new Record to array
{
filter = Builders<UserDayStore>.Filter.Eq("Id", storeID);
var update = Builders<UserDayStore>.Update.AddToSet("Days", dayRecord).CurrentDate("LastModified");
var updateResult = await stores.UpdateOneAsync(filter, update);
return (updateResult.ModifiedCount == 1);
}
重点在于:
var update = Builders<UserDayStore>.Update.Set("Days.$", dayRecord).CurrentDate("LastModified");
添加 .$
使得 mongo 更新数组元素。
而不是
var update = Builders<UserDayStore>.Update.Set("Days.$", dayRecord).CurrentDate("LastModified");
你也可以这样做:
var update = Builders<UserDayStore>.Update.Set(x => x.Days[-1], dayRecord).CurrentDate("LastModified");
我有以下Class设计:
public class UserDayStore
{
public Guid Id { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public DateTime EndOfSubscription { get; set; }
public bool active { get; internal set; }
public DateTime LastModified;
public List<DbDayRecord> Days { get; set; }
}
和
public class DbDayRecord
{
public Guid StoreId { get; set; }
public DateTime LastModified { get; set; }
public DateTime DateOfDay { get; set; }
public String Quote { get; set; }
}
添加 UserDayStore 没有问题,也可以将嵌套项添加到 List<Days>
。当我尝试更新 DayRecord 时,更新已完成,正如我在 Robomongo 中看到的那样。但是,当我尝试在此之后在 UserStore 集合中搜索文档时,我得到了这个异常:
System.FormatException
An error occurred while deserializing the Days property of class QuoteMyDayServer.ServerLogic.UserDayStore: Cannot deserialize a 'List' from BsonType 'Document'.
bei MongoDB.Driver.Linq.MongoQueryProviderImpl
1.Execute(Expression expression) bei MongoDB.Driver.Linq.MongoQueryProviderImpl
1.Execute[TResult](Expression expression) bei System.Linq.Queryable.First[TSource](IQueryable1 source) bei QuoteMyDayServer.ServerLogic.QuoteDatabase.<SetUserState>d__10.MoveNext() in C:\Entwicklung\Apps\QuoteMyDay\Server\QuoteMyDayServer\QuoteMyDayServer\ServerLogic\QuoteDatabase.cs:Zeile 147. --- Ende der Stapelüberwachung vom vorhergehenden Ort, an dem die Ausnahme ausgelöst wurde --- bei System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) bei System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) bei System.Runtime.CompilerServices.TaskAwaiter
1.GetResult() bei NancyTests.MongoTests.d__8.MoveNext() in C:\Entwicklung\Apps\QuoteMyDay\Server\QuoteMyDayServer\NancyTests\MongoTests.cs:Zeile 148. --- Ende der Stapelüberwachung vom vorhergehenden Ort, an dem die Ausnahme ausgelöst wurde --- bei System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) bei System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) bei Xunit.Sdk.TestInvoker`1.<>c__DisplayClass46_1.<b__1>d.MoveNext() in C:\BuildAgent\work\cb37e9acf085d108\src\xunit.execution\Sdk\Frameworks\Runners\TestInvoker.cs:Zeile 227. --- Ende der Stapelüberwachung vom vorhergehenden Ort, an dem die Ausnahme ausgelöst wurde --- bei System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) bei System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) bei Xunit.Sdk.ExecutionTimer.d__4.MoveNext() in C:\BuildAgent\work\cb37e9acf085d108\src\xunit.execution\Sdk\Frameworks\ExecutionTimer.cs:Zeile 48. --- Ende der Stapelüberwachung vom vorhergehenden Ort, an dem die Ausnahme ausgelöst wurde --- bei System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) bei System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) bei Xunit.Sdk.ExceptionAggregator.d__9.MoveNext() in C:\BuildAgent\work\cb37e9acf085d108\src\xunit.core\Sdk\ExceptionAggregator.cs:Zeile 90.System.FormatException
Cannot deserialize a 'List' from BsonType 'Document'.
bei MongoDB.Bson.Serialization.Serializers.EnumerableSerializerBase
2.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) bei MongoDB.Bson.Serialization.Serializers.SerializerBase
1.MongoDB.Bson.Serialization.IBsonSerializer.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) bei MongoDB.Bson.Serialization.IBsonSerializerExtensions.Deserialize(IBsonSerializer serializer, BsonDeserializationContext context) bei MongoDB.Bson.Serialization.BsonClassMapSerializer`1.DeserializeMemberValue(BsonDeserializationContext context, BsonMemberMap memberMap)
如果存在相同"DateOfDay"的DayRecord,这是更新DayRecord的方法,否则会插入一个新的。
public async Task<bool> UpdateDayRecord(Guid storeID, DbDayRecord dayRecord)
{
var stores = GetUserStores();
var builder = Builders<UserDayStore>.Filter;
var filter = builder.Eq("Id", storeID);
var result = await stores.FindAsync(filter);
if (!await result.AnyAsync()) // Check is a Store with that Id exists
{
return false;
}
dayRecord.StoreId = storeID;
filter = builder.ElemMatch("Days", Builders<DbDayRecord>.Filter.Eq("DateOfDay", dayRecord.DateOfDay));
result = await stores.FindAsync(filter);
if (await result.AnyAsync())
{
var update = Builders<UserDayStore>.Update.Set("Days", dayRecord).CurrentDate("LastModified");
var updateResult = await stores.UpdateOneAsync(filter, update);
return (updateResult.ModifiedCount == 1);
}
else
{
filter = Builders<UserDayStore>.Filter.Eq("Id", storeID);
var update = Builders<UserDayStore>.Update.AddToSet("Days", dayRecord).CurrentDate("LastModified");
var updateResult = await stores.UpdateOneAsync(filter, update);
return (updateResult.ModifiedCount == 1);
}
}
调用该方法并更新现有的 DayRecord 后,我在尝试访问 UserDayStore 时遇到上述异常:
public async Task<Guid> GetStoreId (string username)
{
var stores = GetUserStores();
var filter = Builders<UserDayStore>.Filter.Eq("UserName", username);
var result = await stores.FindAsync(filter);
return result.First().Id;
}
FindAsync 调用失败。
这是 JSON 文档更新后的样子
{
"_id" : LUUID("e858f1cc-c81d-7244-b8a0-8beec3c8e10d"),
"LastModified" : ISODate("2016-04-23T10:43:17.293Z"),
"UserName" : "TestUser",
"Password" : "4242",
"EndOfSubscription" : ISODate("2016-06-22T22:00:00.000Z"),
"active" : true,
"Days" : {
"StoreId" : LUUID("e858f1cc-c81d-7244-b8a0-8beec3c8e10d"),
"LastModified" : ISODate("2016-04-24T00:00:00.000Z"),
"DateOfDay" : ISODate("2016-04-23T00:00:00.000Z"),
"Quote" : "Testquote1"
}
}
好吧,我看到了几个错误(很可能是打字错误),所以我通过了。但是,您的 GetStoreId
实施有点冒险。如果给定用户名没有匹配的商店怎么办。您假设总会存在错误的文档。
我已经稍微更改了 GetStoreId
实现,所以请将它与您的实现交换一下,看看它是否有效。
public async Task<Guid> GetStoreId (string username)
{
var cursor = await collection.FindAsync(x => x.UserName == username);
var userDayStore = await cursor.FirstOrDefaultAsync();
return userDayStore != null ? userDayStore.Id: Guid.Empty;
}
感谢@Saleem 的帮助,我找到了问题。
更新前 JSON 文档如下所示:
{
"_id" : LUUID("f7379cb0-bace-0442-9942-4452f8646522"),
"LastModified" : ISODate("2016-04-23T12:59:31.358Z"),
"UserName" : "TestUser",
"Password" : "4242",
"EndOfSubscription" : ISODate("2016-06-22T22:00:00.000Z"),
"active" : true,
"Days" : [
{
"StoreId" : LUUID("f7379cb0-bace-0442-9942-4452f8646522"),
"LastModified" : ISODate("2016-04-24T00:00:00.000Z"),
"DateOfDay" : ISODate("2016-04-23T00:00:00.000Z"),
"Quote" : "Testquote1"
}
]
}
所以问题出在更新上。它将 List 更改为 DayRecord 对象。
一旦我有了可行的解决方案,我就会更新这个问题
我认为您的问题是您的 Update 语句在一种情况下使用 Set 而在另一种情况下使用 AddToSet。 AddToSet是基于数组的操作,Set直接赋值。您应该在这两种情况下都使用 AddToSet 以确保数组存在于 MongoDB.
FormatException 是因为我们期待一个数组(因为类型是列表),而不是我们得到一个文档。
经过反复尝试,我想我找到了正确的方法:
filter = builder.ElemMatch("Days", Builders<DbDayRecord>.Filter.Eq("DateOfDay", dayRecord.DateOfDay));
result = await stores.FindAsync(filter);
if (await result.AnyAsync()) // Record already exists, update it
{
var update = Builders<UserDayStore>.Update.Set("Days.$", dayRecord).CurrentDate("LastModified");
var updateResult = await stores.UpdateOneAsync(filter, update);
return (updateResult.ModifiedCount == 1);
}
else // Add new Record to array
{
filter = Builders<UserDayStore>.Filter.Eq("Id", storeID);
var update = Builders<UserDayStore>.Update.AddToSet("Days", dayRecord).CurrentDate("LastModified");
var updateResult = await stores.UpdateOneAsync(filter, update);
return (updateResult.ModifiedCount == 1);
}
重点在于:
var update = Builders<UserDayStore>.Update.Set("Days.$", dayRecord).CurrentDate("LastModified");
添加 .$
使得 mongo 更新数组元素。
而不是
var update = Builders<UserDayStore>.Update.Set("Days.$", dayRecord).CurrentDate("LastModified");
你也可以这样做:
var update = Builders<UserDayStore>.Update.Set(x => x.Days[-1], dayRecord).CurrentDate("LastModified");