Skip to content

Commit

Permalink
updated readme.md and last service and plugin changes.
Browse files Browse the repository at this point in the history
  • Loading branch information
leonardoporro committed Nov 11, 2016
1 parent 70ab152 commit 920ebdf
Show file tree
Hide file tree
Showing 14 changed files with 262 additions and 119 deletions.
81 changes: 46 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,25 @@

Loads and saves entire detached entity graphs (the entity with their child entities and lists).
Inspired by [GraphDiff](https://github.com/refactorthis/GraphDiff).
The idea is also add some plugins to simplificate some repetitive tasks, like auditing and pagination.

WARNING: This is a development project, not guaranteed to work for your particular purposes.
If you'd like to participate or need some support, please drop me an email: mail@leonardoporro.com.ar
or join https://github.com/leonardoporro/EntityFrameworkCore.Detached.
Thanks in advance for your help!

Features:
- 1.0.0-alpha12:
* Added projections to LoadAsync and LoadPageAsync.
- 1.0.0-alpha10:
* LoadPageAsync: Loads paged results (with filtered and sorted items) automatically.

- 1.0.0-alpha8:
* DeleteAsync: Deletes an entity by key (pending since alpha3).
* LoadAsync error fixed when the key value passed and the property type was not matching.

- 1.0.0-alpha5:
* Audit. Supports automatic setting properties marked as [CreatedBy] [CreatedDate]
* [ModifiedBy] and [ModifiedDate]. An instance of IDetachedSessionInfoProvider is needed to get the current logged user name.
* Supports dependency injection.

- 1.0.0-alpha4
* [ManyToMany] PATCH to work with a simple 1-level many to many association such as User -> Roles.
(while we wait for the feature).

- 1.0.0-alpha3
* [Owned] and [Associated] attributes to define scope when loading a graph.
* LoadAsync<TEntity>(key): loads a single detached root by its key.
* LoadAsync<TEntity>(filter): loads a single detached root filtered by an expression.
* UpdateAsync: persists a detached root (with its children) to the database.

Usage looks like this:
# Features
* Loading entity graphs.
* Updating entity graphs.
* Plugins:
- Audit: Sets CreatedBy, CreatedDate, ModifiedBy, ModifiedDate.
- ManyToMany Patch: Copies an intermediate table back and forth to a given collection
simulating a skip-level navigation. Will be removed when EF Core supports many to many relations.
- PartialKey: Allows adding [PKey(n)] attribute to define composite keys.
- Pagination: Loads pages of data in an homogeneus way.
- [Here your own plugin!].
# Basic Usage
```csharp
// Create a detached wrapper for your context.
IDetachedContext<YourDbContext> context = new DetachedContext<YourDbContext>(new YourDbContext());
Expand All @@ -61,6 +48,39 @@ Company company = await context.LoadAsync<Company>(1);

```

History:
- 1.0.0-preview1:
* New service oriented architecture.
* Plugins!
* Better many to many patch.

- 1.0.0-alpha12:
* Added projections to LoadAsync and LoadPageAsync.

- 1.0.0-alpha10:
* LoadPageAsync: Loads paged results (with filtered and sorted items) automatically.

- 1.0.0-alpha8:
* DeleteAsync: Deletes an entity by key (pending since alpha3).
* LoadAsync error fixed when the key value passed and the property type was not matching.

- 1.0.0-alpha5:
* Audit. Supports automatic setting properties marked as [CreatedBy] [CreatedDate]
* [ModifiedBy] and [ModifiedDate]. An instance of IDetachedSessionInfoProvider is needed to get the current logged user name.
* Supports dependency injection.

- 1.0.0-alpha4
* [ManyToMany] PATCH to work with a simple 1-level many to many association such as User -> Roles.
(while we wait for the feature).

- 1.0.0-alpha3
* [Owned] and [Associated] attributes to define scope when loading a graph.
* LoadAsync<TEntity>(key): loads a single detached root by its key.
* LoadAsync<TEntity>(filter): loads a single detached root filtered by an expression.
* UpdateAsync: persists a detached root (with its children) to the database.



# Configuration:
To be able to process the [Owned] and [Associated] attributes, a custom convention builder is needed.
You can replace the ICoreConventionSetBuilder service by overriding OnConfiguring in your DbContext:
Expand All @@ -81,14 +101,5 @@ Or if you are using your own service collection:
.BuildServiceProvider();
```

# Build

In order to get alpha EF Core dependencies, you would need to add myget.org.
Go to menu Tools -> Options -> NuGet Package Manager -> Package Sources and add this source:
https://dotnet.myget.org/F/aspnetcore-dev/
Then you will be able to download EF Core 1.1.0-alpha...

Also .NET Core 1.0.1 - VS 2015 Tooling Preview 2 (https://go.microsoft.com/fwlink/?LinkID=827546) is required.

# Nuget package:
https://www.nuget.org/packages/EntityFrameworkCore.Detached/
58 changes: 30 additions & 28 deletions src/EntityFrameworkCore.Detached/DetachedContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,9 @@ public class DetachedContext<TDbContext> : IDetachedContext<TDbContext>, IDispos

TDbContext _dbContext;
IServiceProvider _serviceProvider;
ILoadServices _loadServices;
IUpdateServices _updateServices;
IEventManager _eventManager;
IPluginManager _pluginManager;
IDbContextOptions _dbContextOptions;
DetachedOptionsExtension _detachedOptions;
IDetachedServices _detachedServices;

#endregion

Expand All @@ -47,24 +44,21 @@ public DetachedContext(TDbContext dbContext)
_dbContext = dbContext;
_serviceProvider = ((IInfrastructure<IServiceProvider>)_dbContext).Instance;

IServiceProvider _scopedProvider = _serviceProvider.GetRequiredService<IServiceScopeFactory>()
.CreateScope()
.ServiceProvider;
// create a scope for services and plugins.
IServiceProvider scopedProvider = _serviceProvider.GetRequiredService<IServiceScopeFactory>()
.CreateScope()
.ServiceProvider;

IDetachedServices currentDetachedContext = _scopedProvider.GetService<IDetachedServices>();
currentDetachedContext.Initialize(this);
// get update and load services and event manager.
_detachedServices = scopedProvider.GetService<IDetachedServices>();
_detachedServices.Initialize(this, scopedProvider);

// detached services.
_eventManager = _scopedProvider.GetService<IEventManager>();
_pluginManager = _scopedProvider.GetService<IPluginManager>();
_loadServices = _scopedProvider.GetService<ILoadServices>();
_updateServices = _scopedProvider.GetService<IUpdateServices>();

// ef options.
// get ef options.
_dbContextOptions = _serviceProvider.GetService<IDbContextOptions>();
_detachedOptions = _dbContextOptions.FindExtension<DetachedOptionsExtension>();

_pluginManager.EnableAllPlugins();
// load plugins.
_detachedServices.PluginManager.Initialize();
}

#endregion
Expand All @@ -91,32 +85,40 @@ public IEventManager Events
{
get
{
return _eventManager;
return _detachedServices.EventManager;
}
}

public IPluginManager Plugins
{
get
{
return _detachedServices.PluginManager;
}
}

#endregion

public IQueryable<TEntity> GetBaseQuery<TEntity>() where TEntity : class
{
return _loadServices.GetBaseQuery<TEntity>();
return _detachedServices.LoadServices.GetBaseQuery<TEntity>();
}

public async Task<TEntity> LoadAsync<TEntity>(params object[] key) where TEntity : class
{
return await _loadServices.LoadAsync<TEntity>(key);
return await _detachedServices.LoadServices.LoadAsync<TEntity>(key);
}

public async Task<List<TEntity>> LoadAsync<TEntity>(Func<IQueryable<TEntity>, IQueryable<TEntity>> queryConfig) where TEntity : class
{
return await _loadServices.LoadAsync<TEntity>(queryConfig);
return await _detachedServices.LoadServices.LoadAsync<TEntity>(queryConfig);
}

public async Task<List<TResult>> LoadAsync<TEntity, TResult>(Func<IQueryable<TEntity>, IQueryable<TResult>> queryConfig)
where TEntity : class
where TResult : class
{
return await _loadServices.LoadAsync<TEntity, TResult>(queryConfig);
return await _detachedServices.LoadServices.LoadAsync<TEntity, TResult>(queryConfig);
}

public virtual async Task<TEntity> UpdateAsync<TEntity>(TEntity root)
Expand All @@ -126,15 +128,15 @@ public virtual async Task<TEntity> UpdateAsync<TEntity>(TEntity root)
bool autoDetectChanges = _dbContext.ChangeTracker.AutoDetectChangesEnabled;
_dbContext.ChangeTracker.AutoDetectChangesEnabled = false;

TEntity persisted = await _loadServices.LoadPersisted<TEntity>(root);
TEntity persisted = await _detachedServices.LoadServices.LoadPersisted<TEntity>(root);
if (persisted == null) // add new entity.
{
persisted = (TEntity)_updateServices.Add(root).Entity;
persisted = (TEntity)_detachedServices.UpdateServices.Add(root).Entity;
}
else
{
persisted = (TEntity)Events.OnRootLoaded(persisted, _dbContext).Root; // entity to merge has been loaded.
_updateServices.Merge(root, persisted); // merge existing entity.
_detachedServices.UpdateServices.Merge(root, persisted); // merge existing entity.
}
// re-enable autodetect changes.
_dbContext.ChangeTracker.AutoDetectChangesEnabled = autoDetectChanges;
Expand All @@ -149,9 +151,9 @@ public virtual async Task DeleteAsync<TEntity>(params object[] keyValues)
bool autoDetectChanges = _dbContext.ChangeTracker.AutoDetectChangesEnabled;
_dbContext.ChangeTracker.AutoDetectChangesEnabled = false;

TEntity persisted = await _loadServices.LoadPersisted<TEntity>(keyValues);
TEntity persisted = await _detachedServices.LoadServices.LoadPersisted<TEntity>(keyValues);
if (persisted != null)
_updateServices.Delete(persisted);
_detachedServices.UpdateServices.Delete(persisted);

// re-enable autodetect changes.
_dbContext.ChangeTracker.AutoDetectChangesEnabled = true;
Expand All @@ -177,7 +179,7 @@ public void Dispose()
{
_dbContext.Dispose();
_dbContext = null;
_pluginManager.Dispose();
_detachedServices.Dispose();
}
}
}
Expand Down
56 changes: 29 additions & 27 deletions src/EntityFrameworkCore.Detached/DetachedSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,42 +26,44 @@ public static IServiceCollection AddDetachedEntityFramework(this IServiceCollect
.AddScoped<IEventManager, EventManager>()
.AddScoped<IPluginManager, PluginManager>()
.AddScoped<IKeyServicesFactory, KeyServicesFactory>()
.AddSingleton<IKeyServicesCache, KeyServicesCache>()
.AddScoped<IEntryFinder, EntryFinder>()
.AddScoped<ILoadServices, LoadServices>()
.AddScoped<IUpdateServices, UpdateServices>()
.AddScoped<DbContext>(sp => sp.GetRequiredService<IDetachedServices>().DetachedContext.DbContext);
.AddScoped(sp => sp.GetRequiredService<IDetachedServices>().DetachedContext.DbContext)
.AddScoped(sp => sp.GetRequiredService<IDetachedServices>().DetachedContext);

return serviceCollection;
}

public static IServiceCollection AddConventionBuilder<TConventionBuilder>(this IServiceCollection serviceCollection)
where TConventionBuilder : class, ICustomConventionBuilder
{
serviceCollection.AddScoped<ICustomConventionBuilder, TConventionBuilder>();
return serviceCollection;
}
public static IServiceCollection AddConventionBuilder<TConventionBuilder>(this IServiceCollection serviceCollection)
where TConventionBuilder : class, ICustomConventionBuilder
{
serviceCollection.AddScoped<ICustomConventionBuilder, TConventionBuilder>();
return serviceCollection;
}

public static DbContextOptionsBuilder UseDetached(this DbContextOptionsBuilder builder, Action<DetachedOptionsExtension> options = null)
{
DetachedOptionsExtension detachedOptions = new DetachedOptionsExtension();
options?.Invoke(detachedOptions);
((IDbContextOptionsBuilderInfrastructure)builder).AddOrUpdateExtension(detachedOptions);
return builder;
}
public static DbContextOptionsBuilder UseDetached(this DbContextOptionsBuilder builder, Action<DetachedOptionsExtension> options = null)
{
DetachedOptionsExtension detachedOptions = new DetachedOptionsExtension();
options?.Invoke(detachedOptions);
((IDbContextOptionsBuilderInfrastructure)builder).AddOrUpdateExtension(detachedOptions);
return builder;
}

public static DetachedOptionsExtension AddSingleton<TService>(this DetachedOptionsExtension options, TService instance)
where TService : class
{
options.DetachedServices.AddSingleton(instance);
return options;
}
public static DetachedOptionsExtension AddSingleton<TService>(this DetachedOptionsExtension options, TService instance)
where TService : class
{
options.DetachedServices.AddSingleton(instance);
return options;
}

public static DetachedOptionsExtension AddScoped<TService, TImplementation>(this DetachedOptionsExtension options)
where TService : class
where TImplementation : class, TService
{
options.DetachedServices.AddScoped<TService, TImplementation>();
return options;
public static DetachedOptionsExtension AddScoped<TService, TImplementation>(this DetachedOptionsExtension options)
where TService : class
where TImplementation : class, TService
{
options.DetachedServices.AddScoped<TService, TImplementation>();
return options;
}
}
}
}
3 changes: 3 additions & 0 deletions src/EntityFrameworkCore.Detached/IDetachedContext.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using EntityFrameworkCore.Detached.Events;
using EntityFrameworkCore.Detached.Plugins;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Metadata;
Expand All @@ -21,6 +22,8 @@ public interface IDetachedContext : IDisposable
/// </summary>
IEventManager Events { get; }

IPluginManager Plugins { get; }

DbContext DbContext { get; }

/// <summary>
Expand Down
5 changes: 5 additions & 0 deletions src/EntityFrameworkCore.Detached/Plugins/DetachedPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ public virtual bool IsEnabled

public virtual int Priority { get; set; }

public virtual void Initialize()
{
IsEnabled = true;
}

protected virtual void OnEnabled()
{

Expand Down
2 changes: 2 additions & 0 deletions src/EntityFrameworkCore.Detached/Plugins/IDetachedPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ public interface IDetachedPlugin : IDisposable
int Priority { get; }

string Name { get; }

void Initialize();
}
}
6 changes: 4 additions & 2 deletions src/EntityFrameworkCore.Detached/Plugins/IPluginManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ public interface IPluginManager

IDetachedPlugin this[string name] { get; }

void EnableAllPlugins();
void Initialize();

void DisableAllPlugins();
void EnableAll();

void DisableAll();

void Dispose();
}
Expand Down
Loading

0 comments on commit 920ebdf

Please sign in to comment.