Иерархические данные в Linq - опции и производительность

Ссылка Мацери на окончательный ресурс по арифметике с плавающей запятой действительно является окончательным ответом на этот вопрос. Однако для завершения:

octave:34> fprintf("%.80f\n%.80f\n", 0.95, 1 - 0.05)
0.94999999999999995559107901499373838305473327636718750000000000000000000000000000
0.94999999999999995559107901499373838305473327636718750000000000000000000000000000

octave:35> fprintf("%.80f\n%.80f\n", 0.05, 1 - 0.95)
0.05000000000000000277555756156289135105907917022705078125000000000000000000000000
0.05000000000000004440892098500626161694526672363281250000000000000000000000000000

Другими словами, 0,95 труднее точно представить в плавающей запятой, поэтому любой расчет на первом шаге, который включает 0,95 (либо как вход, либо как выход), обязательно менее точный, чем тот, который использует только 0,05.

Поэтому:

1 - 0.05 = 0.95 (imprecise, due to intrinsic floating-point representation)
(1 - 0.05) - 0.95 = exactly 0 (since both are represented identically imprecisely)

vs

1 - 0.95 = imprecise 0.05 (due to involvement of 0.95 in calculation)
(imprecise 0.05) - (precise 0.05) = not exactly 0 (due to difference in precisions)

ОДНАКО . Следует отметить, что эта разница в точности намного ниже допуска машины (как указано в eps - 2.2204e-16 на моей машине). Следовательно, для всех практических применений 4.1633e-17 равно 0. Если практическим моментом здесь является проверка, является ли результат вычисления эффективным 0, то с практической точки зрения всегда нужно учитывать точность машины при работе с вычислениями с плавающей запятой или, предпочтительно, найти способ переформулировать вашу проблему так, чтобы она полностью исключала необходимость проверки на равенство.

12
задан Andy Lester 21 October 2008 в 02:10
поделиться

9 ответов

Я настроил бы представление и связанную основанную на таблице функцию на основе CTE. Мое обоснование для этого состоит в том, что, в то время как Вы могли реализовать логику на стороне приложения, это включит отправку промежуточных данных по проводу для вычисления в приложении. Используя разработчика DBML, представление переводит в объект Таблицы. Можно затем связать функцию с объектом Таблицы и вызвать метод, созданный на DataContext для получения объектов типа, определенного представлением. Используя основанную на таблице функцию позволяет механизму запроса принимать Ваши параметры во внимание при построении набора результатов вместо того, чтобы применить условие на набор результатов, определенный представлением после факта.

CREATE TABLE [dbo].[hierarchical_table](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [parent_id] [int] NULL,
    [data] [varchar](255) NOT NULL,
 CONSTRAINT [PK_hierarchical_table] PRIMARY KEY CLUSTERED 
(
    [id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

CREATE VIEW [dbo].[vw_recursive_view]
AS
WITH hierarchy_cte(id, parent_id, data, lvl) AS
(SELECT     id, parent_id, data, 0 AS lvl
      FROM         dbo.hierarchical_table
      WHERE     (parent_id IS NULL)
      UNION ALL
      SELECT     t1.id, t1.parent_id, t1.data, h.lvl + 1 AS lvl
      FROM         dbo.hierarchical_table AS t1 INNER JOIN
                            hierarchy_cte AS h ON t1.parent_id = h.id)
SELECT     id, parent_id, data, lvl
FROM         hierarchy_cte AS result


CREATE FUNCTION [dbo].[fn_tree_for_parent] 
(
    @parent int
)
RETURNS 
@result TABLE 
(
    id int not null,
    parent_id int,
    data varchar(255) not null,
    lvl int not null
)
AS
BEGIN
    WITH hierarchy_cte(id, parent_id, data, lvl) AS
   (SELECT     id, parent_id, data, 0 AS lvl
        FROM         dbo.hierarchical_table
        WHERE     (id = @parent OR (parent_id IS NULL AND @parent IS NULL))
        UNION ALL
        SELECT     t1.id, t1.parent_id, t1.data, h.lvl + 1 AS lvl
        FROM         dbo.hierarchical_table AS t1 INNER JOIN
            hierarchy_cte AS h ON t1.parent_id = h.id)
    INSERT INTO @result
    SELECT     id, parent_id, data, lvl
    FROM         hierarchy_cte AS result
RETURN 
END

ALTER TABLE [dbo].[hierarchical_table]  WITH CHECK ADD  CONSTRAINT [FK_hierarchical_table_hierarchical_table] FOREIGN KEY([parent_id])
REFERENCES [dbo].[hierarchical_table] ([id])

ALTER TABLE [dbo].[hierarchical_table] CHECK CONSTRAINT [FK_hierarchical_table_hierarchical_table]

Для использования его, Вы сделали бы что-то как - принимающий некоторую разумную схему именования:

using (DataContext dc = new HierarchicalDataContext())
{
    HierarchicalTableEntity h = (from e in dc.HierarchicalTableEntities
                                 select e).First();
    var query = dc.FnTreeForParent( h.ID );
    foreach (HierarchicalTableViewEntity entity in query) {
        ...process the tree node...
    }
}
6
ответ дан 2 December 2019 в 03:56
поделиться

Я получил этот подход из блога Rob Conery (проверка вокруг Pt. 6 для этого кода, также на codeplex), и я люблю использовать его. Это могло быть повторно сформировано для поддержки нескольких "sub" уровней.

var categories = from c in db.Categories
                 select new Category
                 {
                     CategoryID = c.CategoryID,
                     ParentCategoryID = c.ParentCategoryID,
                     SubCategories = new List<Category>(
                                      from sc in db.Categories
                                      where sc.ParentCategoryID == c.CategoryID
                                      select new Category {
                                        CategoryID = sc.CategoryID, 
                                        ParentProductID = sc.ParentProductID
                                        }
                                      )
                             };
1
ответ дан 2 December 2019 в 03:56
поделиться

Этот дополнительный метод мог потенциально быть изменен для использования IQueryable. Я использовал его успешно в прошлом на наборе объектов. Это может работать на Ваш сценарий.

public static IEnumerable<T> ByHierarchy<T>(
 this IEnumerable<T> source, Func<T, bool> startWith, Func<T, T, bool> connectBy)
{
  if (source == null)
   throw new ArgumentNullException("source");

  if (startWith == null)
   throw new ArgumentNullException("startWith");

  if (connectBy == null)
   throw new ArgumentNullException("connectBy");

  foreach (T root in source.Where(startWith))
  {
   yield return root;
   foreach (T child in source.ByHierarchy(c => connectBy(root, c), connectBy))
   {
    yield return child;
   }
 }
}

Вот то, как я назвал его:

comments.ByHierarchy(comment => comment.ParentNum == parentNum, 
 (parent, child) => child.ParentNum == parent.CommentNum && includeChildren)

Этот код является улучшенной, исправленной версией ошибки кода, найденного здесь.

3
ответ дан 2 December 2019 в 03:56
поделиться

Проблема выбрать данные из стороны клиента состоит в том, что Вы никогда не можете быть уверены, как глубоко необходимо пойти. Этот метод сделает одно распространение в прямом и обратном направлениях на глубину, и это мог быть union'd, чтобы сделать от 0 до указанной глубины в одном распространении в прямом и обратном направлениях.

public IQueryable<Node> GetChildrenAtDepth(int NodeID, int depth)
{
  IQueryable<Node> query = db.Nodes.Where(n => n.NodeID == NodeID);
  for(int i = 0; i < depth; i++)
    query = query.SelectMany(n => n.Children);
       //use this if the Children association has not been defined
    //query = query.SelectMany(n => db.Nodes.Where(c => c.ParentID == n.NodeID));
  return query;
}

Это не может, однако, сделать произвольной глубины. При реальном требовании произвольной глубины необходимо сделать это в базе данных - таким образом, можно принять корректное решение остановиться.

0
ответ дан 2 December 2019 в 03:56
поделиться

Я сделал это два пути:

  1. Управляйте извлечением каждого слоя дерева на основе ввода данных пользователем. Вообразите управление структурным видом заполненным с корневым узлом, детьми корня и внуками корня. Только корень и дети расширены (внуки скрыты с коллапсом). Поскольку пользователь разворачивает дочерний узел, внуки корня являются дисплеем (которые были ранее получены и скрыты), и извлечение всех правнуков запускается. Повторите шаблон для Слоев n-типа глубоко. Этот шаблон работает очень хорошо на большие деревья (глубина или ширина), потому что это только получает часть необходимого дерева.
  2. Используйте хранимую процедуру с LINQ. Используйте что-то как общее выражение таблицы на сервере, чтобы создать Ваши результаты в плоской таблице или создать дерево XML в T-SQL. У Scott Guthrie есть большая статья об использовании сохраненного procs в LINQ. Создайте свое дерево из результатов, когда они возвращаются, если в плоском формате, или используют дерево XML, если это - то, именно это Вы возвращаете.
3
ответ дан 2 December 2019 в 03:56
поделиться

В MS SQL 2008 Вы могли использовать HierarchyID непосредственно, в sql2005 Вам, вероятно, придется реализовать их вручную. ParentID не то, что производителен на больших наборах данных. Также проверьте эту статью на большее количество обсуждения темы.

2
ответ дан 2 December 2019 в 03:56
поделиться

This option might also prove useful:

LINQ AsHierarchy() extension method
http://www.scip.be/index.php?Page=ArticlesNET18

15
ответ дан 2 December 2019 в 03:56
поделиться

Я удивлен, что никто не упомянул альтернативный дизайн базы данных - когда иерархию нужно выровнять с нескольких уровней и извлекать с высокой производительностью (не так, учитывая пространство для хранения) Лучше использовать другую таблицу entity-2-entity для отслеживания иерархии вместо подхода parent_id.

Это позволит не только отношения с одним родителем, но и отношения с несколькими родителями, указание уровня и различные типы отношений:

CREATE TABLE Person (
  Id INTEGER,
  Name TEXT
);

CREATE TABLE PersonInPerson (
  PersonId INTEGER NOT NULL,
  InPersonId INTEGER NOT NULL,
  Level INTEGER,
  RelationKind VARCHAR(1)
);
8
ответ дан 2 December 2019 в 03:56
поделиться

Прочтите следующую ссылку.

http://support.microsoft.com/default.aspx?scid=kb;en-us;q248915

0
ответ дан 2 December 2019 в 03:56
поделиться
Другие вопросы по тегам:

Похожие вопросы: