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) == 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) == 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) == 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) == 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) == 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) == 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 int BulkInsertTable <T>(IEnumerable <T> entities, SqlBulkCopyOptions copyOptions = SqlBulkCopyOptions.Default, bool preSaving = true, bool validateFirst = true, bool disableIdentity = false, int?timeout = null, string?message = null) where T : Entity { using (HeavyProfiler.Log(nameof(BulkInsertTable), () => typeof(T).TypeName())) { if (message != null) { return(SafeConsole.WaitRows(message == "auto" ? $"BulkInsering {entities.Count()} {typeof(T).TypeName()}" : message, () => BulkInsertTable(entities, copyOptions, preSaving, validateFirst, disableIdentity, timeout, message: null))); } if (disableIdentity) { copyOptions |= SqlBulkCopyOptions.KeepIdentity; } if (copyOptions.HasFlag(SqlBulkCopyOptions.UseInternalTransaction)) { throw new InvalidOperationException("BulkInsertDisableIdentity not compatible with UseInternalTransaction"); } var list = entities.ToList(); if (preSaving) { Saver.PreSaving(() => GraphExplorer.FromRoots(list)); } if (validateFirst) { Validate <T>(list); } var t = Schema.Current.Table <T>(); bool disableIdentityBehaviour = copyOptions.HasFlag(SqlBulkCopyOptions.KeepIdentity); DataTable dt = new DataTable(); var columns = t.Columns.Values.Where(c => !(c is SystemVersionedInfo.SqlServerPeriodColumn) && (disableIdentityBehaviour || !c.IdentityBehaviour)).ToList(); foreach (var c in columns) { dt.Columns.Add(new DataColumn(c.Name, ConvertType(c.Type))); } using (disableIdentityBehaviour ? Administrator.DisableIdentity(t, behaviourOnly: true) : null) { foreach (var e in list) { if (!e.IsNew) { throw new InvalidOperationException("Entites should be new"); } t.SetToStrField(e); dt.Rows.Add(t.BulkInsertDataRow(e)); } } using (Transaction tr = new Transaction()) { Schema.Current.OnPreBulkInsert(typeof(T), inMListTable: false); Executor.BulkCopy(dt, columns, t.Name, copyOptions, timeout); foreach (var item in list) { item.SetNotModified(); } return(tr.Commit(list.Count)); } } }