mirror of
https://github.com/esiur/esiur-dotnet.git
synced 2026-04-04 12:28:21 +00:00
Layout
This commit is contained in:
73
Stores/Esiur.Stores.EntityCore/EntityResource.cs
Normal file
73
Stores/Esiur.Stores.EntityCore/EntityResource.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2020 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Esiur.Core;
|
||||
using Esiur.Resource;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Esiur.Stores.EntityCore;
|
||||
|
||||
public class EntityResource : IResource
|
||||
{
|
||||
//[NotMapped]
|
||||
//internal object _PrimaryId;
|
||||
|
||||
public event DestroyedEvent OnDestroy;
|
||||
//public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
[NotMapped]
|
||||
public Instance Instance { get; set; }
|
||||
|
||||
public EntityResource()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected virtual void Create()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public AsyncReply<bool> Trigger(ResourceTrigger trigger)
|
||||
{
|
||||
if (trigger == ResourceTrigger.Initialize)
|
||||
Create();
|
||||
|
||||
return new AsyncReply<bool>(true);
|
||||
}
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
OnDestroy?.Invoke(this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
303
Stores/Esiur.Stores.EntityCore/EntityStore.cs
Normal file
303
Stores/Esiur.Stores.EntityCore/EntityStore.cs
Normal file
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2020 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using Esiur.Core;
|
||||
using Esiur.Data;
|
||||
using Esiur.Resource;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Esiur.Proxy;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using System.Reflection;
|
||||
using Esiur.Security.Authority;
|
||||
using System.Collections;
|
||||
using Esiur.Data.Types;
|
||||
|
||||
namespace Esiur.Stores.EntityCore;
|
||||
public class EntityStore : IStore
|
||||
{
|
||||
public Instance Instance { get; set; }
|
||||
|
||||
bool initialized = false;
|
||||
|
||||
|
||||
public bool Initialized => initialized;
|
||||
|
||||
public event DestroyedEvent OnDestroy;
|
||||
|
||||
Dictionary<Type, Dictionary<object, WeakReference>> DB = new Dictionary<Type, Dictionary<object, WeakReference>>();
|
||||
object DBLock = new object();
|
||||
|
||||
Dictionary<string, EntityTypeInfo> TypesByName = new Dictionary<string, EntityTypeInfo>();
|
||||
internal Dictionary<Type, EntityTypeInfo> TypesByType = new Dictionary<Type, EntityTypeInfo>();
|
||||
|
||||
[Attribute]
|
||||
public Func<DbContext> Getter { get; set; }
|
||||
|
||||
|
||||
public AsyncReply<IResource> Get(string path)
|
||||
{
|
||||
var p = path.Split('/');
|
||||
var ti = TypesByName[p[0]];
|
||||
var id = Convert.ChangeType(p[1], ti.PrimaryKey.PropertyType);
|
||||
|
||||
// Get db
|
||||
using (var db = Getter())
|
||||
{
|
||||
|
||||
var res = db.Find(ti.Type.ClrType, id) as IResource;
|
||||
|
||||
|
||||
if (res == null)
|
||||
{
|
||||
return new AsyncReply<IResource>(null);
|
||||
|
||||
//var rt = new AsyncReply<IResource>();
|
||||
//rt.TriggerError(new AsyncException(ErrorType.Management,
|
||||
// (ushort)ExceptionCode.ResourceNotFound, "Resource not found."));
|
||||
//return rt;
|
||||
}
|
||||
|
||||
|
||||
// load navigation properties
|
||||
var ent = db.Entry(res);
|
||||
|
||||
foreach (var rf in ent.References)
|
||||
{
|
||||
rf.Load();
|
||||
}
|
||||
|
||||
foreach(var col in ent.Collections)
|
||||
{
|
||||
col.Load();
|
||||
}
|
||||
|
||||
//var refs = ent.References.ToList();
|
||||
|
||||
db.Dispose();
|
||||
|
||||
return new AsyncReply<IResource>(res);
|
||||
}
|
||||
}
|
||||
|
||||
public AsyncReply<bool> Put(IResource resource, string path)
|
||||
{
|
||||
if (resource == this)
|
||||
return new AsyncReply<bool>(true);
|
||||
|
||||
var type = ResourceProxy.GetBaseType(resource);
|
||||
|
||||
|
||||
var eid = TypesByType[type].PrimaryKey.GetValue(resource);
|
||||
|
||||
lock (DBLock)
|
||||
{
|
||||
if (DB[type].ContainsKey(eid))
|
||||
DB[type].Remove(eid);
|
||||
|
||||
DB[type].Add(eid, new WeakReference(resource));
|
||||
}
|
||||
|
||||
return new AsyncReply<bool>(true);
|
||||
}
|
||||
|
||||
public IResource GetById(Type type, object id)
|
||||
{
|
||||
if (!initialized)
|
||||
throw new Exception("Store is not initialized. Make sure the Warehouse is open.");
|
||||
|
||||
lock (DBLock)
|
||||
{
|
||||
if (!DB[type].ContainsKey(id))
|
||||
return null;
|
||||
|
||||
if (!DB[type][id].IsAlive)
|
||||
return null;
|
||||
|
||||
return DB[type][id].Target as IResource;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//public T CreateDB()
|
||||
//{
|
||||
|
||||
//}
|
||||
|
||||
//DbContext dbContext;
|
||||
//[Attribute]
|
||||
//public DbContext DbContext { get; set; }
|
||||
|
||||
public string Link(IResource resource)
|
||||
{
|
||||
var type = ResourceProxy.GetBaseType(resource.GetType());
|
||||
|
||||
var id = TypesByType[type].PrimaryKey.GetValue(resource);
|
||||
//DbContext.Model.FindEntityType(type).DisplayName();
|
||||
|
||||
|
||||
// DbContext.Model.FindEntityType(type).DisplayName
|
||||
//var entityType = DbContext.Model.FindEntityType(type);
|
||||
//var id = entityType.FindPrimaryKey().Properties
|
||||
// .FirstOrDefault()?.PropertyInfo
|
||||
// .GetValue(resource);
|
||||
// var id = Types
|
||||
|
||||
if (id != null)
|
||||
return this.Instance.Name + "/" + type.Name + "/" + id.ToString();
|
||||
else
|
||||
return this.Instance.Name + "/" + type.Name;
|
||||
}
|
||||
|
||||
public bool Record(IResource resource, string propertyName, object value, ulong? age, DateTime? dateTime)
|
||||
{
|
||||
return true;
|
||||
//throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool Modify(IResource resource, PropertyDef propertyDef, object value, ulong? age, DateTime? dateTime)
|
||||
{
|
||||
return true;
|
||||
//throw new NotImplementedException();
|
||||
}
|
||||
|
||||
|
||||
public AsyncReply<bool> AddChild(IResource parent, IResource child)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public AsyncReply<bool> RemoveChild(IResource parent, IResource child)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public AsyncReply<bool> AddParent(IResource child, IResource parent)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public AsyncReply<bool> RemoveParent(IResource child, IResource parent)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public AsyncBag<T> Children<T>(IResource resource, string name) where T : IResource
|
||||
{
|
||||
return new AsyncBag<T>(null);
|
||||
}
|
||||
|
||||
public AsyncBag<T> Parents<T>(IResource resource, string name) where T : IResource
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public AsyncReply<KeyList<PropertyDef, PropertyValue[]>> GetRecord(IResource resource, DateTime fromDate, DateTime toDate)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
internal DbContextOptions Options { get; set; }
|
||||
|
||||
public AsyncReply<bool> Trigger(ResourceTrigger trigger)
|
||||
{
|
||||
if (trigger == ResourceTrigger.Initialize)// SystemInitialized && DbContext != null)
|
||||
{
|
||||
|
||||
if (Getter == null)
|
||||
throw new Exception("Getter is not set for the store.");
|
||||
// DbContextProvider = () => Activator.CreateInstance(Options.Options.ContextType, Options.Options) as DbContext;
|
||||
|
||||
|
||||
ReloadModel();
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
return new AsyncReply<bool>(true);
|
||||
}
|
||||
|
||||
public void ReloadModel()
|
||||
{
|
||||
|
||||
TypesByName.Clear();
|
||||
TypesByType.Clear();
|
||||
|
||||
var context = Getter();
|
||||
|
||||
var types = context.Model.GetEntityTypes();
|
||||
foreach (var t in types)
|
||||
{
|
||||
var ti = new EntityTypeInfo()
|
||||
{
|
||||
Name = t.ClrType.Name,
|
||||
PrimaryKey = t.FindPrimaryKey().Properties.FirstOrDefault()?.PropertyInfo,
|
||||
Type = t,
|
||||
//Getter = getter
|
||||
};
|
||||
|
||||
TypesByName.Add(t.ClrType.Name, ti);
|
||||
TypesByType.Add(t.ClrType, ti);
|
||||
|
||||
if (!DB.ContainsKey(t.ClrType))
|
||||
DB.Add(t.ClrType, new Dictionary<object, WeakReference>());
|
||||
}
|
||||
}
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
OnDestroy?.Invoke(this);
|
||||
}
|
||||
|
||||
public AsyncReply<bool> Remove(IResource resource)
|
||||
{
|
||||
var type = ResourceProxy.GetBaseType(resource);
|
||||
|
||||
var eid = TypesByType[type].PrimaryKey.GetValue(resource);
|
||||
|
||||
lock (DBLock)
|
||||
{
|
||||
if (DB[type].ContainsKey(eid))
|
||||
{
|
||||
DB[type].Remove(eid);
|
||||
return new AsyncReply<bool>(true);
|
||||
}
|
||||
}
|
||||
|
||||
return new AsyncReply<bool>(false);
|
||||
}
|
||||
|
||||
public AsyncReply<bool> Remove(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public AsyncReply<bool> Move(IResource resource, string newPath)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
16
Stores/Esiur.Stores.EntityCore/EntityTypeInfo.cs
Normal file
16
Stores/Esiur.Stores.EntityCore/EntityTypeInfo.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Stores.EntityCore;
|
||||
|
||||
struct EntityTypeInfo
|
||||
{
|
||||
public string Name;
|
||||
public IEntityType Type;
|
||||
public PropertyInfo PrimaryKey;
|
||||
// public Func<DbContext> Getter;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<AssemblyName>Esiur.Stores.EntityCore</AssemblyName>
|
||||
<Authors>Ahmed Kh. Zamil</Authors>
|
||||
<Company>Esiur Foundation</Company>
|
||||
<Description>Esiur for Entity Framework Core</Description>
|
||||
<Product>Esiur Entity Framework Extension</Product>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<PackageId>Esiur.Stores.EntityCore</PackageId>
|
||||
<Version>1.3.4</Version>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="EntityResource.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="EntityResource.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.10" />
|
||||
<PackageReference Include="System.Collections" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Libraries\Esiur\Esiur.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
122
Stores/Esiur.Stores.EntityCore/EsiurExtensionOptions.cs
Normal file
122
Stores/Esiur.Stores.EntityCore/EsiurExtensionOptions.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2020 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal;
|
||||
using Microsoft.EntityFrameworkCore.Utilities;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using System.Reflection;
|
||||
using Esiur.Proxy;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Esiur.Resource;
|
||||
|
||||
namespace Esiur.Stores.EntityCore;
|
||||
|
||||
public class EsiurExtensionOptions : IDbContextOptionsExtension
|
||||
{
|
||||
|
||||
//public Dictionary<Type, PropertyInfo> Cache { get; } = new Dictionary<Type, PropertyInfo>();
|
||||
//public void AddType(IEntityType type)
|
||||
//{
|
||||
// if (!Cache.ContainsKey(type.ClrType))
|
||||
// Cache.Add(type.ClrType, type.FindPrimaryKey().Properties[0].PropertyInfo);
|
||||
//}
|
||||
|
||||
|
||||
|
||||
DbContextOptionsExtensionInfo _info;
|
||||
EntityStore _store;
|
||||
Warehouse _warehouse;
|
||||
|
||||
public DbContextOptionsExtensionInfo Info => _info;
|
||||
|
||||
public EntityStore Store => _store;
|
||||
|
||||
public Warehouse Warehouse => _warehouse;
|
||||
|
||||
public void ApplyServices(IServiceCollection services)
|
||||
{
|
||||
// services.AddEntityFrameworkProxies();
|
||||
|
||||
new EntityFrameworkServicesBuilder(services)
|
||||
.TryAdd<IConventionSetPlugin, EsiurPlugin>();
|
||||
}
|
||||
|
||||
public void Validate(IDbContextOptions options)
|
||||
{
|
||||
var internalServiceProvider = options.FindExtension<CoreOptionsExtension>()?.InternalServiceProvider;
|
||||
if (internalServiceProvider != null)
|
||||
{
|
||||
var scope = internalServiceProvider.CreateScope();
|
||||
var conventionPlugins = scope.ServiceProvider.GetService<IEnumerable<IConventionSetPlugin>>();
|
||||
if (conventionPlugins?.Any(s => s is EsiurPlugin) == false)
|
||||
{
|
||||
throw new InvalidOperationException("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public EsiurExtensionOptions(EntityStore store)
|
||||
{
|
||||
_info = new ExtensionInfo(this);
|
||||
_store = store;
|
||||
_warehouse = store.Instance.Warehouse;
|
||||
}
|
||||
|
||||
|
||||
private sealed class ExtensionInfo : DbContextOptionsExtensionInfo
|
||||
{
|
||||
|
||||
public ExtensionInfo(IDbContextOptionsExtension extension)
|
||||
: base(extension)
|
||||
{
|
||||
}
|
||||
|
||||
private new EsiurExtensionOptions Extension
|
||||
=> (EsiurExtensionOptions)base.Extension;
|
||||
|
||||
public override bool IsDatabaseProvider => false;
|
||||
|
||||
public override string LogFragment => "Esiur";
|
||||
|
||||
public override int GetServiceProviderHashCode() => 2312;
|
||||
|
||||
public override void PopulateDebugInfo(IDictionary<string, string> debugInfo)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
210
Stores/Esiur.Stores.EntityCore/EsiurExtensions.cs
Normal file
210
Stores/Esiur.Stores.EntityCore/EsiurExtensions.cs
Normal file
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2020 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using Esiur.Core;
|
||||
using Esiur.Data;
|
||||
using Esiur.Misc;
|
||||
using Esiur.Proxy;
|
||||
using Esiur.Resource;
|
||||
using Esiur.Security.Permissions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Stores.EntityCore;
|
||||
|
||||
public static class EsiurExtensions
|
||||
{
|
||||
//public static T CreateResource<T>(this DbContext dbContext, object properties = null) where T:class,IResource
|
||||
//{
|
||||
// return dbContext.GetInfrastructure().CreateResource<T>(properties);
|
||||
|
||||
//}
|
||||
|
||||
public static DbSet<object> SetByType(this DbContext context, Type t)
|
||||
{
|
||||
return (DbSet<object>)context.GetType().GetMethod("Set").MakeGenericMethod(t).Invoke(context, new object[0]);
|
||||
}
|
||||
|
||||
public static T AddResource<T>(this DbSet<T> dbSet, T resource) where T : class, IResource
|
||||
=> AddResourceAsync(dbSet, resource).Wait();
|
||||
|
||||
public static async AsyncReply<T> AddResourceAsync<T>(this DbSet<T> dbSet, T resource) where T : class, IResource
|
||||
{
|
||||
var options = dbSet.GetInfrastructure().GetService<IDbContextOptions>().FindExtension<EsiurExtensionOptions>();
|
||||
var store = options.Store;
|
||||
|
||||
if (store == null)
|
||||
throw new Exception("Store not set, please call 'UseEsiur' on your DbContextOptionsBuilder.");
|
||||
|
||||
if (!store.Initialized)
|
||||
throw new Exception("Store not initialized. Make sure the Warehouse is open");
|
||||
|
||||
var manager = store.Instance.Managers.FirstOrDefault();// > 0 ? store.Instance.Managers.First() : null;
|
||||
|
||||
//var db = dbSet.GetService<ICurrentDbContext>().Context;
|
||||
|
||||
//var resource = dbSet.GetInfrastructure().CreateResource<T>(properties);
|
||||
//var resource = Warehouse.New<T>("", options.Store, null, null, null, properties);
|
||||
|
||||
var resType = typeof(T);
|
||||
var proxyType = ResourceProxy.GetProxy(resType);
|
||||
|
||||
|
||||
IResource res;
|
||||
|
||||
if (proxyType == resType)
|
||||
{
|
||||
res = resource;
|
||||
}
|
||||
else
|
||||
{
|
||||
res = Activator.CreateInstance(proxyType) as IResource;
|
||||
var ps = Map<string,object>.FromObject(resource);
|
||||
|
||||
foreach (var p in ps)
|
||||
{
|
||||
var mi = resType.GetMember(p.Key, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (mi != null)
|
||||
{
|
||||
if (mi is PropertyInfo)
|
||||
{
|
||||
var pi = mi as PropertyInfo;
|
||||
if (pi.CanWrite)
|
||||
{
|
||||
try
|
||||
{
|
||||
pi.SetValue(res, p.Value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Global.Log(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (mi is FieldInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
(mi as FieldInfo).SetValue(res, p.Value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Global.Log(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//await Warehouse.Put<T>("", null, null, null, null, properties);
|
||||
var entity = dbSet.Add((T)res);
|
||||
await entity.Context.SaveChangesAsync();
|
||||
|
||||
var id = store.TypesByType[typeof(T)].PrimaryKey.GetValue(resource);
|
||||
|
||||
await options.Warehouse.Put($"{store.Instance.Name}/{typeof(T).Name}/{id}", res, 0, manager);
|
||||
|
||||
return (T)res;
|
||||
}
|
||||
|
||||
//public static async AsyncReply<T> CreateResourceAsync<T>(this IServiceProvider serviceProvider, T properties = null) where T : class, IResource
|
||||
//{
|
||||
// var options = serviceProvider.GetService<IDbContextOptions>().FindExtension<EsiurExtensionOptions<T>>();
|
||||
|
||||
// var resource = await Warehouse.New<T>("", options.Store, null, null, null, properties);
|
||||
|
||||
// resource.Instance.Managers.AddRange(options.Store.Instance.Managers.ToArray());
|
||||
|
||||
// return resource;
|
||||
//}
|
||||
|
||||
//public static T CreateResource<T>(this IServiceProvider serviceProvider, object properties = null) where T : class, IResource
|
||||
// => CreateResourceAsync<T>(serviceProvider, properties).Wait();
|
||||
|
||||
public static DbContextOptionsBuilder UseEsiur(this DbContextOptionsBuilder optionsBuilder,
|
||||
EntityStore store,
|
||||
Func<DbContext> getter = null
|
||||
|
||||
//IServiceCollection services = null
|
||||
//string name = null,
|
||||
//IResource parent = null,
|
||||
//IPermissionsManager manager = null,
|
||||
//Func<DbContext> dbContextProvider = null
|
||||
)
|
||||
{
|
||||
var extension = optionsBuilder.Options.FindExtension<EsiurExtensionOptions>();
|
||||
|
||||
if (extension == null)
|
||||
{
|
||||
if (store == null)
|
||||
return optionsBuilder;
|
||||
|
||||
store.Options = optionsBuilder.Options;
|
||||
extension = new EsiurExtensionOptions(store);
|
||||
}
|
||||
|
||||
((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);
|
||||
|
||||
return optionsBuilder;
|
||||
|
||||
}
|
||||
|
||||
//public static DbContextOptionsBuilder<TContext> UseEsiur<TContext>(
|
||||
// this DbContextOptionsBuilder<TContext> optionsBuilder,
|
||||
// //DbContext context,
|
||||
// string name = null,
|
||||
// IResource parent = null,
|
||||
// IPermissionsManager manager = null,
|
||||
// Func<DbContext> dbContextProvider = null)
|
||||
// where TContext : DbContext
|
||||
//{
|
||||
|
||||
|
||||
// var extension = optionsBuilder.Options.FindExtension<EsiurExtensionOptions>();
|
||||
|
||||
// if (extension == null)
|
||||
// {
|
||||
// var store = Warehouse.New<EntityStore>(name, null, parent, manager, new { Options = optionsBuilder, DbContextProvider = dbContextProvider }).Wait();
|
||||
// extension = new EsiurExtensionOptions(store);
|
||||
// //store.Options = optionsBuilder;
|
||||
// //store.Options = extension;
|
||||
// //store.DbContext = context;
|
||||
// }
|
||||
|
||||
|
||||
// ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);
|
||||
|
||||
// return optionsBuilder;
|
||||
|
||||
//}
|
||||
|
||||
}
|
||||
58
Stores/Esiur.Stores.EntityCore/EsiurPlugin.cs
Normal file
58
Stores/Esiur.Stores.EntityCore/EsiurPlugin.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2020 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
|
||||
//using Microsoft.EntityFrameworkCore.Proxies.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Stores.EntityCore;
|
||||
public class EsiurPlugin : IConventionSetPlugin
|
||||
{
|
||||
private readonly IDbContextOptions _options;
|
||||
private readonly ProviderConventionSetBuilderDependencies _conventionSetBuilderDependencies;
|
||||
|
||||
public EsiurPlugin(
|
||||
IDbContextOptions options,
|
||||
ProviderConventionSetBuilderDependencies conventionSetBuilderDependencies)
|
||||
{
|
||||
_options = options;
|
||||
_conventionSetBuilderDependencies = conventionSetBuilderDependencies;
|
||||
}
|
||||
|
||||
|
||||
public ConventionSet ModifyConventions(ConventionSet conventionSet)
|
||||
{
|
||||
var extension = _options.FindExtension<EsiurExtensionOptions>();
|
||||
conventionSet.ModelFinalizingConventions.Add(new EsiurProxyRewrite(
|
||||
extension,
|
||||
_conventionSetBuilderDependencies));
|
||||
return conventionSet;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
196
Stores/Esiur.Stores.EntityCore/EsiurProxyRewrite.cs
Normal file
196
Stores/Esiur.Stores.EntityCore/EsiurProxyRewrite.cs
Normal file
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2020 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Esiur.Proxy;
|
||||
using Esiur.Resource;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Internal;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Internal;
|
||||
using Esiur.Data;
|
||||
|
||||
namespace Esiur.Stores.EntityCore;
|
||||
|
||||
public class EsiurProxyRewrite : IModelFinalizingConvention
|
||||
{
|
||||
private static readonly MethodInfo _createInstance
|
||||
= typeof(EsiurProxyRewrite).GetTypeInfo().GetDeclaredMethod(nameof(EsiurProxyRewrite.CreateInstance));
|
||||
|
||||
private readonly ConstructorBindingConvention _directBindingConvention;
|
||||
|
||||
|
||||
|
||||
public static object CreateInstance(IDbContextOptions dbContextOptions,
|
||||
IEntityType entityType,
|
||||
object[] properties)
|
||||
{
|
||||
var id = properties.First();
|
||||
|
||||
var options = dbContextOptions.FindExtension<EsiurExtensionOptions>();
|
||||
var manager = options.Store.Instance.Managers.Count > 0 ? options.Store.Instance.Managers.First() : null;
|
||||
|
||||
var cache = options.Store.GetById(entityType.ClrType, id);
|
||||
|
||||
if (cache != null && cache.Instance != null)
|
||||
{
|
||||
return cache;
|
||||
}
|
||||
|
||||
if (Codec.ImplementsInterface(entityType.ClrType, typeof(IResource)))
|
||||
{
|
||||
// check if the object exists
|
||||
var obj = options.Warehouse.Create(entityType.ClrType) as IResource;
|
||||
options.Store.TypesByType[entityType.ClrType].PrimaryKey.SetValue(obj, id);
|
||||
options.Warehouse.Put($"{options.Store.Instance.Name}/{entityType.ClrType.Name}/{id}", obj, 0, manager).Wait();
|
||||
return obj;
|
||||
}
|
||||
else
|
||||
{
|
||||
// record
|
||||
var obj = Activator.CreateInstance(entityType.ClrType);
|
||||
options.Store.TypesByType[entityType.ClrType].PrimaryKey.SetValue(obj, id);
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public EsiurProxyRewrite(EsiurExtensionOptions ext, ProviderConventionSetBuilderDependencies conventionSetBuilderDependencies)
|
||||
{
|
||||
_directBindingConvention = new ConstructorBindingConvention(conventionSetBuilderDependencies);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> context)
|
||||
{
|
||||
foreach (var entityType in modelBuilder.Metadata.GetEntityTypes())
|
||||
{
|
||||
|
||||
if (!Codec.ImplementsInterface(entityType.ClrType, typeof(IResource)))
|
||||
continue;
|
||||
|
||||
var proxyType = ResourceProxy.GetProxy(entityType.ClrType);
|
||||
|
||||
// var ann = entityType.GetAnnotation(CoreAnnotationNames.ConstructorBinding);
|
||||
|
||||
#pragma warning disable EF1001 // Internal EF Core API usage.
|
||||
var binding = ((EntityType)entityType).ConstructorBinding;// (InstantiationBinding)entityType[CoreAnnotationNames.ConstructorBinding];
|
||||
#pragma warning restore EF1001 // Internal EF Core API usage.
|
||||
if (binding == null)
|
||||
{
|
||||
_directBindingConvention.ProcessModelFinalizing(modelBuilder, context);
|
||||
|
||||
#pragma warning disable EF1001 // Internal EF Core API usage.
|
||||
binding = ((EntityType)entityType).ConstructorBinding; // (InstantiationBinding)entityType[CoreAnnotationNames.ConstructorBinding];
|
||||
#pragma warning restore EF1001 // Internal EF Core API usage.
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
var key = entityType.FindPrimaryKey().Properties.First();
|
||||
if (key == null)
|
||||
continue;
|
||||
|
||||
|
||||
((EntityType)entityType).SetConstructorBinding(
|
||||
UpdateConstructorBindings(key, proxyType),
|
||||
ConfigurationSource.Convention);
|
||||
|
||||
binding = ((EntityType)entityType).ServiceOnlyConstructorBinding;
|
||||
if (binding != null)
|
||||
{
|
||||
((EntityType)entityType).SetServiceOnlyConstructorBinding(
|
||||
UpdateConstructorBindings(key, proxyType),
|
||||
ConfigurationSource.Convention);
|
||||
}
|
||||
|
||||
|
||||
// entityType.SetAnnotation(
|
||||
//#pragma warning disable EF1001 // Internal EF Core API usage.
|
||||
// CoreAnnotationNames.ConstructorBinding,
|
||||
//#pragma warning restore EF1001 // Internal EF Core API usage.
|
||||
// new FactoryMethodBinding(
|
||||
// _createInstance,
|
||||
// new List<ParameterBinding>
|
||||
// {
|
||||
// new DependencyInjectionParameterBinding(typeof(IDbContextOptions), typeof(IDbContextOptions)),
|
||||
// new EntityTypeParameterBinding(),
|
||||
// //new PropertyParameterBinding(key)
|
||||
// // constructor arguments
|
||||
// //new ObjectArrayParameterBinding(binding.ParameterBindings),
|
||||
// //new ContextParameterBinding(typeof(DbContext)),
|
||||
// //new ObjectArrayParameterBinding(entityType.FindPrimaryKey().Properties.Select(x=>new PropertyParameterBinding(x)).ToArray())
|
||||
// new ObjectArrayParameterBinding(new ParameterBinding[]{
|
||||
// new PropertyParameterBinding(key) })
|
||||
// //})
|
||||
// // new Microsoft.EntityFrameworkCore.Metadata.ObjectArrayParameterBinding(),
|
||||
// //new ObjectArrayParameterBinding()
|
||||
|
||||
// },
|
||||
// proxyType));
|
||||
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private InstantiationBinding UpdateConstructorBindings(
|
||||
IConventionProperty key,
|
||||
Type proxyType)
|
||||
{
|
||||
return new FactoryMethodBinding(
|
||||
_createInstance,
|
||||
new List<ParameterBinding>
|
||||
{
|
||||
new DependencyInjectionParameterBinding(typeof(IDbContextOptions), typeof(IDbContextOptions)),
|
||||
new EntityTypeParameterBinding(),
|
||||
new ObjectArrayParameterBinding(new ParameterBinding[]{
|
||||
new PropertyParameterBinding((IProperty)key) })
|
||||
|
||||
},
|
||||
proxyType);
|
||||
}
|
||||
}
|
||||
8
Stores/Esiur.Stores.EntityCore/GlobalSuppressions.cs
Normal file
8
Stores/Esiur.Stores.EntityCore/GlobalSuppressions.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
// This file is used by Code Analysis to maintain SuppressMessage
|
||||
// attributes that are applied to this project.
|
||||
// Project-level suppressions either have no target or are given
|
||||
// a specific target and scoped to a namespace, type, member, etc.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
[assembly: SuppressMessage("Usage", "EF1001:Internal EF Core API usage.", Justification = "<Pending>", Scope = "member", Target = "~M:Esiur.Stores.EntityCore.EsiurProxyRewrite.ProcessModelFinalizing(Microsoft.EntityFrameworkCore.Metadata.Builders.IConventionModelBuilder,Microsoft.EntityFrameworkCore.Metadata.Conventions.IConventionContext{Microsoft.EntityFrameworkCore.Metadata.Builders.IConventionModelBuilder})")]
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"profiles": {
|
||||
"Esiur.Stores.EntityCore": {
|
||||
"commandName": "Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
28
Stores/Esiur.Stores.MongoDB/Esiur.Stores.MongoDB.csproj
Normal file
28
Stores/Esiur.Stores.MongoDB/Esiur.Stores.MongoDB.csproj
Normal file
@@ -0,0 +1,28 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Authors>Ahmed Kh. Zamil</Authors>
|
||||
<Company>Esiur</Company>
|
||||
<Product>Esiur MongoDB Store</Product>
|
||||
<Description>MongoDB Store for Esiur Library</Description>
|
||||
<Copyright>Ahmed Kh. Zamil</Copyright>
|
||||
<PackageProjectUrl>http://www.esiur.com</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/esiur/esiur-dotnet/</RepositoryUrl>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<Version>1.5.6</Version>
|
||||
<PackageId>Esiur.Stores.MongoDB</PackageId>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MongoDB.Bson" Version="2.29.0" />
|
||||
<PackageReference Include="MongoDB.Driver" Version="2.29.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Libraries\Esiur\Esiur.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
938
Stores/Esiur.Stores.MongoDB/MongoDBStore.cs
Normal file
938
Stores/Esiur.Stores.MongoDB/MongoDBStore.cs
Normal file
@@ -0,0 +1,938 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using Esiur.Resource;
|
||||
using System;
|
||||
using Esiur.Core;
|
||||
using MongoDB.Driver.Core;
|
||||
using MongoDB.Driver;
|
||||
using MongoDB.Bson;
|
||||
using Esiur.Data;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using Esiur.Security.Permissions;
|
||||
using Esiur.Proxy;
|
||||
using Esiur.Data.Types;
|
||||
|
||||
namespace Esiur.Stores.MongoDB;
|
||||
|
||||
public class MongoDBStore : IStore
|
||||
{
|
||||
public Instance Instance { get; set; }
|
||||
|
||||
public event DestroyedEvent OnDestroy;
|
||||
MongoClient client;
|
||||
IMongoDatabase database;
|
||||
IMongoCollection<BsonDocument> resourcesCollection;
|
||||
|
||||
KeyList<string, WeakReference> resources = new KeyList<string, WeakReference>();
|
||||
|
||||
|
||||
[Export]
|
||||
public event ResourceEventHandler<IResource> ResourceAdded;
|
||||
|
||||
[Export]
|
||||
public event ResourceEventHandler<IResource> ResourceRemoved;
|
||||
|
||||
int count = 0;
|
||||
|
||||
[Export]
|
||||
public virtual int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
return count;
|
||||
|
||||
//return (int)resourcesCollection.Count(x => true);
|
||||
}
|
||||
}
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
OnDestroy?.Invoke(this);
|
||||
}
|
||||
|
||||
/*
|
||||
public IResource[] Query(string json)
|
||||
{
|
||||
//var json = "{ SendId: 4, 'Events.Code' : { $all : [2], $nin : [3] } }";
|
||||
resourcesCollection.Find(new QueryDocument(BsonDocument.Parse(json)));
|
||||
|
||||
}*/
|
||||
|
||||
public bool Record(IResource resource, string propertyName, object value, ulong? age, DateTime? date)
|
||||
{
|
||||
var objectId = resource.Instance.Variables["objectId"].ToString();
|
||||
//var bsonObjectId = new BsonObjectId(new ObjectId(objectId));
|
||||
|
||||
var record = this.database.GetCollection<BsonDocument>("record_" + objectId);
|
||||
|
||||
record.InsertOne(new BsonDocument()
|
||||
{
|
||||
{"property", propertyName}, {"age", BsonValue.Create(age) }, {"date", date}, {"value", Compose(value) }
|
||||
});
|
||||
|
||||
//var col = this.database.GetCollection<BsonDocument>(collectionName);
|
||||
|
||||
|
||||
|
||||
var filter = Builders<BsonDocument>.Filter.Eq("_id", new BsonObjectId(new ObjectId(objectId)));
|
||||
var update = Builders<BsonDocument>.Update
|
||||
.Set("values." + propertyName, new BsonDocument { { "age", BsonValue.Create(age) },
|
||||
{ "modification", date },
|
||||
{ "value", Compose(value) } });
|
||||
resourcesCollection.UpdateOne(filter, update);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[Export]
|
||||
public AsyncReply<bool> Remove(IResource resource)
|
||||
{
|
||||
var objectId = resource.Instance.Variables["objectId"].ToString();
|
||||
var filter = Builders<BsonDocument>.Filter.Eq("_id", new BsonObjectId(new ObjectId(objectId)));
|
||||
|
||||
this.database.DropCollection("record_" + objectId);
|
||||
resourcesCollection.DeleteOne(filter);
|
||||
|
||||
count--;
|
||||
|
||||
ResourceRemoved?.Invoke(resource);
|
||||
|
||||
|
||||
Instance.Modified("Count");
|
||||
|
||||
return new AsyncReply<bool>(true);
|
||||
}
|
||||
|
||||
async AsyncReply<T> Fetch<T>(string id) where T : IResource
|
||||
{
|
||||
|
||||
if (resources.ContainsKey(id) && resources[id].IsAlive)
|
||||
{
|
||||
if (resources[id].Target is T)
|
||||
return (T)resources[id].Target;// new AsyncReply<T>((T)resources[id].Target);
|
||||
else
|
||||
return default(T);// new AsyncReply<T>(default(T)); ;
|
||||
}
|
||||
|
||||
var filter = Builders<BsonDocument>.Filter.Eq("_id", new BsonObjectId(new ObjectId(id)));
|
||||
var list = resourcesCollection.Find(filter).ToList();
|
||||
if (list.Count == 0)
|
||||
return default(T);// new AsyncReply<T>(default(T));
|
||||
var document = list[0];
|
||||
|
||||
var type = Type.GetType(document["classname"].AsString);
|
||||
|
||||
if (type == null)
|
||||
return default(T);// new AsyncReply<T>(default(T));
|
||||
|
||||
IResource resource = (IResource)Activator.CreateInstance(ResourceProxy.GetProxy(type));
|
||||
|
||||
//var iid = document["_id"].AsObjectId.ToString();
|
||||
if (resources.ContainsKey(id))
|
||||
resources[id] = new WeakReference(resource);
|
||||
else
|
||||
resources.Add(id, new WeakReference(resource));
|
||||
|
||||
//@TODO this causes store.put to be invoked, need fix
|
||||
await Instance.Warehouse.Put(document["name"].AsString, resource);
|
||||
|
||||
|
||||
var parents = document["parents"].AsBsonArray;
|
||||
var children = document["children"].AsBsonArray;
|
||||
//var managers = document["managers"].AsBsonArray;
|
||||
|
||||
var attributes = Parse(document["attributes"]).Then(x =>
|
||||
{
|
||||
resource.Instance.SetAttributes(x as Map<string, object>);
|
||||
});
|
||||
|
||||
// var bag = new AsyncBag<object>();
|
||||
|
||||
resource.Instance.Variables.Add("children", children.Select(x => x.AsString).ToArray());
|
||||
resource.Instance.Variables.Add("parents", parents.Select(x => x.AsString).ToArray());
|
||||
|
||||
// Apply store managers
|
||||
foreach (var m in this.Instance.Managers)
|
||||
resource.Instance.Managers.Add(m);
|
||||
|
||||
/*
|
||||
// load managers
|
||||
foreach(var m in managers)
|
||||
{
|
||||
IPermissionsManager pm = (IPermissionsManager)Activator.CreateInstance(Type.GetType(m["classname"].AsString));
|
||||
var sr = Parse(m["settings"]);
|
||||
bag.Add(sr);
|
||||
sr.Then((x) =>
|
||||
{
|
||||
pm.Initialize((Structure)x, resource);
|
||||
resource.Instance.Managers.Add(pm);
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
// Load values
|
||||
var values = document["values"].AsBsonDocument;
|
||||
|
||||
|
||||
foreach (var v in values)
|
||||
{
|
||||
var valueInfo = v.Value as BsonDocument;
|
||||
|
||||
var x = await Parse(valueInfo["value"]);
|
||||
resource.Instance.LoadProperty(v.Name,
|
||||
(ulong)valueInfo["age"].AsInt64,
|
||||
valueInfo["modification"].ToUniversalTime(),
|
||||
x);
|
||||
|
||||
//bag.Add(av);
|
||||
}
|
||||
|
||||
if (resource is T)
|
||||
return (T)resource;
|
||||
else
|
||||
return default(T);
|
||||
}
|
||||
|
||||
AsyncReply Parse(BsonValue value)
|
||||
{
|
||||
if (value.BsonType == BsonType.Document)
|
||||
{
|
||||
var doc = value.AsBsonDocument;
|
||||
if (doc["type"] == 0)
|
||||
{
|
||||
return Instance.Warehouse.Get<IResource>(doc["link"].AsString);
|
||||
} // structure
|
||||
else if (doc["type"] == 1)
|
||||
{
|
||||
var bag = new AsyncBag<object>();
|
||||
var rt = new AsyncReply<Map<string, object>>();
|
||||
|
||||
var bs = (BsonDocument)doc["values"].AsBsonDocument;
|
||||
var s = new Map<string, object>();
|
||||
|
||||
foreach (var v in bs)
|
||||
bag.Add(Parse(v.Value));
|
||||
|
||||
bag.Seal();
|
||||
bag.Then((x) =>
|
||||
{
|
||||
for (var i = 0; i < x.Length; i++)
|
||||
s[bs.GetElement(i).Name] = x[i];
|
||||
|
||||
rt.Trigger(s);
|
||||
});
|
||||
|
||||
return rt;
|
||||
}
|
||||
else
|
||||
return new AsyncReply<object>(null);
|
||||
}
|
||||
else if (value.BsonType == BsonType.Array)
|
||||
{
|
||||
var array = value.AsBsonArray;
|
||||
var bag = new AsyncBag<object>();
|
||||
|
||||
foreach (var v in array)
|
||||
bag.Add(Parse(v));
|
||||
|
||||
bag.Seal();
|
||||
|
||||
return bag;
|
||||
}
|
||||
else if (value.BsonType == BsonType.DateTime)
|
||||
{
|
||||
return new AsyncReply<object>(value.ToUniversalTime());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
return new AsyncReply<object>(BsonTypeMapper.MapToDotNetValue(value));
|
||||
}
|
||||
}
|
||||
|
||||
public AsyncReply<IResource> Get(string path)
|
||||
{
|
||||
var p = path.Split('/');
|
||||
if (p.Length == 2)
|
||||
if (p[0] == "id")
|
||||
{
|
||||
// load from Id
|
||||
return Fetch<IResource>(p[1]);
|
||||
|
||||
|
||||
/*
|
||||
if (resources.ContainsKey(p[1]))
|
||||
return new AsyncReply<IResource>(resources[p[1]]);
|
||||
else
|
||||
return Fetch(p[1]);
|
||||
*/
|
||||
}
|
||||
|
||||
return new AsyncReply<IResource>(null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public string Link(IResource resource)
|
||||
{
|
||||
return this.Instance.Name + "/id/" + (string)resource.Instance.Variables["objectId"];
|
||||
}
|
||||
|
||||
public async AsyncReply<bool> Put(IResource resource, string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (resource == this)
|
||||
return true;
|
||||
|
||||
var attrs = resource.Instance.GetAttributes();
|
||||
|
||||
foreach (var kv in resources)
|
||||
if (kv.Value.Target == resource)
|
||||
{
|
||||
resource.Instance.Variables.Add("objectId", kv.Key);
|
||||
return true;
|
||||
}
|
||||
|
||||
count++;
|
||||
|
||||
Instance.Modified("Count");
|
||||
|
||||
var type = ResourceProxy.GetBaseType(resource);
|
||||
|
||||
// insert the document
|
||||
var document = new BsonDocument
|
||||
{
|
||||
{ "classname", type.FullName + "," + type.GetTypeInfo().Assembly.GetName().Name },
|
||||
{ "name", resource.Instance.Name },
|
||||
};
|
||||
|
||||
resourcesCollection.InsertOne(document);
|
||||
resource.Instance.Variables["objectId"] = document["_id"].ToString();
|
||||
|
||||
|
||||
// now update the document
|
||||
// * insert first to get the object id, update values, attributes, children and parents after in case the same resource has a property references self
|
||||
|
||||
var parents = new BsonArray();
|
||||
var children = new BsonArray();
|
||||
|
||||
var typeDef = resource.Instance.Definition;
|
||||
|
||||
// setup attributes
|
||||
resource.Instance.Variables["children"] = new string[0];
|
||||
resource.Instance.Variables["parents"] = new string[] { this.Instance.Link };
|
||||
|
||||
// copy old children (in case we are moving a resource from a store to another.
|
||||
if (resource.Instance.Store != this)
|
||||
{
|
||||
var resourceChildren = await resource.Instance.Children<IResource>();
|
||||
|
||||
if (resourceChildren != null)
|
||||
foreach (IResource c in resourceChildren)
|
||||
children.Add(c.Instance.Link);
|
||||
|
||||
var resourceParents = await resource.Instance.Parents<IResource>();
|
||||
|
||||
if (resourceParents == null)
|
||||
{
|
||||
parents.Add(this.Instance.Link);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (IResource p in resourceParents)
|
||||
parents.Add(p.Instance.Link);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// just add self
|
||||
parents.Add(this.Instance.Link);
|
||||
}
|
||||
|
||||
|
||||
var attrsDoc = ComposeStructure(attrs);
|
||||
|
||||
|
||||
var values = new BsonDocument();
|
||||
|
||||
foreach (var pt in typeDef.Properties)
|
||||
{
|
||||
var rt = pt.PropertyInfo.GetValue(resource, null);
|
||||
|
||||
values.Add(pt.Name,
|
||||
new BsonDocument { { "age", BsonValue.Create(resource.Instance.GetAge(pt.Index)) },
|
||||
{ "modification", resource.Instance.GetModificationDate(pt.Index) },
|
||||
{ "value", Compose(rt) } });
|
||||
}
|
||||
|
||||
|
||||
var filter = Builders<BsonDocument>.Filter.Eq("_id", document["_id"]);
|
||||
var update = Builders<BsonDocument>.Update
|
||||
.Set("values", values).Set("parents", parents).Set("children", children).Set("attributes", attrsDoc);
|
||||
resourcesCollection.UpdateOne(filter, update);
|
||||
|
||||
|
||||
resources.Add(document["_id"].AsObjectId.ToString(), new WeakReference(resource));
|
||||
|
||||
//resource.Instance.Variables["objectId"] = document["_id"].ToString();
|
||||
|
||||
ResourceAdded?.Invoke(resource);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public BsonDocument ComposeStructure(Map<string, object> value)
|
||||
{
|
||||
var rt = new BsonDocument { { "type", 1 } };
|
||||
|
||||
var values = new BsonDocument();
|
||||
foreach (var i in value)
|
||||
values.Add(i.Key, Compose(i.Value));
|
||||
|
||||
rt.Add("values", values);
|
||||
return rt;
|
||||
}
|
||||
|
||||
public BsonArray ComposeVarArray(Array array)
|
||||
{
|
||||
var rt = new BsonArray();
|
||||
|
||||
for (var i = 0; i < array.Length; i++)
|
||||
rt.Add(Compose(array.GetValue(i)));// [i]));
|
||||
|
||||
return rt;
|
||||
}
|
||||
|
||||
BsonArray ComposeStructureArray(Map<string, object>[] structures)
|
||||
{
|
||||
var rt = new BsonArray();
|
||||
|
||||
if (structures == null || structures?.Length == 0)
|
||||
return rt;
|
||||
|
||||
foreach (var s in structures)
|
||||
rt.Add(ComposeStructure(s));
|
||||
|
||||
return rt;
|
||||
}
|
||||
|
||||
BsonArray ComposeResourceArray(IResource[] array)
|
||||
{
|
||||
var rt = new BsonArray();
|
||||
foreach (var r in array)
|
||||
{
|
||||
rt.Add(new BsonDocument { { "type", 0 }, { "link", r.Instance.Link } });
|
||||
|
||||
//if (r.Instance.Variables.ContainsKey("objectId"))
|
||||
|
||||
//rt.Add(new BsonObjectId(new ObjectId((string)r.Instance.Variables["objectId"])));
|
||||
}
|
||||
|
||||
return rt;
|
||||
}
|
||||
|
||||
private BsonValue Compose(object valueObj)
|
||||
{
|
||||
|
||||
//@TODO : Rewrite
|
||||
//var (type, value) = Tra Codec.GetDataType(valueObj, null);
|
||||
|
||||
//switch (type)
|
||||
//{
|
||||
// case DataType.Void:
|
||||
// // nothing to do;
|
||||
// return BsonNull.Value;
|
||||
|
||||
// case DataType.String:
|
||||
// return new BsonString((string)value);
|
||||
|
||||
// case DataType.Resource:
|
||||
// case DataType.DistributedResource:
|
||||
|
||||
// return new BsonDocument { { "type", 0 }, { "link", (value as IResource).Instance.Link } };
|
||||
|
||||
// //return new BsonObjectId(new ObjectId((string)(value as IResource).Instance.Variables["objectId"]));
|
||||
|
||||
// case DataType.Structure:
|
||||
// return ComposeStructure((Structure)value);
|
||||
|
||||
// case DataType.VarArray:
|
||||
// return ComposeVarArray((Array)value);
|
||||
|
||||
// case DataType.ResourceArray:
|
||||
// if (value is IResource[])
|
||||
// return ComposeResourceArray((IResource[])value);
|
||||
// else
|
||||
// return ComposeResourceArray((IResource[])DC.CastConvert(value, typeof(IResource[])));
|
||||
|
||||
|
||||
// case DataType.StructureArray:
|
||||
// return ComposeStructureArray((Structure[])value);
|
||||
|
||||
|
||||
// default:
|
||||
// return BsonValue.Create(value);
|
||||
//}
|
||||
|
||||
return BsonValue.Create(valueObj);
|
||||
}
|
||||
|
||||
public AsyncReply<IResource> Retrieve(uint iid)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
[Attribute]
|
||||
public string Connection { get; set; }
|
||||
[Attribute]
|
||||
public string Collection { get; set; }
|
||||
[Attribute]
|
||||
public string Database { get; set; }
|
||||
public AsyncReply<bool> Trigger(ResourceTrigger trigger)
|
||||
{
|
||||
|
||||
if (trigger == ResourceTrigger.Initialize)
|
||||
{
|
||||
|
||||
var collectionName = Collection ?? "resources";
|
||||
var dbName = Database ?? "Esiur";
|
||||
client = new MongoClient(Connection ?? "mongodb://localhost");
|
||||
database = client.GetDatabase(dbName);
|
||||
|
||||
resourcesCollection = this.database.GetCollection<BsonDocument>(collectionName);
|
||||
|
||||
|
||||
count = (int)resourcesCollection.CountDocuments(x => true);
|
||||
|
||||
|
||||
// return new AsyncReply<bool>(true);
|
||||
|
||||
|
||||
/*
|
||||
var filter = new BsonDocument();
|
||||
|
||||
var list = resourcesCollection.Find(filter).ToList();
|
||||
|
||||
|
||||
// if (list.Count == 0)
|
||||
// return new AsyncBag<IResource>(new IResource[0]);
|
||||
|
||||
var bag = new AsyncBag<IResource>();
|
||||
|
||||
for(var i = 0; i < list.Count; i++)
|
||||
{
|
||||
Console.WriteLine("Loading {0}/{1}", i, list.Count);
|
||||
bag.Add(Get("id/" + list[i]["_id"].AsObjectId.ToString()));
|
||||
}
|
||||
|
||||
bag.Seal();
|
||||
|
||||
var rt = new AsyncReply<bool>();
|
||||
|
||||
bag.Then((x) => {
|
||||
|
||||
// storeChildren.AddRange(x);
|
||||
rt.Trigger(true);
|
||||
|
||||
});
|
||||
|
||||
return rt;
|
||||
*/
|
||||
|
||||
return new AsyncReply<bool>(true);
|
||||
}
|
||||
else if (trigger == ResourceTrigger.Terminate)
|
||||
{
|
||||
// save all resources
|
||||
foreach (var resource in resources.Values)
|
||||
if (resource.IsAlive)
|
||||
SaveResource(resource.Target as IResource);
|
||||
|
||||
return new AsyncReply<bool>(true);
|
||||
}
|
||||
else
|
||||
return new AsyncReply<bool>(true);
|
||||
}
|
||||
|
||||
|
||||
public void SaveResource(IResource resource)
|
||||
{
|
||||
var attrs = resource.Instance.GetAttributes();
|
||||
|
||||
var parents = new BsonArray();
|
||||
var children = new BsonArray();
|
||||
var typeDef = resource.Instance.Definition;
|
||||
|
||||
//foreach (IResource c in resource.Instance.Children)
|
||||
// children.Add(c.Instance.Link);
|
||||
|
||||
var plist = resource.Instance.Variables["parents"] as string[];
|
||||
|
||||
foreach (var link in plist)// Parents)
|
||||
parents.Add(link);
|
||||
|
||||
|
||||
var values = new BsonDocument();
|
||||
|
||||
foreach (var pt in typeDef.Properties)
|
||||
{
|
||||
/*
|
||||
#if NETSTANDARD1_5
|
||||
var pi = resource.GetType().GetTypeInfo().GetProperty(pt.Name);
|
||||
#else
|
||||
var pi = resource.GetType().GetProperty(pt.Name);
|
||||
#endif
|
||||
*/
|
||||
var rt = pt.PropertyInfo.GetValue(resource, null);
|
||||
|
||||
values.Add(pt.Name,
|
||||
new BsonDocument { { "age", BsonValue.Create(resource.Instance.GetAge(pt.Index)) },
|
||||
{ "modification", resource.Instance.GetModificationDate(pt.Index) },
|
||||
{ "value", Compose(rt) } });
|
||||
|
||||
}
|
||||
|
||||
var attrsDoc = ComposeStructure(attrs);
|
||||
|
||||
var type = ResourceProxy.GetBaseType(resource);
|
||||
|
||||
var document = new BsonDocument
|
||||
{
|
||||
{ "parents", parents },
|
||||
{ "children", children },
|
||||
{ "attributes", attrsDoc },
|
||||
{ "classname", type.FullName + "," + type.GetTypeInfo().Assembly.GetName().Name },
|
||||
{ "name", resource.Instance.Name },
|
||||
{ "_id", new BsonObjectId(new ObjectId(resource.Instance.Variables["objectId"].ToString())) },
|
||||
{"values", values }
|
||||
};
|
||||
|
||||
|
||||
|
||||
var filter = Builders<BsonDocument>.Filter.Eq("_id", document["_id"]);
|
||||
|
||||
/*
|
||||
var update = Builders<BsonDocument>.Update
|
||||
.Set("values", values);
|
||||
|
||||
var update = Builders<BsonDocument>.Update.Set("values", values).Set("parents", parents;
|
||||
col.UpdateOne(filter, update);
|
||||
|
||||
*/
|
||||
|
||||
resourcesCollection.ReplaceOne(filter, document);
|
||||
}
|
||||
|
||||
public AsyncReply<PropertyValue[]> GetPropertyRecordByAge(IResource resource, string propertyName, ulong fromAge, ulong toAge)
|
||||
{
|
||||
var objectId = resource.Instance.Variables["objectId"].ToString();
|
||||
|
||||
var record = this.database.GetCollection<BsonDocument>("record_" + objectId);
|
||||
var builder = Builders<BsonDocument>.Filter;
|
||||
|
||||
var filter = builder.Gte("age", fromAge) & builder.Lte("age", toAge) & builder.Eq("property", propertyName);
|
||||
|
||||
var reply = new AsyncReply<PropertyValue[]>();
|
||||
|
||||
record.FindAsync(filter).ContinueWith((x) =>
|
||||
{
|
||||
var values = ((Task<IAsyncCursor<BsonDocument>>)x).Result.ToList();
|
||||
|
||||
var bag = new AsyncBag<object>();
|
||||
|
||||
foreach (var v in values)
|
||||
bag.Add(Parse(v["value"]));
|
||||
|
||||
bag.Seal();
|
||||
|
||||
bag.Then((results) =>
|
||||
{
|
||||
var list = new List<PropertyValue>();
|
||||
for (var i = 0; i < results.Length; i++)
|
||||
list.Add(new PropertyValue(results[i], (ulong)values[i]["age"].AsInt64, values[i]["date"].ToUniversalTime()));
|
||||
|
||||
reply.Trigger(list.ToArray());
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
public AsyncReply<PropertyValue[]> GetPropertyRecordByDate(IResource resource, string propertyName, DateTime fromDate, DateTime toDate)
|
||||
{
|
||||
var objectId = resource.Instance.Variables["objectId"].ToString();
|
||||
|
||||
var record = this.database.GetCollection<BsonDocument>("record_" + objectId);
|
||||
var builder = Builders<BsonDocument>.Filter;
|
||||
|
||||
var filter = builder.Gte("date", fromDate) & builder.Lte("date", toDate) & builder.Eq("property", propertyName);
|
||||
|
||||
var reply = new AsyncReply<PropertyValue[]>();
|
||||
|
||||
record.FindAsync(filter).ContinueWith((x) =>
|
||||
{
|
||||
var values = ((Task<IAsyncCursor<BsonDocument>>)x).Result.ToList();
|
||||
|
||||
var bag = new AsyncBag<object>();
|
||||
|
||||
foreach (var v in values)
|
||||
bag.Add(Parse(v["value"]));
|
||||
|
||||
bag.Seal();
|
||||
|
||||
bag.Then((results) =>
|
||||
{
|
||||
var list = new List<PropertyValue>();
|
||||
for (var i = 0; i < results.Length; i++)
|
||||
list.Add(new PropertyValue(results[i], (ulong)values[i]["age"].AsInt64, values[i]["date"].ToUniversalTime()));
|
||||
|
||||
reply.Trigger(list.ToArray());
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
AsyncReply<KeyList<PropertyDef, PropertyValue[]>> GetRecordByAge(IResource resource, ulong fromAge, ulong toAge)
|
||||
{
|
||||
var properties = resource.Instance.Definition.Properties.Where(x => x.HasHistory).ToList();
|
||||
|
||||
var reply = new AsyncReply<KeyList<PropertyDef, PropertyValue[]>>();
|
||||
|
||||
AsyncBag<PropertyValue[]> bag = new AsyncBag<PropertyValue[]>();
|
||||
|
||||
foreach (var p in properties)
|
||||
bag.Add(GetPropertyRecordByAge(resource, p.Name, fromAge, toAge));
|
||||
|
||||
bag.Seal();
|
||||
|
||||
bag.Then(x =>
|
||||
{
|
||||
var list = new KeyList<PropertyDef, PropertyValue[]>();
|
||||
|
||||
for (var i = 0; i < x.Length; i++)
|
||||
list.Add(properties[i], x[i]);
|
||||
|
||||
reply.Trigger(list);
|
||||
});
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
public AsyncReply<KeyList<PropertyDef, PropertyValue[]>> GetRecord(IResource resource, DateTime fromDate, DateTime toDate)
|
||||
{
|
||||
var properties = resource.Instance.Definition.Properties.Where(x => x.HasHistory).ToList();
|
||||
|
||||
var reply = new AsyncReply<KeyList<PropertyDef, PropertyValue[]>>();
|
||||
|
||||
AsyncBag<PropertyValue[]> bag = new AsyncBag<PropertyValue[]>();
|
||||
|
||||
foreach (var p in properties)
|
||||
bag.Add(GetPropertyRecordByDate(resource, p.Name, fromDate, toDate));
|
||||
|
||||
bag.Seal();
|
||||
|
||||
bag.Then(x =>
|
||||
{
|
||||
var list = new KeyList<PropertyDef, PropertyValue[]>();
|
||||
|
||||
for (var i = 0; i < x.Length; i++)
|
||||
list.Add(properties[i], x[i]);
|
||||
|
||||
reply.Trigger(list);
|
||||
});
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
public bool Modify(IResource resource, PropertyDef propertyDef, object value, ulong? age, DateTime? dateTime)
|
||||
{
|
||||
|
||||
if (resource == this)
|
||||
return true;
|
||||
|
||||
var objectId = resource.Instance.Variables["objectId"].ToString();
|
||||
|
||||
var filter = Builders<BsonDocument>.Filter.Eq("_id", new BsonObjectId(new ObjectId(objectId)));
|
||||
var update = Builders<BsonDocument>.Update
|
||||
.Set("values." + propertyDef.Name, new BsonDocument { { "age", BsonValue.Create(age) },
|
||||
{ "modification", dateTime },
|
||||
{ "value", Compose(value) } });
|
||||
|
||||
resourcesCollection.UpdateOne(filter, update);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public AsyncBag<T> Children<T>(IResource resource, string name) where T : IResource
|
||||
{
|
||||
|
||||
if (resource == this)
|
||||
{
|
||||
IFindFluent<BsonDocument, BsonDocument> match;
|
||||
|
||||
if (name == null)
|
||||
match = resourcesCollection.Find(x => (x["parents"] as BsonArray).Contains(this.Instance.Name));
|
||||
else
|
||||
match = resourcesCollection.Find(x => (x["parents"] as BsonArray).Contains(this.Instance.Name) && x["name"] == name);
|
||||
|
||||
|
||||
var st = match.ToList().Select(x => x["_id"].ToString()).ToArray();
|
||||
|
||||
|
||||
var bag = new AsyncBag<T>();
|
||||
|
||||
foreach (var s in st)
|
||||
{
|
||||
var r = Fetch<T>(s);
|
||||
if (r.Ready && r.Result == null)
|
||||
continue;
|
||||
|
||||
bag.Add(r);
|
||||
}
|
||||
|
||||
bag.Seal();
|
||||
return bag;
|
||||
}
|
||||
else
|
||||
{
|
||||
var children = (string[])resource.Instance.Variables["children"];
|
||||
|
||||
if (children == null)
|
||||
{
|
||||
return new AsyncBag<T>(null);
|
||||
}
|
||||
|
||||
var rt = new AsyncBag<T>();
|
||||
|
||||
|
||||
foreach (var child in children)
|
||||
{
|
||||
var r = Instance.Warehouse.Get<T>(child);
|
||||
if (r is AsyncReply<T>)
|
||||
rt.Add(r);// (AsyncReply<T>)r);
|
||||
}
|
||||
|
||||
rt.Seal();
|
||||
return rt;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public AsyncReply<bool> AddChild(IResource resource, IResource child)
|
||||
{
|
||||
var list = (string[])resource.Instance.Variables["children"];
|
||||
resource.Instance.Variables["children"] = list.Concat(new string[] { child.Instance.Link }).ToArray();
|
||||
|
||||
SaveResource(resource);
|
||||
|
||||
return new AsyncReply<bool>(true);
|
||||
}
|
||||
|
||||
public AsyncReply<bool> RemoveChild(IResource parent, IResource child)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public AsyncReply<bool> AddParent(IResource resource, IResource parent)
|
||||
{
|
||||
var list = (string[])resource.Instance.Variables["parents"];
|
||||
resource.Instance.Variables["parents"] = list.Concat(new string[] { parent.Instance.Link }).ToArray();
|
||||
|
||||
SaveResource(resource);
|
||||
|
||||
return new AsyncReply<bool>(true);
|
||||
}
|
||||
|
||||
public AsyncReply<bool> RemoveParent(IResource child, IResource parent)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public AsyncReply<bool> Move(IResource resource, string newPath)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
|
||||
public AsyncBag<T> Parents<T>(IResource resource, string name) where T : IResource
|
||||
{
|
||||
|
||||
if (resource == this)
|
||||
{
|
||||
return new AsyncBag<T>(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
var parents = (string[])resource.Instance.Variables["parents"];
|
||||
|
||||
if (parents == null)
|
||||
{
|
||||
return new AsyncBag<T>(null);
|
||||
}
|
||||
|
||||
var rt = new AsyncBag<T>();
|
||||
|
||||
|
||||
|
||||
foreach (var parent in parents)
|
||||
{
|
||||
var r = Instance.Warehouse.Get<T>(parent);
|
||||
if (r is AsyncReply<T>)
|
||||
rt.Add(r);// (AsyncReply<T>)r);
|
||||
}
|
||||
|
||||
|
||||
rt.Seal();
|
||||
|
||||
|
||||
return rt;
|
||||
}
|
||||
}
|
||||
|
||||
public AsyncReply<bool> Remove(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
55
Stores/Esiur.Stores.MongoDB/MongoDBStoreGeneric.cs
Normal file
55
Stores/Esiur.Stores.MongoDB/MongoDBStoreGeneric.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2020 Ahmed Kh. Zamil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
using Esiur.Core;
|
||||
using Esiur.Data;
|
||||
using Esiur.Proxy;
|
||||
using Esiur.Resource;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Stores.MongoDB
|
||||
{
|
||||
public class MongoDBStore<T> : MongoDBStore where T:IResource
|
||||
{
|
||||
[Export]
|
||||
public async AsyncReply<T> New(string name = null, object properties = null)
|
||||
{
|
||||
var resource = Instance.Warehouse.Create<T>(properties);
|
||||
await Instance.Warehouse.Put(this.Instance.Name + "/" + name, resource);
|
||||
resource.Instance.Managers.AddRange(this.Instance.Managers.ToArray());
|
||||
return resource;
|
||||
}
|
||||
|
||||
[Export]
|
||||
public async AsyncReply<IResource[]> Slice(int index, int limit)
|
||||
{
|
||||
var list = await this.Instance.Children<IResource>();
|
||||
return list.Skip(index).Take(limit).ToArray();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user