public static void Save(Entity[] entities) { if (entities == null || entities.Any(e => e == null)) { throw new ArgumentNullException("entity"); } using (var log = HeavyProfiler.LogNoStackTrace("PreSaving")) { Schema schema = Schema.Current; DirectedGraph <Modifiable> modifiables = PreSaving(() => GraphExplorer.FromRoots(entities)); HashSet <Entity> wasNew = modifiables.OfType <Entity>().Where(a => a.IsNew).ToHashSet(ReferenceEqualityComparer <Entity> .Default); HashSet <Entity> wasSelfModified = modifiables.OfType <Entity>().Where(a => a.Modified == ModifiedState.SelfModified).ToHashSet(ReferenceEqualityComparer <Entity> .Default); log.Switch("Integrity"); var error = GraphExplorer.FullIntegrityCheck(modifiables); if (error != null) { #if DEBUG var withEntities = error.WithEntities(modifiables); throw new IntegrityCheckException(withEntities); #else throw new IntegrityCheckException(error); #endif } log.Switch("Graph"); GraphExplorer.PropagateModifications(modifiables.Inverse()); //colapsa modifiables (collections and embeddeds) keeping indentifiables only DirectedGraph <Entity> identifiables = GraphExplorer.ColapseIdentifiables(modifiables); foreach (var node in identifiables) { schema.OnSaving(node); } //Remove all the edges that doesn't mean a dependency identifiables.RemoveEdges(identifiables.Edges.Where(e => !e.To.IsNew).ToList()); //Remove all the nodes that are not modified List <Entity> notModified = identifiables.Where(node => !node.IsGraphModified).ToList(); notModified.ForEach(node => identifiables.RemoveFullNode(node, None)); log.Switch("SaveGroups"); SaveGraph(schema, identifiables); foreach (var node in identifiables) { schema.OnSaved(node, new SavedEventArgs { IsRoot = entities.Contains(node), WasNew = wasNew.Contains(node), WasSelfModified = wasSelfModified.Contains(node), }); } EntityCache.Add(identifiables); EntityCache.Add(notModified); GraphExplorer.CleanModifications(modifiables); } }
public static FluentInclude <T> WithVirtualMList <T, L>(this FluentInclude <T> fi, Expression <Func <T, MList <L> > > mListField, Expression <Func <L, Lite <T> > > backReference, Action <L, T> onSave = null, Action <L, T> onRemove = null, bool?lazyRetrieve = null, bool?lazyDelete = null) //To avoid StackOverflows where T : Entity where L : Entity { var mListPropertRoute = PropertyRoute.Construct(mListField); RegisteredVirtualMLists.GetOrCreate(typeof(T)).Add(typeof(L), mListPropertRoute); if (lazyRetrieve == null) { lazyRetrieve = (typeof(L) == typeof(T)); } if (lazyDelete == null) { lazyDelete = (typeof(L) == typeof(T)); } Func <T, MList <L> > getMList = GetAccessor(mListField); Action <L, Lite <T> > setter = null; bool preserveOrder = fi.SchemaBuilder.Settings.FieldAttributes(mListPropertRoute) .OfType <PreserveOrderAttribute>() .Any(); if (preserveOrder && !typeof(ICanBeOrdered).IsAssignableFrom(typeof(L))) { throw new InvalidOperationException($"'{typeof(L).Name}' should implement '{nameof(ICanBeOrdered)}' because '{ReflectionTools.GetPropertyInfo(mListField).Name}' contains '[{nameof(PreserveOrderAttribute)}]'"); } var sb = fi.SchemaBuilder; if (lazyRetrieve.Value) { sb.Schema.EntityEvents <T>().Retrieved += (T e) => { if (ShouldAvoidMListType(typeof(L))) { return; } var mlist = getMList(e); if (mlist == null) { return; } var query = Database.Query <L>() .Where(line => backReference.Evaluate(line) == e.ToLite()); MList <L> newList = preserveOrder ? query.ToVirtualMListWithOrder() : query.ToVirtualMList(); mlist.AssignAndPostRetrieving(newList); }; } if (preserveOrder) { sb.Schema.EntityEvents <T>().RegisterBinding <MList <L> >(mListField, shouldSet: () => !lazyRetrieve.Value && !VirtualMList.ShouldAvoidMListType(typeof(L)), valueExpression: e => Database.Query <L>().Where(line => backReference.Evaluate(line) == e.ToLite()).ExpandLite(line => backReference.Evaluate(line), ExpandLite.ToStringLazy).ToVirtualMListWithOrder(), valueFunction: (e, retriever) => Schema.Current.CacheController <L>().Enabled ? Schema.Current.CacheController <L>().RequestByBackReference <T>(retriever, backReference, e.ToLite()).ToVirtualMListWithOrder(): Database.Query <L>().Where(line => backReference.Evaluate(line) == e.ToLite()).ExpandLite(line => backReference.Evaluate(line), ExpandLite.ToStringLazy).ToVirtualMListWithOrder() ); } else { sb.Schema.EntityEvents <T>().RegisterBinding(mListField, shouldSet: () => !lazyRetrieve.Value && !VirtualMList.ShouldAvoidMListType(typeof(L)), valueExpression: e => Database.Query <L>().Where(line => backReference.Evaluate(line) == e.ToLite()).ExpandLite(line => backReference.Evaluate(line), ExpandLite.ToStringLazy).ToVirtualMList(), valueFunction: (e, retriever) => Schema.Current.CacheController <L>().Enabled ? Schema.Current.CacheController <L>().RequestByBackReference <T>(retriever, backReference, e.ToLite()).ToVirtualMList() : Database.Query <L>().Where(line => backReference.Evaluate(line) == e.ToLite()).ExpandLite(line => backReference.Evaluate(line), ExpandLite.ToStringLazy).ToVirtualMList() ); } sb.Schema.EntityEvents <T>().PreSaving += (T e, PreSavingContext ctx) => { if (VirtualMList.ShouldAvoidMListType(typeof(L))) { return; } var mlist = getMList(e); if (mlist == null) { return; } if (mlist.Count > 0) { var graph = Saver.PreSaving(() => GraphExplorer.FromRoot(mlist).RemoveAllNodes(ctx.Graph)); GraphExplorer.PropagateModifications(graph.Inverse()); var errors = GraphExplorer.FullIntegrityCheck(graph); if (errors != null) { throw new IntegrityCheckException(errors); } } if (mlist.IsGraphModified) { e.SetSelfModified(); } }; sb.Schema.EntityEvents <T>().Saving += (T e) => { if (VirtualMList.ShouldAvoidMListType(typeof(L))) { return; } var mlist = getMList(e); if (mlist == null) { return; } if (preserveOrder) { mlist.ForEach((o, i) => ((ICanBeOrdered)o).Order = i); } if (GraphExplorer.IsGraphModified(mlist)) { e.SetModified(); } }; sb.Schema.EntityEvents <T>().Saved += (T e, SavedEventArgs args) => { if (VirtualMList.ShouldAvoidMListType(typeof(L))) { return; } var mlist = getMList(e); if (mlist != null && !GraphExplorer.IsGraphModified(mlist)) { return; } if (!(args.WasNew || ShouldConsiderNew(typeof(T)))) { var oldElements = mlist.EmptyIfNull().Where(line => !line.IsNew); var query = Database.Query <L>() .Where(p => backReference.Evaluate(p) == e.ToLite()); if (onRemove == null) { query.Where(p => !oldElements.Contains(p)).UnsafeDelete(); } else { query.Where(p => !oldElements.Contains(p)).ToList().ForEach(line => onRemove(line, e)); } } if (mlist != null) { if (mlist.Any()) { if (setter == null) { setter = CreateSetter(backReference); } mlist.ForEach(line => setter(line, e.ToLite())); if (onSave == null) { mlist.SaveList(); } else { mlist.ForEach(line => { if (GraphExplorer.IsGraphModified(line)) { onSave(line, e); } }); } var priv = (IMListPrivate)mlist; for (int i = 0; i < mlist.Count; i++) { if (priv.GetRowId(i) == null) { priv.SetRowId(i, mlist[i].Id); } } } mlist.SetCleanModified(false); } }; sb.Schema.EntityEvents <T>().PreUnsafeDelete += query => { if (VirtualMList.ShouldAvoidMListType(typeof(L))) { return(null); } //You can do a VirtualMList to itself at the table level, but there should not be cycles inside the instances var toDelete = Database.Query <L>().Where(se => query.Any(e => backReference.Evaluate(se).Is(e))); if (lazyDelete.Value) { if (toDelete.Any()) { toDelete.UnsafeDelete(); } } else { toDelete.UnsafeDelete(); } return(null); }; return(fi); }
public Dictionary <Guid, IntegrityCheck>?FullIntegrityCheck() { var graph = GraphExplorer.FromRoot(this); return(GraphExplorer.FullIntegrityCheck(graph)); }