Реализация XML-репозитория - PullRequest
8 голосов
/ 27 января 2011

Я ищу простой пример XML-репозитория (GetAll, Add, Update, Delete).

Все говорят: «Это хорошая идея - использовать шаблон репозитория, потому что вы можете поменять месторасположение хранилища данных ...». Теперь мне нужно сохранить свои данные в файле XML и не знаю, как реализовать репозиторий XML. Я искал по всему Google и не могу найти его.

Если возможно, отправьте пример, содержащий дескриптор реляционных данных. Например, когда вы сохраняете сущность продукта в EF, а все зависимые от продукта сущности также сохраняются.

Ответы [ 2 ]

10 голосов
/ 11 марта 2011

Что ж, решение Petter - это хорошо.

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

public interface IRepository<T>
{
    IEnumerable<T> GetAll();
    IEnumerable<T> GetAll(object parentId);
    T GetByKey(object keyValue);

    void Insert(T entidade, bool autoPersist = true);
    void Update(T entidade, bool autoPersist = true);
    void Delete(T entidade, bool autoPersist = true);

    void Save();
}

И базовый класс для репозиториев XML

public abstract class XmlRepositoryBase<T> : IRepository<T>
{

    public virtual XElement ParentElement { get; protected set; }

    protected XName ElementName { get; private set; }

    protected abstract Func<XElement, T> Selector { get; }

    #endregion

    protected XmlRepositoryBase(XName elementName)
    {
        ElementName = elementName;

        // clears the "cached" ParentElement to allow hot file changes
        XDocumentProvider.Default.CurrentDocumentChanged += (sender, eventArgs) => ParentElement = null;
    }

    #region

    protected abstract void SetXElementValue(T model, XElement element);

    protected abstract XElement CreateXElement(T model);

    protected abstract object GetEntityId(T entidade);

    #region IRepository<T>

    public T GetByKey(object keyValue)
    {
        // I intend to remove this magic string "Id"
        return XDocumentProvider.Default.GetDocument().Descendants(ElementName)
            .Where(e => e.Attribute("Id").Value == keyValue.ToString())
            .Select(Selector)
            .FirstOrDefault();
    }

    public IEnumerable<T> GetAll()
    {
        return ParentElement.Elements(ElementName).Select(Selector);
    }

    public virtual IEnumerable<T> GetAll(object parentId)
    {
        throw new InvalidOperationException("This entity doesn't contains a parent.");
    }

    public virtual void Insert(T entity, bool autoPersist = true)
    {
        ParentElement.Add(CreateXElement(entity));

        if (autoPersist)
            Save();
    }

    public virtual void Update(T entity, bool autoPersist= true)
    {
        // I intend to remove this magic string "Id"
        SetXElementValue(
            entity,
            ParentElement.Elements().FirstOrDefault(a => a.Attribute("Id").Value == GetEntityId(entity).ToString()
        ));

        if (persistir)
            Save();
    }

    public virtual void Delete(T entity, bool autoPersist = true)
    {
        ParentElement.Elements().FirstOrDefault(a => a.Attribute("Id").Value == GetEntityId(entity).ToString()).Remove();

        if (autoPersist)
            Save();
    }


    public virtual void Save()
    {
        XDocumentProvider.Default.Save();
    }
    #endregion

    #endregion
}

И еще 2 абстрактных класса, один для независимых сущностей, а другой для дочерних сущностей.Чтобы не читать файл Xml каждый раз, я сделал своего рода элемент управления кэшем

public abstract class EntityXmlRepository<T> : XmlRepositoryBase<T>
{
    #region cache control

    private XElement _parentElement;
    private XName xName;

    protected EntityXmlRepository(XName entityName)
        : base(entityName)
    {
    }

    public override XElement ParentElement
    {
        get
        {
            // returns in memory element or get it from file
            return _parentElement ?? ( _parentElement = GetParentElement() );
        }
        protected set
        {
            _parentElement = value;
        }
    }

    /// <summary>
    /// Gets the parent element for this node type
    /// </summary>
    protected abstract XElement GetParentElement();
    #endregion
}

Теперь реализация для дочерних типов

public abstract class ChildEntityXmlRepository<T> : XmlRepositoryBase<T>
{
    private object _currentParentId;
    private object _lastParentId;

    private XElement _parentElement;

    public override XElement ParentElement
    {
        get 
        {
            if (_parentElement == null)
            {
                _parentElement = GetParentElement(_currentParentId);
                _lastParentId = _currentParentId;
            }
            return _parentElement;
        }
        protected set 
        {
            _parentElement = value; 
        }
    }

    /// <summary>
    /// Defines wich parent entity is active
    /// when this property changes, the parent element field is nuled, forcing the parent element to be updated
    /// </summary>
    protected object CurrentParentId
    {
        get
        {
            return _currentParentId;
        }
        set
        {
            _currentParentId = value;
            if (value != _lastParentId)
            {
                _parentElement = null;
            }
        }
    }       



    protected ChildEntityXmlRepository(XName entityName) : base(entityName){}

    protected abstract XElement GetParentElement(object parentId);

    protected abstract object GetParentId(T entity);


    public override IEnumerable<T> GetAll(object parentId)
    {
        CurrentParentId = parentId;
        return ParentElement.Elements(ElementName).Select(Selector);
    }

    public override void Insert(T entity, bool persistir = true)
    {
        CurrentParentId = GetParentId(entity);
        base.Insert(entity, persistir);
    }

    public override void Update(T entity, bool persistir = true)
    {
        CurrentParentId = GetParentId(entity);
        base.Update(entity, persistir);
    }

    public override void Delete(T entity, bool persistir = true)
    {
        CurrentParentId = GetParentId(entity);
        base.Delete(entity, persistir);
    }
}

Теперь, реальный мирреализация

public class RepositorioAgendamento : EntityXmlRepository<Agendamento>, IRepositorioAgendamento
{
    protected override Func<XElement, Agendamento> Selector
    {
        get
        {
            return x => new Agendamento() {
                Id = x.Attribute("Id").GetGuid(),
                   Descricao = x.Attribute("Descricao").Value,
                   TipoAgendamento = x.Attribute("TipoAgendamento").GetByte(),
                   Dias = x.Attribute("Dias").GetByte(),
                   Data = x.Attribute("Data").GetDateTime(),
                   Ativo = x.Attribute("Ativo").GetBoolean(),
            };
        }
    }

    protected override XElement CreateXElement(Agendamento agendamento)
    {
        agendamento.Id = Guid.NewGuid();

        return new XElement(ElementName,
                new XAttribute("Id", agendamento.Id),
                new XAttribute("Descricao", agendamento.Descricao),
                new XAttribute("TipoAgendamento", agendamento.TipoAgendamento),
                new XAttribute("Dias", agendamento.Dias),
                new XAttribute("Data", agendamento.Data),
                new XAttribute("Ativo", agendamento.Ativo),
                new XElement(XmlNames.GruposBackup),
                new XElement(XmlNames.Credenciais)
                );
    }

    protected override void SetXElementValue(Agendamento modelo, XElement elemento)
    {
        elemento.Attribute("Descricao").SetValue(modelo.Descricao);
        elemento.Attribute("TipoAgendamento").SetValue(modelo.TipoAgendamento);
        elemento.Attribute("Dias").SetValue(modelo.Dias);
        elemento.Attribute("Data").SetValue(modelo.Data);
        elemento.Attribute("Ativo").SetValue(modelo.Ativo);
    }


    public RepositorioAgendamento() : base(XmlNames.Agendamento)
    {

    }

    protected override XElement GetParentElement()
    {
        return XDocumentProvider.Default.GetDocument().Elements(XmlNames.Agendamentos).First();
    }

    protected override object GetEntityId(Agendamento entidade)
    {
        return entidade.Id;
    }

    public IEnumerable<Agendamento> ObterAtivos()
    {
        return ParentElement.Elements()
            .Where(e => e.Attribute("Ativo").GetBoolean())
            .Select(Selector);
    }
}

А теперь XDocumentProvider.Его функция состоит в том, чтобы абстрагировать доступ к XML-файлу и унифицировать для всех репозиториев то, что XDocument является контекстом данных. Это можно назвать UnitOfWork ?

public abstract class XDocumentProvider
{
    // not thread safe yet
    private static bool pendingChanges;

    private bool _documentLoadedFromFile;

    FileSystemWatcher fileWatcher;

    public static XDocumentProvider Default;

    public event EventHandler CurrentDocumentChanged;

    private XDocument _loadedDocument;

    public string FileName { get; set; }


    protected XDocumentProvider()
    {
        fileWatcher = new FileSystemWatcher();
        fileWatcher.NotifyFilter = NotifyFilters.LastWrite;
        fileWatcher.Changed += fileWatcher_Changed;
    }

    void fileWatcher_Changed(object sender, FileSystemEventArgs e)
    {
        if (_documentLoadedFromFile && !pendingChanges)
        {
            GetDocument(true);
        }
    }


    /// <summary>
    /// Returns an open XDocument or create a new document
    /// </summary>
    /// <returns></returns>
    public XDocument GetDocument(bool refresh = false)
    {
        if (refresh || _loadedDocument == null)
        {
            // we need to refactor it, but just to demonstrate how should work I will send this way ;P
            if (File.Exists(FileName))
            {
                _loadedDocument = XDocument.Load(FileName);
                _documentLoadedFromFile = true;

                if (fileWatcher.Path != Environment.CurrentDirectory)
                {
                    fileWatcher.Path = Environment.CurrentDirectory;
                    fileWatcher.Filter = FileName;
                    fileWatcher.EnableRaisingEvents = true;
                }
            }
            else
            {
                _loadedDocument = CreateNewDocument();
                fileWatcher.EnableRaisingEvents = false;
                _documentLoadedFromFile = false;
            }

            if(CurrentDocumentChanged != null) CurrentDocumentChanged(this, EventArgs.Empty);
        }

        return _loadedDocument;
    }

    /// <summary>
    /// Creates a new XDocument with a determined schemma.
    /// </summary>
    public abstract XDocument CreateNewDocument();

    public void Save()
    {
        if (_loadedDocument == null)
            throw new InvalidOperationException();

        try
        {
            // tells the file watcher that he cannot raise the changed event, because his function is to capture external changes.
            pendingChanges = true;
            _loadedDocument.Save(FileName);
        }
        finally
        {
            pendingChanges = false;
        }
    }
}

Тогда у меня может быть много репозиториев для разных сущностей, добавляющих нерешенные постоянные действия в одном контексте данных.

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

В моей конфигурации IoC я должен установить Default для XDocumentProvider.При необходимости мы можем передать конструктор XDocumentProvider вместо этого статического свойства «По умолчанию»

Что вы думаете о моей реализации?

Спасибо

2 голосов
/ 07 марта 2011

Мы с коллегой реализовали именно такой репозиторий XML, и он называется XmlRepository : -).

Он построен внутри с linq to XML, и внешний доступ аналогичен тому, как выиспользуйте linq для nhibernate.Это сделано с помощью linq для объектов, использование в клиентском коде очень просто, легко и быстро понятно из-за простого XML-комментария интерфейса.

В текущем выпуске (сборке) нет встроенной поддержки для подклассов или 1: n отношений, но в текущем исходном коде разработки, который вы также можете найти на сайте выше, встроены оба.

Он не полностью готов к выпуску - в нем могут быть незначительные ошибки, но попробуйте, возьмите исходный код и улучшите его, если хотите.Это открытый исходный код.

Любые комментарии, пожелания, конструктивная критика и исправления для проекта с открытым исходным кодом (только для чтения) сделают мою коллегу (Golo Roden) и меня счастливой и приведут проект в лучшее состояние..

Пример приложения доступен здесь (текст на немецком языке).

...