using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using InABox.Clients;
using InABox.Configuration;
using InABox.Core;
using InABox.WPF;
using Expression = System.Linq.Expressions.Expression;
namespace InABox.DynamicGrid
{
public interface IDynamicDataGrid : IDynamicGrid
{
///
/// The tag the the DynamicGridColumns are stored against. If set to ,
/// the name of is used as a default.
///
string? ColumnsTag { get; set; }
IColumns LoadEditorColumns();
}
public class DynamicDataGrid : DynamicGrid, IDynamicDataGrid where TEntity : Entity, IRemotable, IPersistent, new()
{
public delegate bool FilterSelected(DynamicGridFilter filter);
public event FilterSelected OnFilterSelected;
public delegate void OnReloadEventHandler(object sender, Filters criteria, Columns columns, ref SortOrder? sortby);
private readonly int ChunkSize = 500;
private readonly Button MergeBtn;
private readonly Button FilterBtn;
private bool _showFilterList;
public bool ShowFilterList
{
get => _showFilterList;
set
{
_showFilterList = value;
if (FilterBtn != null) // FilterBtn can be null when ShowFilterList is set in DynamicGrid constructor
FilterBtn.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
}
}
protected Tuple>? SelectedFilter;
private Column[] FilterColumns;
public DynamicDataGrid() : base()
{
FilterBtn = AddButton("", Wpf.Resources.filter.AsBitmapImage(), DoFilter);
FilterBtn.Margin = new Thickness(0, 2, 7, 0);
FilterBtn.Padding = new Thickness(0);
FilterBtn.Visibility = ShowFilterList ? Visibility.Visible : Visibility.Collapsed;
Options.BeginUpdate();
if (Security.CanEdit())
Options.Add(DynamicGridOption.AddRows).Add(DynamicGridOption.EditRows);
if (Security.CanDelete())
Options.Add(DynamicGridOption.DeleteRows);
if (Security.CanImport())
Options.Add(DynamicGridOption.ImportData);
if (Security.CanExport())
Options.Add(DynamicGridOption.ExportData);
if (Security.CanMerge())
Options.Add(DynamicGridOption.MultiSelect);
Options.EndUpdate();
MergeBtn = AddButton("Merge", Wpf.Resources.merge.AsBitmapImage(Color.White), DoMerge);
var fields = DatabaseSchema.Properties(typeof(TEntity));
foreach (var field in fields)
if (!MasterColumns.Any(x => x.ColumnName == field.Name))
MasterColumns.Add(new DynamicGridColumn { ColumnName = field.Name });
var cols = LookupFactory.DefineColumns();
// Minimum Columns for Lookup values
foreach (var col in cols.Items)
HiddenColumns.Add(CoreUtils.CreateLambdaExpression(col.Property));
// Minimum Columns for Successful Saving
// This should be cross-checked with the relevant Store<>
// so that clients will (usually) provide sufficient columns for saving
var required = LookupFactory.RequiredColumns();
foreach (var col in required.Items)
HiddenColumns.Add(CoreUtils.CreateLambdaExpression(col.Property));
//HiddenColumns.Add(x => x.ID);
if (typeof(TEntity).GetInterfaces().Contains(typeof(IIssues)))
{
HiddenColumns.Add(x => (x as IIssues)!.Issues);
var coltype = typeof(DynamicIssuesColumn<>).MakeGenericType(typeof(TEntity));
ActionColumns.Add((Activator.CreateInstance(coltype, this) as DynamicActionColumn)!);
}
SetupFilterColumns();
}
[MemberNotNull(nameof(FilterColumns))]
private void SetupFilterColumns()
{
if (typeof(TEntity).GetCustomAttribute() is AutoEntity auto)
{
if (auto.Generator is not null)
{
var columns = auto.Generator.IDColumns;
FilterColumns = columns.Select(x => new Column(x.Property)).ToArray();
}
else
{
FilterColumns = Array.Empty>();
}
}
else
{
FilterColumns = new[] { new Column(x => x.ID) };
}
foreach (var column in FilterColumns)
{
AddHiddenColumn(column.Property);
}
}
protected override void BeforeLoad(IDynamicEditorForm form)
{
form.ReadOnly = !Security.CanEdit();
base.BeforeLoad(form);
}
public string? ColumnsTag { get; set; }
protected override void OptionsChanged(object sender, EventArgs args)
{
base.OptionsChanged(sender, args);
if (MergeBtn != null)
MergeBtn.Visibility = Visibility.Collapsed;
ShowFilterList = Options.Contains(DynamicGridOption.FilterRows);
}
protected override void SelectItems(CoreRow[]? rows)
{
base.SelectItems(rows);
MergeBtn.Visibility = Options.Contains(DynamicGridOption.MultiSelect) && typeof(TEntity).IsAssignableTo(typeof(IMergeable)) && Security.CanMerge() && rows != null && rows.Length > 1
? Visibility.Visible
: Visibility.Collapsed;
}
/*private Filter? CreateFilter(Dictionary criteria) where T : Entity
{
Filter? filter = null;
foreach (var key in criteria.Keys)
{
var exp = CoreUtils.GetPropertyExpression(key);
var flt = new Filter(exp).IsEqualTo(criteria[key]);
if (filter != null)
filter.Ands.Add(flt);
else
filter = flt;
}
return filter;
}
private Columns CreateColumns(List columnnames) where T : Entity
{
var expressions = new List>>();
foreach (var columnname in columnnames)
try
{
var expression = CoreUtils.GetPropertyExpression(columnname);
expressions.Add(expression);
}
catch (Exception e)
{
Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
}
if (!columnnames.Contains("ID"))
expressions.Add(CoreUtils.GetPropertyExpression("ID"));
//if (!columnnames.Contains("Attributes"))
// expressions.Add(CoreUtils.GetPropertyExpression("Attributes"));
return new Columns(expressions.ToArray());
}
private SortOrder? CreateSortOrder(string columnname) where T : Entity
{
if (string.IsNullOrWhiteSpace(columnname))
return null;
var expression = CoreUtils.GetPropertyExpression(columnname);
return new SortOrder(expression);
}*/
public event OnReloadEventHandler? OnReload;
protected override void Reload(Filters criteria, Columns columns, ref SortOrder? sort,
Action action)
{
//if (sort == null)
// sort = new SortOrder(x => x.Sort);
if (SelectedFilter != null)
criteria.Add(SelectedFilter.Item2);
OnReload?.Invoke(this, criteria, columns, ref sort);
new Client().Query(criteria.Combine(), columns, sort, action);
}
private CoreRow[]? SelectedBeforeRefresh;
protected override void OnAfterRefresh()
{
base.OnAfterRefresh();
if (SelectedBeforeRefresh is not null)
{
var selectedValues = SelectedBeforeRefresh
.Select(x => FilterColumns.Select(c => x[c.Property]).ToList())
.ToList();
SelectedBeforeRefresh = null;
var selectedRows = Data.Rows.Where(r =>
{
return selectedValues.Any(v =>
{
for (int i = 0; i < v.Count; ++i)
{
if (v[i]?.Equals(r[FilterColumns[i].Property]) != true)
return false;
}
return true;
});
}).ToArray();
SelectedRows = selectedRows;
SelectItems(selectedRows);
}
}
public override void Refresh(bool reloadcolumns, bool reloaddata)
{
SelectedBeforeRefresh = SelectedRows;
base.Refresh(reloadcolumns, reloaddata);
}
IColumns IDynamicDataGrid.LoadEditorColumns() => LoadEditorColumns();
public Columns LoadEditorColumns()
{
var result = new Columns().Default(
ColumnType.IncludeOptional,
ColumnType.IncludeForeignKeys,
ColumnType.IncludeUserProperties,
ColumnType.IncludeEditable);
var cols = DataColumns();
foreach (var col in cols.Items)
if (!result.Items.Any(x => string.Equals(x.Property, col.Property)))
result.Add(col.Property);
var props = DatabaseSchema.Properties(typeof(TEntity))
.Where(x => x.Setter() != null)
.OrderBy(x => CoreUtils.GetPropertySequence(typeof(TEntity), x.Name));
foreach (var col in result.Items)
{
var prop = DatabaseSchema.Property(typeof(TEntity), col.Property);
if (prop?.Editor is DataLookupEditor dataLookup)
{
foreach (var lookupColumn in LookupFactory.DefineFilterColumns(dataLookup.Type).ColumnNames())
{
result.Add(lookupColumn);
}
}
}
return result;
}
private Filter? GetRowFilter(CoreRow row)
{
var newFilters = new Filters();
foreach (var column in FilterColumns)
{
newFilters.Add(new Filter(column.Property).IsEqualTo(row[column.Property]));
}
return newFilters.Combine();
}
protected override TEntity[] LoadItems(CoreRow[] rows)
{
Filter? filter = null;
var results = new List();
for (var i = 0; i < rows.Length; i = i + ChunkSize)
{
var chunk = rows.Skip(i).Take(ChunkSize);
foreach (var row in chunk)
{
var newFilter = GetRowFilter(row);
if (newFilter is not null)
{
//var id = row.Get(x => x.ID);
if (filter is null)
filter = newFilter;//new Filter(x => x.ID).IsEqualTo(id);
else
filter = filter.Or(newFilter);//.Or(x => x.ID).IsEqualTo(id);
}
}
var columns = LoadEditorColumns(); // new Columns().Default(ColumnType.IncludeOptional, ColumnType.IncludeForeignKeys, ColumnType.IncludeUserProperties);
var data = new Client().Query(filter, columns);
results.AddRange(data.Rows.Select(x => x.ToObject()));
}
return results.ToArray();
}
protected override TEntity LoadItem(CoreRow row)
{
var id = row.Get(x => x.ID);
var filter = GetRowFilter(row);//new Filter(x => x.ID).IsEqualTo(id);
return new Client().Load(filter).FirstOrDefault()
?? throw new Exception($"No {typeof(TEntity).Name} with ID {id}");
}
public override void SaveItem(TEntity item)
{
new Client().Save(item, "Edited by User");
}
public override void SaveItems(TEntity[] items)
{
new Client().Save(items, "Edited by User");
}
protected override void DeleteItems(params CoreRow[] rows)
{
var deletes = new List();
foreach (var row in rows)
{
var delete = new TEntity();
foreach (var column in FilterColumns)
{
CoreUtils.SetPropertyValue(delete, column.Property, row[column.Property]);
}
//var delete = /* row.ToObject(); */ new TEntity { ID = row.Get(x => x.ID) };
deletes.Add(delete);
}
new Client().Delete(deletes, "Deleted on User Request");
}
private object GetPropertyValue(Expression member, object obj)
{
var objectMember = Expression.Convert(member, typeof(object));
var getterLambda = Expression.Lambda>(objectMember);
var getter = getterLambda.Compile();
return getter();
}
protected override void ObjectToRow(TEntity obj, CoreRow row)
{
foreach (var column in Data.Columns)
{
var prop = DatabaseSchema.Property(obj.GetType(), column.ColumnName);
if (prop is StandardProperty)
row.Set(column.ColumnName, CoreUtils.GetPropertyValue(obj, prop.Name));
else if (prop is CustomProperty)
row.Set(column.ColumnName, obj.UserProperties[column.ColumnName]);
}
//base.ObjectToRow(obj, row);
}
//private void Auditgrid_OnReload(object sender, Dictionary criteria, ref String sort)
//{
// criteria["DocumentType"] = typeof(TEntity).EntityName();
// criteria["DocumentID"] = (sender as FrameworkElement).Tag;
// sort = "TimeStamp";
//}
public override void LoadEditorButtons(TEntity item, DynamicEditorButtons buttons)
{
base.LoadEditorButtons(item, buttons);
if (ClientFactory.IsSupported())
buttons.Add("Audit Trail", Wpf.Resources.view.AsBitmapImage(), item, AuditTrailClick);
}
private void AuditTrailClick(object sender, object item)
{
var entity = (TEntity)item;
var window = new AuditWindow(entity.ID);
window.ShowDialog();
}
protected override void LoadColumnsMenu(ContextMenu menu)
{
base.LoadColumnsMenu(menu);
//menu.Items.Add(new Separator());
var ResetColumns = new MenuItem { Header = "Reset Columns to Default" };
ResetColumns.Click += ResetColumnsClick;
menu.Items.Add(ResetColumns);
if (Security.IsAllowed())
{
menu.Items.Add(new Separator());
var UpdateDefaultColumns = new MenuItem { Header = "Mark Columns as Default" };
UpdateDefaultColumns.Click += UpdateDefaultColumnsClick;
menu.Items.Add(UpdateDefaultColumns);
}
}
private void ResetColumnsClick(object sender, RoutedEventArgs e)
{
VisibleColumns.Clear();
SaveColumns(VisibleColumns);
Refresh(true, true);
}
private void UpdateDefaultColumnsClick(object sender, RoutedEventArgs e)
{
var tag = GetTag();
new GlobalConfiguration(tag).Save(VisibleColumns);
new UserConfiguration(tag).Delete();
Refresh(true, true);
}
private string GetTag()
{
var tag = typeof(TEntity).Name;
if (!string.IsNullOrWhiteSpace(ColumnsTag))
tag = string.Format("{0}.{1}", tag, ColumnsTag);
return tag;
}
private BaseEditor GetColumnEditor(DynamicGridColumn column)
{
try
{
var prop = DatabaseSchema.Property(typeof(TEntity), column.ColumnName);
if (prop != null) return prop.Editor;
var type = CoreUtils.GetProperty(typeof(TEntity), column.ColumnName);
return type.GetEditor() ?? new NullEditor();
}
catch (Exception e)
{
Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
}
return new NullEditor();
}
protected override DynamicGridColumns LoadColumns()
{
var tag = GetTag();
var user = Task.Run(() => new UserConfiguration(tag).Load());
user.Wait();
var global = Task.Run(() => new GlobalConfiguration(tag).Load());
global.Wait();
//Task.WaitAll(user, global);
var columns = user.Result.Any() ? user.Result : global.Result;
if (!columns.Any())
GenerateColumns(columns); //override this to provide specific columns on startup
var removes = columns.Where(x => x is null || string.IsNullOrWhiteSpace(x.ColumnName) || DatabaseSchema.Property(typeof(TEntity), x.ColumnName) == null || GetColumnEditor(x) is NullEditor)
.ToArray();
foreach (var remove in removes)
columns.Remove(remove);
if (columns.Count == 0)
columns.AddRange(base.LoadColumns());
foreach (var column in columns)
try
{
var prop = DatabaseSchema.Property(typeof(TEntity), column.ColumnName);
if (prop != null)
{
column.Editor = prop.Editor;
}
else
{
var type = CoreUtils.GetProperty(typeof(TEntity), column.ColumnName);
column.Editor = type.GetEditor() ?? new NullEditor();
}
}
catch (Exception e)
{
Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
}
columns.RemoveAll(x => DatabaseSchema.Property(typeof(TEntity), x.ColumnName) == null || GetColumnEditor(x) is NullEditor);
return columns;
}
protected virtual void GenerateColumns(DynamicGridColumns columns)
{
var cols = new Columns().Default(Options.Contains(DynamicGridOption.DirectEdit)
? new[] { ColumnType.IncludeForeignKeys, ColumnType.ExcludeID }
: new ColumnType[] {
ColumnType.IncludeLinked, ColumnType.IncludeNestedLinks, ColumnType.IncludeFormulae,
ColumnType.IncludeAggregates, ColumnType.ExcludeID });
if (cols != null)
{
foreach (var col in cols.Items)
{
var mc = MasterColumns.FirstOrDefault(x => x.ColumnName.Equals(col.Property));
if (mc != null && !(mc.Editor is NullEditor) && mc.Editor.Visible != Visible.Hidden)
columns.Add(mc);
}
}
}
protected override void SaveColumns(DynamicGridColumns columns)
{
var tag = GetTag();
new UserConfiguration(tag).Save(columns);
}
protected override BaseEditor? GetEditor(object item, DynamicGridColumn column)
{
var prop = DatabaseSchema.Properties(typeof(TEntity)).FirstOrDefault(x => x.Name == column.ColumnName);
if (prop != null)
return prop.Editor;
return base.GetEditor(item, column);
}
protected override object? GetEditorValue(object item, string name)
{
if (item is TEntity entity)
{
var prop = DatabaseSchema.Properties(typeof(TEntity)).FirstOrDefault(x => x.Name == name);
if (prop is CustomProperty)
{
if (entity.UserProperties.ContainsKey(name))
return entity.UserProperties[name];
return null;
}
}
return base.GetEditorValue(item, name);
}
protected override void SetEditorValue(object item, string name, object value)
{
var prop = DatabaseSchema.Properties(typeof(TEntity)).FirstOrDefault(x => x.Name == name);
if (prop is CustomProperty && item is TEntity entity)
{
entity.UserProperties[name] = value;
}
else
{
base.SetEditorValue(item, name, value);
}
}
protected bool Duplicate(
CoreRow row,
Expression> codefield,
Type[] childtypes)
{
var id = row.Get(x => x.ID);
var code = row.Get(codefield) as string;
var tasks = new List();
var itemtask = Task.Run(() =>
{
var filter = new Filter(x => x.ID).IsEqualTo(id);
var result = new Client().Load(filter).FirstOrDefault()
?? throw new Exception("Entity does not exist!");
return result;
});
tasks.Add(itemtask);
//itemtask.Wait();
Task>? codetask = null;
if (!string.IsNullOrWhiteSpace(code))
{
codetask = Task.Run(() =>
{
var columns = new Columns(codefield);
//columns.Add(codefield);
var filter = new Filter(codefield).BeginsWith(code);
var table = new Client().Query(filter, columns);
var result = table.Rows.Select(x => x.Get(codefield) as string).ToList();
return result;
});
tasks.Add(codetask);
}
//codetask.Wait();
var children = new Dictionary();
foreach (var childtype in childtypes)
{
var childtask = Task.Run(() =>
{
var prop = childtype.GetProperties().FirstOrDefault(x =>
x.PropertyType.GetInterfaces().Contains(typeof(IEntityLink)) &&
x.PropertyType.GetInheritedGenericTypeArguments().FirstOrDefault() == typeof(TEntity));
if (prop is not null)
{
var filter = Core.Filter.Create(childtype);
filter.Expression = CoreUtils.GetMemberExpression(childtype, prop.Name + ".ID");
filter.Operator = Operator.IsEqualTo;
filter.Value = id;
var sort = LookupFactory.DefineSort(childtype);
var client = ClientFactory.CreateClient(childtype);
var table = client.Query(filter, null, sort);
foreach (var r in table.Rows)
{
r["ID"] = Guid.Empty;
r[prop.Name + ".ID"] = Guid.Empty;
}
children[childtype] = table;
}
else
{
Logger.Send(LogType.Error, "", $"DynamicDataGrid<{typeof(TEntity)}>.Duplicate(): No parent property found for child type {childtype}");
}
});
tasks.Add(childtask);
//childtask.Wait();
}
//var manytomanys = CoreUtils.TypeList(
// AppDomain.CurrentDomain.GetAssemblies(),
// x => x.GetInterfaces().Any(intf => intf.IsGenericType && intf.GetGenericTypeDefinition() == typeof(IManyToMany<,>) && intf.GenericTypeArguments.Contains(typeof(T)))
//);
//Task childtask = Task.Run(() =>
//{
// var result = new CoreTable();
// result.LoadColumns(typeof(TChild));
// var children = new Client().Load(new Filter(x => linkfield).IsEqualTo(id));
// foreach (var child in children)
// {
// child.ID = Guid.Empty;
// String linkprop = CoreUtils.GetFullPropertyName(linkfield, ".");
// CoreUtils.SetPropertyValue(child, linkprop, Guid.Empty);
// var newrow = result.NewRow();
// result.LoadRow(newrow, child);
// result.Rows.Add(newrow);
// }
// return result;
//});
//tasks.Add(childtask);
Task.WaitAll(tasks.ToArray());
var item = itemtask.Result;
item.ID = Guid.Empty;
if (codetask != null)
{
var codes = codetask.Result;
var i = 1;
while (codes.Contains(string.Format("{0} ({1})", code, i)))
i++;
var codeprop = CoreUtils.GetFullPropertyName(codefield, ".");
CoreUtils.SetPropertyValue(item, codeprop, string.Format("{0} ({1})", code, i));
}
var grid = new DynamicDataGrid();
return grid.EditItems(new[] { item }, t => children.ContainsKey(t) ? children[t] : null, true);
}
private bool DoFilter(Button button, CoreRow[] rows)
{
var menu = new ContextMenu();
var none = new MenuItem { Header = "Clear Filters", Tag = null };
none.Click += Filter_Click;
menu.Items.Add(none);
var tag = GetTag();
var filters = new GlobalConfiguration(tag).Load();
foreach (var filter in filters)
{
var item = new MenuItem { Header = filter.Name, Tag = filter };
if (SelectedFilter?.Item1 == filter.Name)
{
item.IsChecked = true;
}
item.Click += Filter_Click;
menu.Items.Add(item);
}
if (Security.IsAllowed())
{
menu.Items.Add(new Separator());
var manage = new MenuItem { Header = "Manage Filters" };
manage.Click += (s, e) =>
{
var window = new DynamicGridFilterEditor(filters, typeof(TEntity));
if (window.ShowDialog() == true)
{
new GlobalConfiguration(tag).Save(filters);
}
};
menu.Items.Add(manage);
}
menu.IsOpen = true;
return false;
}
private void Filter_Click(object sender, RoutedEventArgs e)
{
var tag = (sender as MenuItem)?.Tag;
string text = "";
Bitmap image;
if (tag is DynamicGridFilter filter)
{
SelectedFilter = new(filter.Name, Serialization.Deserialize>(filter.Filter));
image = Wpf.Resources.filter_set;
if ((bool)(OnFilterSelected?.Invoke(filter)))
text = filter.Name;
}
else
{
SelectedFilter = null;
image = Wpf.Resources.filter;
OnFilterSelected?.Invoke(new DynamicGridFilter());
}
UpdateButton(FilterBtn, image.AsBitmapImage(), text);
Refresh(false, true);
}
private bool DoMerge(Button arg1, CoreRow[] arg2)
{
if (arg2 == null || arg2.Length <= 1)
return false;
var targetid = arg2.Last().Get(x => x.ID);
var target = arg2.Last().ToObject().ToString();
var otherids = arg2.Select(r => r.Get(x => x.ID)).Where(x => x != targetid).ToArray();
string[] others = arg2.Where(r => otherids.Contains(r.Get("ID"))).Select(x => x.ToObject().ToString()!).ToArray();
var rows = arg2.Length;
if (MessageBox.Show(
string.Format(
"This will merge the following items:\n\n- {0}\n\n into:\n\n- {1}\n\nAfter this, the items will be permanently removed.\nAre you sure you wish to do this?",
string.Join("\n- ", others),
target
),
"Merge Items Warning",
MessageBoxButton.YesNo,
MessageBoxImage.Stop) != MessageBoxResult.Yes
)
return false;
using (new WaitCursor())
{
var types = CoreUtils.TypeList(
AppDomain.CurrentDomain.GetAssemblies(),
x =>
x.IsClass
&& !x.IsGenericType
&& x.IsSubclassOf(typeof(Entity))
&& !x.Equals(typeof(AuditTrail))
&& !x.Equals(typeof(TEntity))
&& x.GetCustomAttribute() == null
).ToArray();
foreach (var type in types)
{
var props = CoreUtils.PropertyList(
type,
x =>
x.PropertyType.GetInterfaces().Contains(typeof(IEntityLink))
&& x.PropertyType.GetInheritedGenericTypeArguments().Contains(typeof(TEntity))
);
foreach (var prop in props)
{
var propname = string.Format(prop.Name + ".ID");
var filter = Core.Filter.Create(type);
filter.Expression = CoreUtils.CreateMemberExpression(type, propname);
filter.Operator = Operator.InList;
filter.Value = otherids;
var columns = Columns.Create(type)
.Add("ID")
.Add(propname);
var updates = ClientFactory.CreateClient(type).Query(filter, columns).Rows.Select(r => r.ToObject(type)).ToArray();
if (updates.Any())
{
foreach (var update in updates)
CoreUtils.SetPropertyValue(update, propname, targetid);
ClientFactory.CreateClient(type).Save(updates,
string.Format("Merged {0} Records", typeof(TEntity).EntityName().Split('.').Last()));
}
}
}
var histories = new Client()
.Query(new Filter(x => x.EntityID).InList(otherids), new Columns(x => x.EntityID)).Rows
.Select(x => x.ToObject()).ToArray();
foreach (var history in histories)
history.EntityID = targetid;
if (histories.Any())
new Client().Save(histories, "");
var deletes = new List