Связанный список рамках реализации объекта основных


Я пытаюсь создать модель данных, где есть Document лиц, которые должны быть в определенном порядке друг к другу. Вспомните статью, в которой есть разделы, которые строят друг на друга.

Пример:

Document1 -> Document3 -> Document2 -> Document6 -> Document5 -> Document4

Я пытался реализовать linked list для достижения этой цели. К сожалению EF Core 2.0 не поддерживает рекурсивные запросы. Поэтому я нуждался в родительской сущности, имени DocumentCollection. Требование состоит в том, чтобы быть в состоянии получить Documents в предопределенном порядке.

Пожалуйста, предложить улучшения или альтернативные подходы.


Определения сущностей

DocumentCollection модель:

public class DocumentCollection
{
    public int Id { get; set; }
    public ICollection<Document> Documents { get; set; }

    public DocumentCollection()
    {
        Documents = new HashSet<Document>();
    }
}

Document модель:

public class Document
{
    public int Id { get; set; }
    public string Name { get; set; }

    #region Navigation Properties

    public int CollectionId { get; set; }
    public DocumentCollection Collection { get; set; }

    public int? PredecessorId { get; set; }
    public Document Predecessor { get; set; }

    public int? SuccessorId { get; set; }
    public Document Successor { get; set; }

    #endregion
}

DbContext отношения сопоставления:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Product>().HasKey(product => product.Id);
    modelBuilder.Entity<Product>().Property(product => product.Name).HasMaxLength(70);

    modelBuilder.Entity<DocumentCollection>()
        .HasKey(collection => collection.Id);

    modelBuilder.Entity<Document>().HasKey(document => document.Id);
    modelBuilder.Entity<Document>().Property(document => document.Name).HasMaxLength(70);

    modelBuilder.Entity<DocumentCollection>()
        .HasMany(collection => collection.Documents)
        .WithOne(document => document.Collection)
        .HasPrincipalKey(collection => collection.Id)
        .HasForeignKey(document => document.CollectionId)
        .OnDelete(DeleteBehavior.Cascade);

    modelBuilder.Entity<Document>()
        .HasOne(document => document.Predecessor)
        .WithOne(document => document.Successor)
        .HasPrincipalKey<Document>(principal => principal.Id)
        .HasForeignKey<Document>(dependent => dependent.PredecessorId);

    modelBuilder.Entity<Document>()
        .HasOne(document => document.Successor)
        .WithOne(document => document.Predecessor)
        .HasPrincipalKey<Document>(principal => principal.Id)
        .HasForeignKey<Document>(dependent => dependent.SuccessorId);

    base.OnModelCreating(modelBuilder);
}

Использовать сервис для управления сущностями:

public class DocumentService
{
    private readonly DataContext _context;
    public DbSet<Document> Entities => _context.Documents;

    public DocumentService(DataContext context)
    {
        _context = context;
    }

    [...]
}

Создание исходной Document:

public async Task<Document> AddDocument(Document document)
{
    document.Collection = new DocumentCollection();
    var result = await Entities.AddAsync(document);
    await _context.SaveChangesAsync();
    return result.Entity;
}

Добавление Document к определенной позиции:

public async Task<Document> AddDocumentSuccessor(int parentId, Document document)
{
    var predecessor = await Entities
        .Include(entity => entity.Successor)
        .SingleOrDefaultAsync(entity => entity.Id == parentId);

    if (predecessor == null)
    {
        return null;
    }

    document.CollectionId = predecessor.CollectionId;
    var result = await Entities.AddAsync(document);
    await _context.SaveChangesAsync();

    var newEntity = result.Entity;

    newEntity.PredecessorId = predecessor.Id;
    if (predecessor.Successor != null)
    {
        newEntity.SuccessorId = predecessor.SuccessorId;
    }

    predecessor.SuccessorId = newEntity.Id;
    if (predecessor.Successor != null)
    {
        predecessor.Successor.PredecessorId = newEntity.Id;
    }

    await _context.SaveChangesAsync();
    return result.Entity;
}

Получаем упорядоченный список соответствующих Document лиц:

public async Task<IEnumerable<Document>> GetRelatedDocuments(int id)
{
    var collectionId = await Entities
        .Where(entity => entity.Id == id)
        .Select(entity => entity.CollectionId)
        .SingleOrDefaultAsync();

    if (collectionId == 0)
    {
        return null;
    }

    var result = await Entities
        .Where(entity => entity.CollectionId == collectionId)
        .Select(entity => new Document
        {
            Id = entity.Id,
            Name = entity.Name,
            CollectionId = entity.CollectionId,
            PredecessorId = entity.PredecessorId,
            SuccessorId = entity.SuccessorId
        })
        .ToArrayAsync();

    return OrderByHierarchy(result);
}

Используемый метод сортировки:

private static IEnumerable<Document> OrderByHierarchy(
    IReadOnlyCollection<Document> documents)
{
    if (documents.Count == 0)
    {
        yield break;
    }

    var dict = documents.ToDictionary
    (
        entity => entity.PredecessorId ?? 0, 
        entity => entity
    );

    var key = 0;
    Document document;

    do
    {
        document = dict[key];
        yield return document;
        key = document.Id;
    }
    while (document.SuccessorId != null);
}


Комментарии
1 ответ

Я считаю, вы должны придерживаться того же правила именования оригинала LinkedListNode. Это означает, что вы должны иметь Preview и Next свойства и не Predecessor и Successor respecitively.

Я бы потом реализовать эти две в другой класс, например LinkedEntityNode:

public class LinkedEntityNode
{
public int Id { get; set; }
public int? NextId { get; set; }
public int? PreviousId { get; set; }
}

и поместить его в отдельную таблицу.

Тогда давайте ссылку на документ узел:

public class Document
{
public int Id { get; set; }

public string Name { get; set; }

public int? NodeId { get; set; }

#region Navigation Properties

public LinkedEntityNode Node { get; set; }

#endregion
}

Для этого потребуется несколько корректировок в модели и в сортировке и поиске логике, но таким образом можно раз и навсегда внедрить и использовать ее в любой другой проект, чтобы связать все виды материала таким образом. Не просто Document лица.

Я тоже думаю, что вам не нужно, типа коллекции, потому что, как только вы знаете один документ (узел), вы можете восстановить его оттуда.

1
ответ дан 13 апреля 2018 в 06:04 Источник Поделиться