在表中移动排序顺序会导致IIS崩溃

时间:2016-05-10 00:57:36

标签: asp.net asp.net-mvc asp.net-mvc-5

当前项目:

  • ASP.NET 4.5.2
  • MVC 5

我有一个条目的以下更新代码:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> EditClient(ModifyClientViewModel model) {
  Clients clients = await db.Clients.FindAsync(new Guid(model.ClientId));
  if(clients == null) { return HttpNotFound(); }
  try {
    if(ModelState.IsValid) {
      TextInfo ti = CultureInfo.CurrentCulture.TextInfo;
      clients.ClientName = ti.ToTitleCase(model.ClientName.Trim());
      clients.CityId = new Guid(model.CityId);
      clients.SortOrder = MakeRoom(model.SortOrder, model.ClientId);
      clients.Active = model.Active;
      clients.Modified = DateTime.UtcNow;
      clients.TouchedBy = User.GetClaimValue("UserGuid");
      db.Entry(clients).State = EntityState.Modified;
      await db.SaveChangesAsync();
      return RedirectToAction("Index");
    }
  } catch(DbUpdateConcurrencyException ex) {
// removed
  }
  return View(model);
}

重要的一行是第11行,它调用MakeRoom(),我需要能够改变相关条目的排序顺序,假设它已被更改。

现在,关于排序顺序的一句话:它是SQL Server 2012中的short(smallint)列,因为无论如何可能永远不会有超过几百个条目。这些是从1开始的连续数字。数字序列中没有间隙(删除会将其他所有人拉下来),因此从该列中提取最大值也将描述其中的行数。虽然两行可以具有相同的SortOrder(该列允许重复以使排序代码起作用),但这不应该是持久状态 - 只有在实际的排序代码运行时它才会存在。排序完成后,不应存在重复。

如果有人记得他们的第一年编程课程,这个递归应该类似于“冒泡排序”,其中一个元素冒泡到它应该的位置。除此之外,我们通过实际更改行的SortOrder值来“冒泡”。

我有一些假设的代码是一个递归算法,它采用所需的位置和当前位置的ID,并通过交换当前位置使当前位置接近所需位置循环回到自身之前的下一个更近的项目。一旦current = desired,递归就应该结束,将所需的值反馈回上面的代码。

以下是我所做的:

public short MakeRoom(short newSort, string id) {
  var currentClient = db.Clients.Find(new Guid(id));
  short currentSort = currentClient.SortOrder;
  if(currentSort != newSort) { // need to shift the current value
    short shiftSort = (currentSort < newSort ? currentSort++ : currentSort--); //are we shifting up or down?
    var shiftClient = db.Clients.Where(x => x.SortOrder == shiftSort).First();
    Clients clients = db.Clients.Find(shiftClient.ClientId);
    clients.SortOrder = currentSort; // give the next row the current client's sort number
    db.Entry(clients).State = EntityState.Modified;
    db.SaveChanges();
    currentClient.SortOrder = shiftSort; // give the current row the next row's sort number
    db.Entry(currentClient).State = EntityState.Modified;
    db.SaveChanges();
    MakeRoom(newSort, id); //One swap done, proceed to the next swap.
  }
  return newSort;
}

正如您所看到的,我的“基本条件”是当前是否需要,如果匹配,则应该忽略所有代码以支持return语句。如果它不匹配,代码执行一个班次,然后调用自己进行下一个班次(通过当前客户的ID重新评估当前的排序值,因为当前的排序号现在因为刚刚 - 先前班次执行)。完成所有轮班并且当前=期望时,代码将以return语句退出。

我希望有人可以检查我的逻辑,看看它是否是我的问题所在。我似乎有一个无法实际接触数据库的无限循环,因为数据库中的所有值实际上都没有被改变 - IIS最终会崩溃。

编辑1:发现堆栈溢出。结果问题是

    var shiftClient = db.Clients.Where(x => x.SortOrder == shiftSort).First();

问题是,我不确定为什么。在前一行中,我将shiftSort设置为从当前排序中取消一个(取决于方向)。然后我想通过这个shiftSort值(这是SortOrder)获取ClientID。由于列中应该只有一个这样的SortOrder值,我应该能够使用上面的行搜索它。但显然它会引发堆栈溢出。

所以,具体来说:假设我去了一个SortOrder为53的客户端。我希望他的SortOrder为50.最后一行取50(newSort)并发现它少了比currentSort,所以它为shiftSort赋值currentSort--(53-1 = 52)。上面的行应该取52的值,并返回该52存在的行,以便在以下行中,可以使用52的ClientID将该行修改为53(交换)。

连连呢?我不明白为什么我在这里遇到堆栈溢出。

编辑2 :通过MakeRoom方法进行了修改,但我仍然遇到受影响行的Stack Overflow:

public short MakeRoom(short newSort, string id) {
  Clients currentClient = db.Clients.Find(new Guid(id));
  short currentSort = currentClient.SortOrder;
  if(currentSort != newSort) { // need to shift the current sort
    short shiftSort = (currentSort < newSort ? currentSort++ : currentSort--);
    Clients shiftClient = db.Clients.Where(x => x.SortOrder == shiftSort).FirstOrDefault(); //Stack Overflow is here -- why??
    shiftClient.SortOrder = currentSort;
    db.Entry(shiftClient).State = EntityState.Modified;
    currentClient.SortOrder = shiftSort;
    db.Entry(currentClient).State = EntityState.Modified;
    db.SaveChanges();
    MakeRoom(newSort, id);
  }
  return newSort;
}

编辑3:我再次更改了我的MakeRoom方法:

public void MakeRoom(short newSort, string id) {
  var currentClient = db.Clients.Find(new Guid(id));
  short currentSort = currentClient.SortOrder;
  if(currentSort < newSort) {
    var set = db.Clients.Where(x => x.SortOrder > currentSort && x.SortOrder <= newSort).OrderBy(c => c.SortOrder).ToList();
    short i = set.First().SortOrder;
    set.ForEach(c => {
      c.SortOrder = i--;
      c.Modified = DateTime.UtcNow;
      c.TouchedBy = User.GetClaimValue("UserGuid");
      db.Entry(c).State = EntityState.Modified;
    });
    db.SaveChanges();
  } else if(currentSort > newSort) {
    var set = db.Clients.Where(x => x.SortOrder >= newSort && x.SortOrder < currentSort).OrderBy(c => c.SortOrder).ToList();
    short i = set.First().SortOrder;
    set.ForEach(c => {
      c.SortOrder = i++;
      c.Modified = DateTime.UtcNow;
      c.TouchedBy = User.GetClaimValue("UserGuid");
      db.Entry(c).State = EntityState.Modified;
    });
    db.SaveChanges();
  }
}

但是即使调试器清楚地遍历代码并以正确的方式,实际的DB值也不会改变。

2 个答案:

答案 0 :(得分:0)

让数据库为您排序。

var first = db.Clients.FirstOrDefault(c => c.Guid == guid);

var set = db.Clients
    .Where(c => c.SortOrder >= first.SortOrder)
    .OrderBy(c => c.SortOrder).ToList();

int i = set.First().SortOrder;  // or 1

set.ForEach(c => {
    c.SortOrder = i++;
    db.Entry(c).State = EntityState.Modified;
});
db.SaveChanges();

答案 1 :(得分:0)

喔。我的。善良。谈论俯视导致房屋倒塌的单螺钉。

我的问题不在于导致堆栈溢出的行 - 我的问题出在前一行:

short shiftSort = (currentSort < newSort ? currentSort++ : currentSort--);

你能看到问题吗?我没有。到现在。在尝试实现solution provided by Jasen时(感谢您的努力,先生,他们非常感谢),我被迫更仔细地查看我的代码,我发现了一些奇怪的事情:增量/减量。这些应该是为了改变项目的实际值递增/递减,即使它是作业的一部分。所以难怪我的脚本是f ** cking up - 我在将currentSort分配给shiftSort的同时更改了[HttpPost] [ValidateAntiForgeryToken] public async Task<ActionResult> EditClient(ModifyClientViewModel model) { Clients clients = await db.Clients.FindAsync(new Guid(model.ClientId)); if(clients == null) { return HttpNotFound(); } try { if(ModelState.IsValid) { MakeRoom(model.SortOrder, clients.SortOrder); TextInfo ti = CultureInfo.CurrentCulture.TextInfo; clients.ClientName = ti.ToTitleCase(model.ClientName.Trim()); clients.CityId = new Guid(model.CityId); clients.SortOrder = model.SortOrder; clients.Active = model.Active; clients.Modified = DateTime.UtcNow; clients.TouchedBy = User.GetClaimValue("UserGuid"); db.Entry(clients).State = EntityState.Modified; await db.SaveChangesAsync(); return RedirectToAction("Index"); } } catch(DbUpdateConcurrencyException ex) { // Ignore } return View(model); } 的值。

我所做的是双重的:因为没有真正需要传回一个值,我改变了原来的HttpPost方法:

public void MakeRoom(short newSort, short oldSort) {
  if(oldSort != newSort) { // need to shift the current sort
    short shiftSort = (oldSort < newSort ? (short)(oldSort + 1) : (short)(oldSort - 1));
    Clients currentClient = db.Clients.Where(x => x.SortOrder == oldSort).FirstOrDefault();
    currentClient.SortOrder = shiftSort;
    db.Entry(currentClient).State = EntityState.Modified;
    Clients shiftClient = db.Clients.Where(x => x.SortOrder == shiftSort).FirstOrDefault();
    shiftClient.SortOrder = oldSort;
    db.Entry(shiftClient).State = EntityState.Modified;
    db.SaveChanges();
    MakeRoom(newSort, shiftSort);
  }
}

注意MakeRoom()如何移到前面,只给出Source和Destination值?不再需要传递ID。

然后是实际的方法:

            grid.on('dgrid-select', function () {
                var selectedRows = [];
                for (var e in grid.selection) {
                    grid.selection[e] && selectedRows.push(e)
                }

                //get the selector column and checkbox from that column
                var column = grid.columns["select"];
                var headerCheckbox = column._selectorHeaderCheckbox;
                if (selectedRows.length === grid.get('total')) {
                    //update the checkbox when the selection count equal to total count.
                    headerCheckbox.indeterminate = false;
                    headerCheckbox.checked = true;
                    headerCheckbox.setAttribute('aria-checked', 'true');
                    grid.allSelected = true;
                }
            });

            grid.on('dgrid-deselect', function () {
                var selectedRows = [];
                for (var e in grid.selection) {
                    grid.selection[e] && selectedRows.push(e)
                }

                //get the selector column and checkbox from that column
                var column = grid.columns["select"];
                var headerCheckbox = column._selectorHeaderCheckbox;
                if (selectedRows.length === 0) {
                    headerCheckbox.indeterminate = false;
                    headerCheckbox.checked = false;
                    headerCheckbox.setAttribute('aria-checked', 'false');
                    grid.allSelected = false;
                }
            });

现在看看shiftSort的赋值 - 我为它指定了由单个数字修改的oldSort值。我很惭愧地说,这让我们发挥了重要作用。

我的代码现在可以完美运行,甚至可以立即使用许多中间项目。我可以选择一个SortOrder为3的项目,并将它移动到一个53的SortOrder,从53到4的所有内容都完美地向下移动一个以便为之前具有SortOrder的项目腾出空间(在SortOrder 53中) 3。

这种方法的好处在于它可以同样好地用于删除(以防止编号中的间隙)和添加(当你想要新项目而不是结束时)。对于删除,您只需将删除项目之后的所有内容(例如,SortOrder 33到Max SortOrder)向下移动一个,对于添加项,您将所有内容从插入点移至Max SortOrder向上,然后插入您的值。

我希望这可以帮助任何追随我的人,命运帮助他们与Google合作 - 他们会得到大量关于排序和分页输出的结果,而且很少关于改变< em> value 源和目标值之间每个条目的排序顺序。