Skip to content

Commit

Permalink
Merge pull request #59 from TurnerSoftware/entity-command-writing
Browse files Browse the repository at this point in the history
EntityWriter system overhaul to support commands
  • Loading branch information
Turnerj authored May 13, 2019
2 parents 35c5b5e + 866e58c commit 282f843
Show file tree
Hide file tree
Showing 35 changed files with 855 additions and 506 deletions.
2 changes: 2 additions & 0 deletions src/Directory.build.props
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@

<DebugSymbols>true</DebugSymbols>
<DebugType>embedded</DebugType>

<LangVersion>Latest</LangVersion>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)' == 'Debug' ">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ public void OnNext(DiagnosticCommand value)
{
OnNextWriteCommand(writeCommandBase);
}
else if (value is IndexDiagnosticCommandBase indexCommandBase)
{
OnNextIndexCommand(indexCommandBase);
}
}

private void OnNextWriteCommand(WriteDiagnosticCommandBase commandBase)
Expand Down Expand Up @@ -107,5 +111,38 @@ private string GetWriteModelAsString<TEntity>(WriteModel<TEntity> writeModel)
return $"Can't render {writeModel.ModelType} to a string";
}
}

private void OnNextIndexCommand(IndexDiagnosticCommandBase commandBase)
{
var onNextIndexCommand = GetType().GetMethods(BindingFlags.Instance | BindingFlags.NonPublic)
.Where(m => m.IsGenericMethod && m.Name == "OnNextIndexCommand").FirstOrDefault();
onNextIndexCommand.MakeGenericMethod(commandBase.EntityType).Invoke(this, new[] { commandBase });
}
#pragma warning disable CRR0026 // Unused member
private void OnNextIndexCommand<TEntity>(IndexDiagnosticCommand<TEntity> command)
{
if (command.CommandState == CommandState.Start)
{
var queryList = command.IndexModel.GroupBy(w => w.Options.Name)
.Select(g => g.Key.ToString() + "\n" + string.Join("\n", g.Select(w => GetIndexModelAsString(w))));
var indexModelString = string.Join("; ", queryList);
Commands[command.CommandId] = StackExchange.Profiling.MiniProfiler.Current.CustomTiming("mongoframework", indexModelString, command.Source);
}
else if (Commands.TryRemove(command.CommandId, out var current))
{
current.Errored = command.CommandState == CommandState.Error;
current.Stop();
}
}
#pragma warning restore CRR0026 // Unused member
private string GetIndexModelAsString<TEntity>(CreateIndexModel<TEntity> indexModel)
{
var serializer = BsonSerializer.LookupSerializer<TEntity>();
return new BsonDocument
{
{ "Keys", indexModel.Keys.Render(serializer, BsonSerializer.SerializerRegistry) },
{ "Options", indexModel.Options.ToBsonDocument() }
}.ToString();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using MongoDB.Driver;
using MongoFramework.Infrastructure.Commands;
using MongoFramework.Infrastructure.DefinitionHelpers;
using MongoFramework.Infrastructure.Diagnostics;
using MongoFramework.Infrastructure.Mapping;
Expand All @@ -11,12 +12,12 @@

namespace MongoFramework.Infrastructure
{
public class EntityWriter<TEntity> : IEntityWriter<TEntity> where TEntity : class
public class CommandWriter<TEntity> : ICommandWriter<TEntity> where TEntity : class
{
public IMongoDbConnection Connection { get; }
private IEntityDefinition EntityDefinition { get; }

public EntityWriter(IMongoDbConnection connection)
public CommandWriter(IMongoDbConnection connection)
{
Connection = connection ?? throw new ArgumentNullException(nameof(connection));
EntityDefinition = EntityMapping.GetOrCreateDefinition(typeof(TEntity));
Expand All @@ -27,46 +28,9 @@ private IMongoCollection<TEntity> GetCollection()
return Connection.GetDatabase().GetCollection<TEntity>(EntityDefinition.CollectionName);
}

private IEnumerable<WriteModel<TEntity>> BuildWriteModel(IEntityCollection<TEntity> entityCollection)
public void Write(IEnumerable<IWriteCommand<TEntity>> writeCommands)
{
var idFieldName = EntityDefinition.GetIdName();
var writeModel = new List<WriteModel<TEntity>>();

foreach (var entry in entityCollection.GetEntries())
{
if (entry.State == EntityEntryState.Added)
{
EntityMutation<TEntity>.MutateEntity(entry.Entity, MutatorType.Insert, Connection);
writeModel.Add(new InsertOneModel<TEntity>(entry.Entity));
}
else if (entry.State == EntityEntryState.Updated)
{
EntityMutation<TEntity>.MutateEntity(entry.Entity, MutatorType.Update, Connection);
var idFieldValue = EntityDefinition.GetIdValue(entry.Entity);
var filter = Builders<TEntity>.Filter.Eq(idFieldName, idFieldValue);
var updateDefintion = UpdateDefinitionHelper.CreateFromDiff<TEntity>(entry.OriginalValues, entry.CurrentValues);

//MongoDB doesn't like it if an UpdateDefinition is empty.
//This is primarily to work around a mutation that may set an entity to its default state.
if (updateDefintion.HasChanges())
{
writeModel.Add(new UpdateOneModel<TEntity>(filter, updateDefintion));
}
}
else if (entry.State == EntityEntryState.Deleted)
{
var idFieldValue = EntityDefinition.GetIdValue(entry.Entity);
var filter = Builders<TEntity>.Filter.Eq(idFieldName, idFieldValue);
writeModel.Add(new DeleteOneModel<TEntity>(filter));
}
}

return writeModel;
}

public void Write(IEntityCollection<TEntity> entityCollection)
{
var writeModel = BuildWriteModel(entityCollection);
var writeModel = writeCommands.SelectMany(c => c.GetModel());

if (writeModel.Any())
{
Expand All @@ -76,7 +40,7 @@ public void Write(IEntityCollection<TEntity> entityCollection)
Connection.DiagnosticListener.OnNext(new WriteDiagnosticCommand<TEntity>
{
CommandId = commandId,
Source = $"{nameof(EntityWriter<TEntity>)}.{nameof(Write)}",
Source = $"{nameof(CommandWriter<TEntity>)}.{nameof(Write)}",
CommandState = CommandState.Start,
EntityType = typeof(TEntity),
WriteModel = writeModel
Expand All @@ -85,7 +49,7 @@ public void Write(IEntityCollection<TEntity> entityCollection)
Connection.DiagnosticListener.OnNext(new WriteDiagnosticCommand<TEntity>
{
CommandId = commandId,
Source = $"{nameof(EntityWriter<TEntity>)}.{nameof(Write)}",
Source = $"{nameof(CommandWriter<TEntity>)}.{nameof(Write)}",
CommandState = CommandState.End,
EntityType = typeof(TEntity),
WriteModel = writeModel
Expand All @@ -96,7 +60,7 @@ public void Write(IEntityCollection<TEntity> entityCollection)
Connection.DiagnosticListener.OnNext(new WriteDiagnosticCommand<TEntity>
{
CommandId = commandId,
Source = $"{nameof(EntityWriter<TEntity>)}.{nameof(Write)}",
Source = $"{nameof(CommandWriter<TEntity>)}.{nameof(Write)}",
CommandState = CommandState.Error,
EntityType = typeof(TEntity),
WriteModel = writeModel
Expand All @@ -108,9 +72,9 @@ public void Write(IEntityCollection<TEntity> entityCollection)
}
}

public async Task WriteAsync(IEntityCollection<TEntity> entityCollection, CancellationToken cancellationToken = default(CancellationToken))
public async Task WriteAsync(IEnumerable<IWriteCommand<TEntity>> writeCommands, CancellationToken cancellationToken = default(CancellationToken))
{
var writeModel = BuildWriteModel(entityCollection);
var writeModel = writeCommands.SelectMany(c => c.GetModel());

cancellationToken.ThrowIfCancellationRequested();

Expand All @@ -122,7 +86,7 @@ public void Write(IEntityCollection<TEntity> entityCollection)
Connection.DiagnosticListener.OnNext(new WriteDiagnosticCommand<TEntity>
{
CommandId = commandId,
Source = $"{nameof(EntityWriter<TEntity>)}.{nameof(WriteAsync)}",
Source = $"{nameof(CommandWriter<TEntity>)}.{nameof(WriteAsync)}",
CommandState = CommandState.Start,
EntityType = typeof(TEntity),
WriteModel = writeModel
Expand All @@ -131,7 +95,7 @@ public void Write(IEntityCollection<TEntity> entityCollection)
Connection.DiagnosticListener.OnNext(new WriteDiagnosticCommand<TEntity>
{
CommandId = commandId,
Source = $"{nameof(EntityWriter<TEntity>)}.{nameof(WriteAsync)}",
Source = $"{nameof(CommandWriter<TEntity>)}.{nameof(WriteAsync)}",
CommandState = CommandState.End,
EntityType = typeof(TEntity),
WriteModel = writeModel
Expand All @@ -142,7 +106,7 @@ public void Write(IEntityCollection<TEntity> entityCollection)
Connection.DiagnosticListener.OnNext(new WriteDiagnosticCommand<TEntity>
{
CommandId = commandId,
Source = $"{nameof(EntityWriter<TEntity>)}.{nameof(WriteAsync)}",
Source = $"{nameof(CommandWriter<TEntity>)}.{nameof(WriteAsync)}",
CommandState = CommandState.Error,
EntityType = typeof(TEntity),
WriteModel = writeModel
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Text;
using MongoDB.Driver;
using MongoFramework.Infrastructure.Mapping;

namespace MongoFramework.Infrastructure.Commands
{
public static class EntityDefinitionExtensions
{
public static FilterDefinition<TEntity> CreateIdFilterFromEntity<TEntity>(this IEntityDefinition definition, TEntity entity)
{
return Builders<TEntity>.Filter.Eq(definition.GetIdName(), definition.GetIdValue(entity));
}
public static FilterDefinition<TEntity> CreateIdFilter<TEntity>(this IEntityDefinition definition, object entityId)
{
return Builders<TEntity>.Filter.Eq(definition.GetIdName(), entityId);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Text;
using MongoDB.Driver;
using MongoFramework.Infrastructure.DefinitionHelpers;
using MongoFramework.Infrastructure.Mapping;

namespace MongoFramework.Infrastructure.Commands
{
public class RemoveEntityByIdCommand<TEntity> : IWriteCommand<TEntity> where TEntity : class
{
private object EntityId { get; }

public RemoveEntityByIdCommand(object entityId)
{
EntityId = entityId;
}

public IEnumerable<WriteModel<TEntity>> GetModel()
{
var definition = EntityMapping.GetOrCreateDefinition(typeof(TEntity));
yield return new DeleteOneModel<TEntity>(definition.CreateIdFilter<TEntity>(EntityId));
}
}
}
25 changes: 25 additions & 0 deletions src/MongoFramework/Infrastructure/Commands/RemoveEntityCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Text;
using MongoDB.Driver;
using MongoFramework.Infrastructure.DefinitionHelpers;
using MongoFramework.Infrastructure.Mapping;

namespace MongoFramework.Infrastructure.Commands
{
public class RemoveEntityCommand<TEntity> : IWriteCommand<TEntity> where TEntity : class
{
private EntityEntry<TEntity> EntityEntry { get; }

public RemoveEntityCommand(EntityEntry<TEntity> entityEntry)
{
EntityEntry = entityEntry;
}

public IEnumerable<WriteModel<TEntity>> GetModel()
{
var definition = EntityMapping.GetOrCreateDefinition(typeof(TEntity));
yield return new DeleteOneModel<TEntity>(definition.CreateIdFilterFromEntity(EntityEntry.Entity));
}
}
}
21 changes: 10 additions & 11 deletions src/MongoFramework/Infrastructure/Commands/UpdateEntityCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using System.Collections.Generic;
using System.Text;
using MongoDB.Driver;
using MongoFramework.Infrastructure.DefinitionHelpers;
using MongoFramework.Infrastructure.Mapping;

namespace MongoFramework.Infrastructure.Commands
{
Expand All @@ -16,18 +18,15 @@ public UpdateEntityCommand(EntityEntry<TEntity> entityEntry)

public IEnumerable<WriteModel<TEntity>> GetModel()
{
//var idFieldValue = EntityMapper.GetIdValue(entry.Entity);
//var filter = Builders<TEntity>.Filter.Eq(idFieldName, idFieldValue);
//var updateDefintion = UpdateDefinitionHelper.CreateFromDiff<TEntity>(EntityEntry.OriginalValues, EntityEntry.CurrentValues);
var definition = EntityMapping.GetOrCreateDefinition(typeof(TEntity));
var updateDefintion = UpdateDefinitionHelper.CreateFromDiff<TEntity>(EntityEntry.OriginalValues, EntityEntry.CurrentValues);

////MongoDB doesn't like it if an UpdateDefinition is empty.
////This is primarily to work around a mutation that may set an entity to its default state.
//if (updateDefintion.HasChanges())
//{
// writeModel.Add(new UpdateOneModel<TEntity>(filter, updateDefintion));
//}
//yield return new InsertOneModel<TEntity>(EntityEntry.Entity);
yield break;
//MongoDB doesn't like it if an UpdateDefinition is empty.
//This is primarily to work around a mutation that may set an entity to its default state.
if (updateDefintion.HasChanges())
{
yield return new UpdateOneModel<TEntity>(definition.CreateIdFilterFromEntity(EntityEntry.Entity), updateDefintion);
}
}
}
}
Loading

0 comments on commit 282f843

Please sign in to comment.