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 { fi.SchemaBuilder.Include <L>(); var mListPropertRoute = PropertyRoute.Construct(mListField); var backReferenceRoute = PropertyRoute.Construct(backReference, avoidLastCasting: true); if (fi.SchemaBuilder.Settings.FieldAttribute <IgnoreAttribute>(mListPropertRoute) == null) { throw new InvalidOperationException($"The property {mListPropertRoute} should have an IgnoreAttribute to be used as Virtual MList"); } RegisteredVirtualMLists.GetOrCreate(typeof(T)).Add(mListPropertRoute, new VirtualMListInfo(mListPropertRoute, backReferenceRoute)); var defLazyRetrieve = lazyRetrieve ?? (typeof(L) == typeof(T)); var defLazyDelete = 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 (defLazyRetrieve) { sb.Schema.EntityEvents <T>().Retrieved += (T e, PostRetrievingContext ctx) => { if (ShouldAvoidMListType(typeof(L))) { return; } var mlist = getMList(e); if (mlist == null) { return; } var query = Database.Query <L>() .Where(line => backReference.Evaluate(line).Is(e.ToLite())); MList <L> newList = preserveOrder ? query.ToVirtualMListWithOrder() : query.ToVirtualMList(); mlist.AssignAndPostRetrieving(newList, ctx); }; } if (preserveOrder) { sb.Schema.EntityEvents <T>().RegisterBinding <MList <L> >(mListField, shouldSet: () => !defLazyRetrieve && !VirtualMList.ShouldAvoidMListType(typeof(L)), valueExpression: (e, rowId) => Database.Query <L>().Where(line => backReference.Evaluate(line).Is(e.ToLite())).ExpandLite(line => backReference.Evaluate(line), ExpandLite.ToStringLazy).ToVirtualMListWithOrder(), valueFunction: (e, rowId, 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).Is(e.ToLite())).ExpandLite(line => backReference.Evaluate(line), ExpandLite.ToStringLazy).ToVirtualMListWithOrder() ); } else { sb.Schema.EntityEvents <T>().RegisterBinding(mListField, shouldSet: () => !defLazyRetrieve && !VirtualMList.ShouldAvoidMListType(typeof(L)), valueExpression: (e, rowId) => Database.Query <L>().Where(line => backReference.Evaluate(line).Is(e.ToLite())).ExpandLite(line => backReference.Evaluate(line), ExpandLite.ToStringLazy).ToVirtualMList(), valueFunction: (e, rowId, 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).Is(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) { #if DEBUG var withEntites = errors.WithEntities(graph); throw new IntegrityCheckException(withEntites); #else throw new IntegrityCheckException(errors); #endif } } 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).Is(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 (defLazyDelete) { if (toDelete.Any()) { toDelete.UnsafeDelete(); } } else { toDelete.UnsafeDelete(); } return(null); }; return(fi); }
public static FluentInclude <T> WithVirtualMListInitializeOnly <T, L>(this FluentInclude <T> fi, Expression <Func <T, MList <L> > > mListField, Expression <Func <L, Lite <T>?> > backReference, Action <L, T>?onSave = null) where T : Entity where L : Entity { fi.SchemaBuilder.Include <L>(); Func <T, MList <L> > getMList = GetAccessor(mListField); Action <L, Lite <T> >?setter = null; var sb = fi.SchemaBuilder; sb.Schema.EntityEvents <T>().RegisterBinding(mListField, shouldSet: () => false, valueExpression: (e, rowId) => Database.Query <L>().Where(line => backReference.Evaluate(line).Is(e.ToLite())).ExpandLite(line => backReference.Evaluate(line), ExpandLite.ToStringLazy).ToVirtualMListWithOrder() ); sb.Schema.EntityEvents <T>().Saving += (T e) => { if (VirtualMList.ShouldAvoidMListType(typeof(L))) { return; } var mlist = getMList(e); if (mlist == null) { return; } if (GraphExplorer.IsGraphModified(getMList(e))) { e.SetModified(); } }; sb.Schema.EntityEvents <T>().Saved += (T e, SavedEventArgs args) => { if (VirtualMList.ShouldAvoidMListType(typeof(L))) { return; } var mlist = getMList(e); if (mlist == null) { return; } if (!GraphExplorer.IsGraphModified(mlist)) { return; } 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); }; return(fi); }