实现“ReOrderable Collection”并将其保留到数据库的最佳方式

我的域名对象:

public class MyDomainObject { public Guid Id { get; set; } public string Name { get; set; } public int DisplayOrder { get; set; } } 

假设样本数据:

 var list = new List() { new MyDomainObject {Name = "Element1", DisplayOrder = 0}, new MyDomainObject {Name = "Element2", DisplayOrder = 1}, new MyDomainObject {Name = "Element3", DisplayOrder = 2}, new MyDomainObject {Name = "Element4", DisplayOrder = 3}, }; 

现在我将“Element3”的DisplayOrder从2更改为1.我的列表应如下所示:

  • Element1(DisplayOrder = 0)
  • Element3(DisplayOrder = 1)
  • Element2(DisplayOrder = 2)
  • Element4(DisplayOrder = 3)

现在我删除“Element3”

  • Element1(DisplayOrder = 0)
  • Element2(DisplayOrder = 1)
  • Element4(DisplayOrder = 2)

那么将这种机制持久化到数据库的最佳方法是什么?

基本上我需要一个“ReOrderableCollection”,它将使用OrderBy“DisplayOrder”在数据库中填充,其中Collection Index Match“DisplayOrder”,并通过从Collection Index分配DisplayOrder来保留项目。

我在这里回答了一个关于重新排序的先前/类似问题: 如何设计可以重新排序的表?

这样可以很好地重新保留订单,没有间隙。 取决于大小,重新下订单的列表可能是一个完全可行的选项,对于长列表Mark Byers的想法看起来非常好。

从你的例子看,你似乎总是希望序列没有间隙,从零开始。 但这意味着删除第一个元素将需要更新列表中每个项目的数据库中的行。 这很简单,它可以工作(这些都是好事),但并不总是理想的。 既然你没有真正说明你的意思,那么你要求“最好的方法”,请允许我建议一种替代方法:

DisplayOrder真正重要的不是实际值,而是它们的相对顺序。 如果要提高数据库的性能,可以考虑放宽应该没有间隙的要求,然后尝试查找DisplayOrders的最小数量的更改,以确保存储正确的顺序,即使存在间隙结果序列。 如果您这样做,那么添加,删除或移动单个项目通常只需要更新数据库中的一行,但有时需要移动其他项目以创建一个间隙,其中必须在另外两个项目之间插入项目有连续的DisplayOrders。

您还可以通过从DisplayOrder 100,200,300及更高版本开始来最小化间隙不可用的次数,例如允许在其间插入DisplayOrder 150(或者可能使用real / float类型而不是整数)。

此方法的另一个优点是,如果使用数据库数据比较工具来观察当前版本的数据库与旧版本之间的更改,则更容易看到对显示顺序进行了哪些修改。 您将只看到用户实际移动的项目的显示顺序的更改,而不是每次删除项目时更改列表的一半。 如果使用增量备份策略,它还将减少备份的大小。

我要说的是,在大多数情况下,这些优点并不比天真的方法有显着优势。 这取决于您的系统是否值得实施此系统或只是保持简单。 如果有疑问,请保持简单。 对于具有小列表,几乎没有修改以及您不关心更改历史记录的系统,每次使用新的DisplayOrders覆盖整个列表可能会很好。

我可以看到,DisplayOrder似乎具有与集合的index属性相同的值。 所以我将尝试使用它而不是DisplayOrder属性。 在DB上,我将使用DisplayOrder列来读取和保存项目,而不是域对象。 HTH ema

现在我假设您确实希望始终重新组织您的列表,以便DisplayOrder从0开始并且无间隙地增加,并且您希望这自动发生。 您可以实现自己的集合类型和接口IDisplayOrderable,并让您更改列表的类型成员也自动更新集合中项目的DisplayOrder。 与我的另一个关于将数据存储在数据中的替代方法的答案相反,这个答案显示了如何编写一个客户端类,可以更容易地自动将对象中的DisplayOrder与列表索引同步,这样当你准备好将更改提交到数据库,已经为您正确设置了DisplayOrder字段。

我认为答案最好是作为一些源代码:

 using System; using System.Collections.Generic; using System.Linq; interface IDisplayOrderable { int DisplayOrder { get; set; } } class ReorderableList : IList where T : IDisplayOrderable { List list = new List(); private void updateDisplayOrders() { int displayOrder = 0; foreach (T t in list) { t.DisplayOrder = displayOrder++; } } public ReorderableList() { } public ReorderableList(IEnumerable items) { list = new List(items.OrderBy(item => item.DisplayOrder)); } public void Insert(int index, T item) { list.Insert(index, item); updateDisplayOrders(); } public void Add(T item) { list.Add(item); updateDisplayOrders(); } public bool Remove(T item) { bool result = list.Remove(item); if (result) updateDisplayOrders(); return result; } public IEnumerator GetEnumerator() { return list.GetEnumerator(); } // TODO: Other members and methods required to implement IList... } class Item : IDisplayOrderable { public string Name { get; set; } public int DisplayOrder { get; set; } } class Program { static void Main() { Item foo = new Item { Name = "foo", DisplayOrder = 0 }; Item bar = new Item { Name = "bar", DisplayOrder = 1 }; Item baz = new Item { Name = "baz", DisplayOrder = 2 }; // Pretend this came from the database. IEnumerable query = new Item[] { bar, foo }; // The constructor automatically reorder the elements. ReorderableList items = new ReorderableList(query); items.Add(baz); items.Remove(foo); items.Insert(1, foo); foreach (Item item in items) Console.WriteLine("{0} : {1}", item.Name, item.DisplayOrder); } } 

输出:

 bar : 0 foo : 1 baz : 2 

也许这就是你想要的那种答案?

我可能通过创建一个自定义List来创建一个解决方案,该List在构造函数参数中获取Lamba Expression,以便列表能够自我更新项属性“DisplayOrder”。

样本类

 public class MyItem { public string Name { get; set; } public int DisplayOrder { get; set; } } 

示例程序

 public class Program { static void Main(string[] args) { var list = new DisplayOrderableList(p => p.DisplayOrder) { new MyItem{ Name = "Item 1"}, new MyItem{ Name = "Item 2"}, new MyItem{ Name = "Item 3"}, }; var item = list.Where(p => p.Name == "Item 2").FirstOrDefault(); list.MoveUp(item); list.ForEach(p => Console.WriteLine("{0}-{1}", p.Name, p.DisplayOrder)); Console.WriteLine(); list.MoveDown(item); list.ForEach(p => Console.WriteLine("{0}-{1}", p.Name, p.DisplayOrder)); Console.WriteLine(); Console.ReadLine(); } } 

DisplayOrderableList的自定义实现

 public class DisplayOrderableList : List { #region Private Fields private PropertyInfo _property; #endregion #region Constructors public DisplayOrderableList(Expression> expression) { ValidateExpression(expression); } #endregion #region Public Methods public void MoveUp(T item) { if (!Contains(item)) throw new ArgumentNullException("item", "item doesn't exists in collection"); var idx = IndexOf(item); RemoveAt(idx); if (idx > 0) Insert(idx - 1, item); else Insert(0, item); UpdateDisplayOrder(); } public void MoveDown(T item) { if (!Contains(item)) throw new ArgumentNullException("item", "item doesn't exists in collection"); var idx = IndexOf(item); RemoveAt(idx); if (idx + 1 > Count) Add(item); else Insert(idx + 1, item); UpdateDisplayOrder(); } #endregion #region Private Methods private void UpdateDisplayOrder() { foreach (var item in this) { _property.SetValue(item, IndexOf(item), null); } } #endregion #region Expression Methods private void ValidateExpression(Expression> expression) { var lamba = ToLambaExpression(expression); var propInfo = ToPropertyInfo(lamba); if (!propInfo.CanWrite) { throw new ArgumentException(String.Format("Property {0} as no setters", propInfo.Name)); } _property = propInfo; } private static LambdaExpression ToLambaExpression(Expression expression) { var lambda = expression as LambdaExpression; if (lambda == null) { throw new ArgumentException("Invalid Expression"); } var convert = lambda.Body as UnaryExpression; if (convert != null && convert.NodeType == ExpressionType.Convert) { lambda = Expression.Lambda(convert.Operand, lambda.Parameters.ToArray()); } return lambda; } private static PropertyInfo ToPropertyInfo(LambdaExpression expression) { if (expression == null) { throw new ArgumentNullException("expression", "Expression cannot be null."); } var prop = expression.Body as MemberExpression; if (prop == null) { throw new ArgumentException("Invalid expression"); } var propInfo = prop.Member as PropertyInfo; if (propInfo == null) { throw new ArgumentException("Invalid property"); } return propInfo; } #endregion } 

现在得到以下输出:

 Item 2-0 Item 1-1 Item 3-2 Item 1-0 Item 2-1 Item 3-2 

这是一个概念certificate,应该加强,但它是一个开始。

你怎么看待这件事 ?

我知道这是一个老问题,但是这里和其他问题的评论帮助我解决了类似的问题,我想提供我的代码,以防它帮助其他人寻找类似的东西。 您可以在以下链接中找到我的代码:

如何设计可重新排序的表?