Browse Source

Content fix for MongoDB

pull/221/head
Sebastian Stehle 8 years ago
parent
commit
cdca6a65a0
  1. 4
      src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppEntity.cs
  2. 35
      src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppRepository_SnapshotStore.cs
  3. 5
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs
  4. 29
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository_SnapshotStore.cs
  5. 4
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs
  6. 28
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs
  7. 16
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs
  8. 1
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs
  9. 4
      src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEntity.cs
  10. 34
      src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleRepository_SnapshotStore.cs
  11. 4
      src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaEntity.cs
  12. 36
      src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository_SnapshotStore.cs
  13. 16
      src/Squidex.Infrastructure.MongoDb/MongoDb/IVersionedEntity.cs
  14. 60
      src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs
  15. 29
      src/Squidex.Infrastructure.MongoDb/States/MongoSnapshotStore.cs
  16. 2
      src/Squidex.Infrastructure.MongoDb/States/MongoState.cs

4
src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppEntity.cs

@ -14,7 +14,7 @@ using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Entities.MongoDb.Apps
{
public sealed class MongoAppEntity
public sealed class MongoAppEntity : IVersionedEntity<Guid>
{
[BsonId]
[BsonElement]
@ -28,7 +28,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Apps
[BsonElement]
[BsonRequired]
public int Version { get; set; }
public long Version { get; set; }
[BsonElement]
[BsonRequired]

35
src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppRepository_SnapshotStore.cs

@ -12,6 +12,7 @@ using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Apps.State;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.MongoDb.Apps
@ -32,36 +33,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Apps
return (null, EtagVersion.NotFound);
}
public async Task WriteAsync(Guid key, AppState value, long oldVersion, long newVersion)
public Task WriteAsync(Guid key, AppState value, long oldVersion, long newVersion)
{
try
{
await Collection.UpdateOneAsync(x => x.Id == key && x.Version == oldVersion,
Update
.Set(x => x.UserIds, value.Contributors.Keys.ToArray())
.Set(x => x.Name, value.Name)
.Set(x => x.State, value)
.Set(x => x.Version, newVersion),
Upsert);
}
catch (MongoWriteException ex)
{
if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
{
var existingVersion =
await Collection.Find(x => x.Id == key)
.Project<MongoAppEntity>(Projection.Exclude(x => x.Id)).FirstOrDefaultAsync();
if (existingVersion != null)
{
throw new InconsistentStateException(existingVersion.Version, oldVersion, ex);
}
}
else
{
throw;
}
}
return Collection.UpsertVersionedAsync(key, oldVersion, newVersion, u => u
.Set(x => x.Name, value.Name)
.Set(x => x.State, value)
.Set(x => x.UserIds, value.Contributors.Keys.ToArray()));
}
}
}

5
src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs

@ -10,10 +10,11 @@ using System;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Squidex.Domain.Apps.Entities.Assets.State;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
{
public sealed class MongoAssetEntity
public sealed class MongoAssetEntity : IVersionedEntity<Guid>
{
[BsonId]
[BsonElement]
@ -26,6 +27,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
[BsonElement]
[BsonRequired]
public int Version { get; set; }
public long Version { get; set; }
}
}

29
src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository_SnapshotStore.cs

@ -32,34 +32,9 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
return (null, EtagVersion.NotFound);
}
public async Task WriteAsync(Guid key, AssetState value, long oldVersion, long newVersion)
public Task WriteAsync(Guid key, AssetState value, long oldVersion, long newVersion)
{
try
{
await Collection.UpdateOneAsync(x => x.Id == key && x.Version == oldVersion,
Update
.Set(x => x.State, value)
.Set(x => x.Version, newVersion),
Upsert);
}
catch (MongoWriteException ex)
{
if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
{
var existingVersion =
await Collection.Find(x => x.Id == key).Only(x => x.Id, x => x.Version)
.FirstOrDefaultAsync();
if (existingVersion != null)
{
throw new InconsistentStateException(existingVersion["Version"].AsInt64, oldVersion, ex);
}
}
else
{
throw;
}
}
return Collection.UpsertVersionedAsync(key, oldVersion, newVersion, u => u.Set(x => x.State, value));
}
}
}

4
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs

@ -87,10 +87,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
[BsonElement("mb")]
public RefToken LastModifiedBy { get; set; }
[BsonRequired]
[BsonElement("lt")]
public bool IsLatest { get; set; }
[BsonIgnore]
public NamedContentData Data
{

28
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs

@ -26,6 +26,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
public partial class MongoContentRepository : MongoRepositoryBase<MongoContentEntity>, IContentRepository
{
private readonly IAppProvider appProvider;
private readonly IMongoCollection<MongoContentEntity> archiveCollection;
protected IMongoCollection<MongoContentEntity> ArchiveCollection
{
get { return archiveCollection; }
}
public MongoContentRepository(IMongoDatabase database, IAppProvider appProvider)
: base(database)
@ -33,6 +39,8 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
Guard.NotNull(appProvider, nameof(appProvider));
this.appProvider = appProvider;
archiveCollection = database.GetCollection<MongoContentEntity>("States_Contents_Archive");
}
protected override string CollectionName()
@ -42,25 +50,23 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
protected override async Task SetupCollectionAsync(IMongoCollection<MongoContentEntity> collection)
{
await collection.Indexes.CreateOneAsync(
await archiveCollection.Indexes.CreateOneAsync(
Index
.Ascending(x => x.Id)
.Ascending(x => x.Version));
await collection.Indexes.CreateOneAsync(
Index
.Ascending(x => x.Id)
.Descending(x => x.Version));
.Ascending(x => x.SchemaId)
.Ascending(x => x.Status)
.Text(x => x.DataText));
await collection.Indexes.CreateOneAsync(
Index
.Ascending(x => x.SchemaId)
.Descending(x => x.IsLatest)
.Descending(x => x.LastModified));
.Ascending(x => x.Id)
.Ascending(x => x.Version));
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.ReferencedIds));
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Status));
await collection.Indexes.CreateOneAsync(Index.Text(x => x.DataText));
}
public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, ODataUriParser odataQuery)
@ -94,7 +100,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet<Guid> ids)
{
var find = Collection.Find(x => ids.Contains(x.Id) && x.IsLatest);
var find = Collection.Find(x => ids.Contains(x.Id));
var contentItems = find.ToListAsync();
var contentCount = find.CountAsync();
@ -121,7 +127,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
public async Task<IContentEntity> FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id, long version)
{
var contentEntity =
await Collection.Find(x => x.Id == id && x.Version >= version).SortBy(x => x.Version)
await ArchiveCollection.Find(x => x.Id == id && x.Version >= version).SortBy(x => x.Version)
.FirstOrDefaultAsync();
contentEntity?.ParseData(schema.SchemaDef);
@ -132,7 +138,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
public async Task<IContentEntity> FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id)
{
var contentEntity =
await Collection.Find(x => x.Id == id && x.IsLatest)
await Collection.Find(x => x.Id == id)
.FirstOrDefaultAsync();
contentEntity?.ParseData(schema.SchemaDef);

16
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs

@ -41,8 +41,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
public async Task WriteAsync(Guid key, ContentState value, long oldVersion, long newVersion)
{
var documentId = $"{key}_{newVersion}";
if (value.SchemaId == Guid.Empty)
{
return;
@ -52,12 +50,13 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
var idData = value.Data?.ToIdModel(schema.SchemaDef, true);
var id = key.ToString();
var document = SimpleMapper.Map(value, new MongoContentEntity
{
DocumentId = documentId,
DocumentId = key.ToString(),
DataText = idData?.ToFullText(),
DataByIds = idData,
IsLatest = !value.IsDeleted,
ReferencedIds = idData?.ToReferencedIds(schema.SchemaDef),
});
@ -65,15 +64,14 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
try
{
await Collection.InsertOneAsync(document);
await Collection.UpdateManyAsync(x => x.Id == value.Id && x.Version < value.Version, Update.Set(x => x.IsLatest, false));
await Collection.ReplaceOneAsync(x => x.DocumentId == id && x.Version == oldVersion, document, Upsert);
}
catch (MongoWriteException ex)
{
if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
{
var existingVersion =
await Collection.Find(x => x.Id == value.Id && x.IsLatest).Only(x => x.Id, x => x.Version)
await Collection.Find(x => x.DocumentId == id).Only(x => x.DocumentId, x => x.Version)
.FirstOrDefaultAsync();
if (existingVersion != null)
@ -86,6 +84,10 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
throw;
}
}
document.DocumentId = $"{key}_{newVersion}";
await ArchiveCollection.ReplaceOneAsync(x => x.DocumentId == document.DocumentId, document, Upsert);
}
private async Task<ISchemaEntity> GetSchemaAsync(Guid appId, Guid schemaId)

1
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs

@ -68,7 +68,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
var filters = new List<FilterDefinition<MongoContentEntity>>
{
Filter.Eq(x => x.SchemaId, schemaId),
Filter.Eq(x => x.IsLatest, true),
Filter.In(x => x.Status, status)
};

4
src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEntity.cs

@ -14,7 +14,7 @@ using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
{
public sealed class MongoRuleEntity
public sealed class MongoRuleEntity : IVersionedEntity<Guid>
{
[BsonId]
[BsonElement]
@ -33,7 +33,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
[BsonElement]
[BsonRequired]
public int Version { get; set; }
public long Version { get; set; }
[BsonElement]
[BsonRequired]

34
src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleRepository_SnapshotStore.cs

@ -32,36 +32,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
return (null, EtagVersion.NotFound);
}
public async Task WriteAsync(Guid key, RuleState value, long oldVersion, long newVersion)
public Task WriteAsync(Guid key, RuleState value, long oldVersion, long newVersion)
{
try
{
await Collection.UpdateOneAsync(x => x.Id == key && x.Version == oldVersion,
Update
.Set(x => x.State, value)
.Set(x => x.AppId, value.AppId)
.Set(x => x.IsDeleted, value.IsDeleted)
.Set(x => x.Version, newVersion),
Upsert);
}
catch (MongoWriteException ex)
{
if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
{
var existingVersion =
await Collection.Find(x => x.Id == key).Only(x => x.Id, x => x.Version)
.FirstOrDefaultAsync();
if (existingVersion != null)
{
throw new InconsistentStateException(existingVersion["Version"].AsInt64, oldVersion, ex);
}
}
else
{
throw;
}
}
return Collection.UpsertVersionedAsync(key, oldVersion, newVersion, u => u
.Set(x => x.State, value)
.Set(x => x.AppId, value.AppId)
.Set(x => x.IsDeleted, value.IsDeleted));
}
}
}

4
src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaEntity.cs

@ -14,7 +14,7 @@ using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Entities.MongoDb.Schemas
{
public sealed class MongoSchemaEntity
public sealed class MongoSchemaEntity : IVersionedEntity<Guid>
{
[BsonId]
[BsonElement]
@ -37,7 +37,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Schemas
[BsonElement]
[BsonRequired]
public int Version { get; set; }
public long Version { get; set; }
[BsonElement]
[BsonRequired]

36
src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository_SnapshotStore.cs

@ -32,37 +32,13 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Schemas
return (null, EtagVersion.NotFound);
}
public async Task WriteAsync(Guid key, SchemaState value, long oldVersion, long newVersion)
public Task WriteAsync(Guid key, SchemaState value, long oldVersion, long newVersion)
{
try
{
await Collection.UpdateOneAsync(x => x.Id == key && x.Version == oldVersion,
Update
.Set(x => x.State, value)
.Set(x => x.AppId, value.AppId)
.Set(x => x.Name, value.Name)
.Set(x => x.Version, newVersion)
.Set(x => x.IsDeleted, value.IsDeleted),
Upsert);
}
catch (MongoWriteException ex)
{
if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
{
var existingVersion =
await Collection.Find(x => x.Id == key).Only(x => x.Version)
.FirstOrDefaultAsync();
if (existingVersion != null)
{
throw new InconsistentStateException(existingVersion["Version"].AsInt64, oldVersion, ex);
}
}
else
{
throw;
}
}
return Collection.UpsertVersionedAsync(key, oldVersion, newVersion, u => u
.Set(x => x.State, value)
.Set(x => x.AppId, value.AppId)
.Set(x => x.Name, value.Name)
.Set(x => x.IsDeleted, value.IsDeleted));
}
}
}

16
src/Squidex.Infrastructure.MongoDb/MongoDb/IVersionedEntity.cs

@ -0,0 +1,16 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Infrastructure.MongoDb
{
public interface IVersionedEntity<T>
{
T Id { get; set; }
long Version { get; set; }
}
}

60
src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs

@ -11,11 +11,14 @@ using System.Linq.Expressions;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver;
using Squidex.Infrastructure.States;
namespace Squidex.Infrastructure.MongoDb
{
public static class MongoExtensions
{
private static readonly UpdateOptions Upsert = new UpdateOptions { IsUpsert = true };
public static async Task<bool> InsertOneIfNotExistsAsync<T>(this IMongoCollection<T> collection, T document)
{
try
@ -55,5 +58,62 @@ namespace Squidex.Infrastructure.MongoDb
{
return find.Project<BsonDocument>(Builders<TDocument>.Projection.Include(include1).Include(include2).Include(include3));
}
public static async Task UpsertVersionedAsync<T, TKey>(this IMongoCollection<T> collection, TKey key, long oldVersion, long newVersion, Func<UpdateDefinition<T>, UpdateDefinition<T>> updater) where T : IVersionedEntity<TKey>
{
try
{
var update = updater(Builders<T>.Update.Set(x => x.Version, newVersion));
await collection.UpdateOneAsync(x => x.Id.Equals(key) && x.Version == oldVersion,
update
.Set(x => x.Version, newVersion),
Upsert);
}
catch (MongoWriteException ex)
{
if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
{
var existingVersion =
await collection.Find(x => x.Id.Equals(key)).Only(x => x.Id, x => x.Version)
.FirstOrDefaultAsync();
if (existingVersion != null)
{
throw new InconsistentStateException(existingVersion[nameof(IVersionedEntity<TKey>.Version)].AsInt64, oldVersion, ex);
}
}
else
{
throw;
}
}
}
public static async Task UpsertVersionedAsync<T, TKey>(this IMongoCollection<T> collection, TKey key, long oldVersion, long newVersion, T doc) where T : IVersionedEntity<TKey>
{
try
{
await collection.ReplaceOneAsync(x => x.Id.Equals(key) && x.Version == oldVersion, doc, Upsert);
}
catch (MongoWriteException ex)
{
if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
{
var existingVersion =
await collection.Find(x => x.Id.Equals(key)).Only(x => x.Id, x => x.Version)
.FirstOrDefaultAsync();
if (existingVersion != null)
{
throw new InconsistentStateException(existingVersion[nameof(IVersionedEntity<TKey>.Version)].AsInt64, oldVersion, ex);
}
}
else
{
throw;
}
}
}
}
}

29
src/Squidex.Infrastructure.MongoDb/States/MongoSnapshotStore.cs

@ -44,34 +44,9 @@ namespace Squidex.Infrastructure.States
return (default(T), EtagVersion.NotFound);
}
public async Task WriteAsync(TKey key, T value, long oldVersion, long newVersion)
public Task WriteAsync(TKey key, T value, long oldVersion, long newVersion)
{
try
{
await Collection.UpdateOneAsync(x => Equals(x.Id, key) && x.Version == oldVersion,
Update
.Set(x => x.Doc, value)
.Set(x => x.Version, newVersion),
Upsert);
}
catch (MongoWriteException ex)
{
if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
{
var existingVersion =
await Collection.Find(x => Equals(x.Id, key)).Only(x => x.Id, x => x.Version)
.FirstOrDefaultAsync();
if (existingVersion != null)
{
throw new InconsistentStateException(existingVersion["Version"].AsInt64, oldVersion, ex);
}
}
else
{
throw;
}
}
return Collection.UpsertVersionedAsync(key, oldVersion, newVersion, u => u.Set(x => x.Doc, value));
}
}
}

2
src/Squidex.Infrastructure.MongoDb/States/MongoState.cs

@ -12,7 +12,7 @@ using Squidex.Infrastructure.MongoDb;
namespace Squidex.Infrastructure.States
{
public sealed class MongoState<T, TKey>
public sealed class MongoState<T, TKey> : IVersionedEntity<TKey>
{
[BsonId]
[BsonElement]

Loading…
Cancel
Save