#25: adapt XmlDataWriter to one file per entry strategy

pull/27/head
Simon G 5 years ago
parent f0783df9c2
commit 84d98a5dee
  1. 17
      GBase/DataHandling/Exceptions/InvalidXmlFileException.cs
  2. 2
      GBase/DataHandling/XmlDataHandler.cs
  3. 62
      GBase/DataHandling/XmlDataWriter.cs
  4. 4
      GBase/Interfaces/DataHandling/IDataWriter.cs
  5. 6
      Test.GBase/DataHandling/XmlDataHandlerTest.cs

@ -0,0 +1,17 @@
// Author: Gockner, Simon
// Created: 2020-11-09
// Copyright(c) 2020 SimonG. All Rights Reserved.
using System;
namespace GBase.DataHandling.Exceptions
{
public class InvalidXmlFileException : Exception
{
public InvalidXmlFileException(string message)
: base(message)
{
}
}
}

@ -144,7 +144,7 @@ namespace GBase.DataHandling
valueString = value.ToString(); valueString = value.ToString();
await _cache.TryRemoveValue<T, TProperty>(propertyName, value); await _cache.TryRemoveValue<T, TProperty>(propertyName, value);
await _xmlDataWriter.Remove<T, TProperty>(propertyName, valueString); await _xmlDataWriter.Remove<T, TProperty>(TODO, propertyName, valueString, TODO);
} }
/// <summary> /// <summary>

@ -8,6 +8,7 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml.Linq; using System.Xml.Linq;
using GBase.DataHandling.Exceptions;
using GBase.Interfaces.DataHandling; using GBase.Interfaces.DataHandling;
using GBase.Interfaces.DataHandling.Xml; using GBase.Interfaces.DataHandling.Xml;
@ -52,6 +53,7 @@ namespace GBase.DataHandling
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
/// <returns>A <see cref="Task"/> to await</returns> /// <returns>A <see cref="Task"/> to await</returns>
/// <exception cref="ArgumentNullException"><paramref name="propertyName"/></exception> /// <exception cref="ArgumentNullException"><paramref name="propertyName"/></exception>
/// <exception cref="InvalidXmlFileException">No root element is set</exception>
public async Task Write<T, TProperty>(FileStream file, string propertyName, string value, bool overwrite, CancellationToken cancellationToken) public async Task Write<T, TProperty>(FileStream file, string propertyName, string value, bool overwrite, CancellationToken cancellationToken)
{ {
string typeName = typeof(T).FullName; string typeName = typeof(T).FullName;
@ -59,43 +61,35 @@ namespace GBase.DataHandling
throw new ArgumentNullException(nameof(typeName)); throw new ArgumentNullException(nameof(typeName));
XDocument xmlDocument = await XDocument.LoadAsync(file, LoadOptions.None, cancellationToken); XDocument xmlDocument = await XDocument.LoadAsync(file, LoadOptions.None, cancellationToken);
file.Seek(0, SeekOrigin.Begin); //reset stream to it's beginning to be able to save it file.Seek(0, SeekOrigin.Begin); //reset stream to it's beginning to be able to save it
XElement typeElement = _rootElement.Element(typeName); XElement rootElement = xmlDocument.Root;
if (typeElement != null) //type element already exists if (rootElement == null)
throw new InvalidXmlFileException("No root element is set.");
XElement propertyElement = rootElement.Element(propertyName);
if (propertyElement != null) //property element already exists
{ {
XElement propertyElement = typeElement.Element(propertyName); XElement valueElement = propertyElement.Element(XmlDataHandler.VALUE_ELEMENT_NAME);
if (propertyElement != null) //property element already exists if (valueElement != null && overwrite) //value element exists and overwrite is true
{ {
XElement valueElement = propertyElement.Element(XmlDataHandler.VALUE_ELEMENT_NAME); valueElement.Value = value; //overwrite existing value
if (valueElement != null && overwrite) //value element exists and overwrite is true
{
valueElement.Value = value; //overwrite existing value
}
else if (!overwrite && propertyElement.Elements(XmlDataHandler.VALUE_ELEMENT_NAME).Any(v => v.Value.Equals(value))) //no overwrite and same value exists already
{
XElement sameValueElement = propertyElement.Elements(XmlDataHandler.VALUE_ELEMENT_NAME).First(v => v.Value.Equals(value));
sameValueElement.Remove(); //remove the already existing value from its current position
propertyElement.AddFirst(sameValueElement); //add it as the first element again
}
else //no value element exists or overwrite is false
propertyElement.AddFirst(new XElement(XmlDataHandler.VALUE_ELEMENT_NAME) { Value = value }); //add a new value element
} }
else //property element doesn't exist else if (!overwrite && propertyElement.Elements(XmlDataHandler.VALUE_ELEMENT_NAME).Any(v => v.Value.Equals(value))) //no overwrite and same value exists already
{ {
propertyElement = new XElement(propertyName, new XElement(XmlDataHandler.VALUE_ELEMENT_NAME) { Value = value }); //create the new property element with the value element XElement sameValueElement = propertyElement.Elements(XmlDataHandler.VALUE_ELEMENT_NAME).First(v => v.Value.Equals(value));
propertyElement.SetAttributeValue(XmlDataHandler.VALUE_TYPE_ATTRIBUTE_NAME, typeof(TProperty).FullName); //add the property type attribute sameValueElement.Remove(); //remove the already existing value from its current position
propertyElement.AddFirst(sameValueElement); //add it as the first element again
typeElement.Add(propertyElement); //create new property element with the value element
} }
else //no value element exists or overwrite is false
propertyElement.AddFirst(new XElement(XmlDataHandler.VALUE_ELEMENT_NAME) { Value = value }); //add a new value element
} }
else //type element doesn't exist else //property element doesn't exist
{ {
XElement propertyElement = new XElement(propertyName, new XElement(XmlDataHandler.VALUE_ELEMENT_NAME) { Value = value }); //create the new property element with the value element propertyElement = new XElement(propertyName, new XElement(XmlDataHandler.VALUE_ELEMENT_NAME) { Value = value }); //create the new property element with the value element
propertyElement.SetAttributeValue(XmlDataHandler.VALUE_TYPE_ATTRIBUTE_NAME, typeof(TProperty).FullName); //add the property type attribute propertyElement.SetAttributeValue(XmlDataHandler.VALUE_TYPE_ATTRIBUTE_NAME, typeof(TProperty).FullName); //add the property type attribute
_rootElement.Add(new XElement(typeName, propertyElement)); //create a new type element with the new property element rootElement.Add(propertyElement); //create new property element with the value element
} }
//TODO: check if whole file is overwritten (probably) -> performance issues for large files? //TODO: check if whole file is overwritten (probably) -> performance issues for large files?
@ -107,19 +101,27 @@ namespace GBase.DataHandling
/// </summary> /// </summary>
/// <typeparam name="T">The <see cref="Type"/> of the property</typeparam> /// <typeparam name="T">The <see cref="Type"/> of the property</typeparam>
/// <typeparam name="TProperty">The <see cref="Type"/> of the property</typeparam> /// <typeparam name="TProperty">The <see cref="Type"/> of the property</typeparam>
/// <param name="file"></param>
/// <param name="propertyName">The name of the property</param> /// <param name="propertyName">The name of the property</param>
/// <param name="value">The value to set</param> /// <param name="value">The value to set</param>
/// <param name="cancellationToken"></param>
/// <returns>A <see cref="Task"/> to await</returns> /// <returns>A <see cref="Task"/> to await</returns>
/// <exception cref="ArgumentNullException"><paramref name="propertyName"/></exception> /// <exception cref="ArgumentNullException"><paramref name="propertyName"/></exception>
public async Task Remove<T, TProperty>(string propertyName, string value) /// <exception cref="InvalidXmlFileException">No root element is set</exception>
public async Task Remove<T, TProperty>(FileStream file, string propertyName, string value, CancellationToken cancellationToken)
{ {
string typeName = typeof(T).FullName; string typeName = typeof(T).FullName;
if (typeName == null) if (typeName == null)
throw new ArgumentNullException(nameof(typeName)); throw new ArgumentNullException(nameof(typeName));
_file.Seek(0, SeekOrigin.Begin); //reset stream to it's beginning to be able to save it XDocument xmlDocument = await XDocument.LoadAsync(file, LoadOptions.None, cancellationToken);
file.Seek(0, SeekOrigin.Begin); //reset stream to it's beginning to be able to save it
XElement typeElement = _rootElement.Element(typeName); XElement rootElement = xmlDocument.Root;
if (rootElement == null)
throw new InvalidXmlFileException("No root element is set.");
XElement typeElement = rootElement.Element(typeName);
XElement propertyElement = typeElement?.Element(propertyName); XElement propertyElement = typeElement?.Element(propertyName);
XElement valueElement = propertyElement?.Elements(XmlDataHandler.VALUE_ELEMENT_NAME).FirstOrDefault(e => e.Value.Equals(value)); XElement valueElement = propertyElement?.Elements(XmlDataHandler.VALUE_ELEMENT_NAME).FirstOrDefault(e => e.Value.Equals(value));
if (valueElement == null) if (valueElement == null)
@ -128,7 +130,7 @@ namespace GBase.DataHandling
valueElement.Remove(); valueElement.Remove();
//TODO: check if whole file is overwritten (probably) -> performance issues for large files? //TODO: check if whole file is overwritten (probably) -> performance issues for large files?
await _xmlDocument.SaveAsync(_file, SaveOptions.OmitDuplicateNamespaces, _cancellationToken); //save the document with the added elements await xmlDocument.SaveAsync(file, SaveOptions.OmitDuplicateNamespaces, cancellationToken); //save the document with the added elements
} }
} }
} }

@ -41,9 +41,11 @@ namespace GBase.Interfaces.DataHandling
/// </summary> /// </summary>
/// <typeparam name="T">The <see cref="Type"/> of the property</typeparam> /// <typeparam name="T">The <see cref="Type"/> of the property</typeparam>
/// <typeparam name="TProperty">The <see cref="Type"/> of the property</typeparam> /// <typeparam name="TProperty">The <see cref="Type"/> of the property</typeparam>
/// <param name="file"></param>
/// <param name="propertyName">The name of the property</param> /// <param name="propertyName">The name of the property</param>
/// <param name="value">The value to set</param> /// <param name="value">The value to set</param>
/// <param name="cancellationToken"></param>
/// <returns>A <see cref="Task"/> to await</returns> /// <returns>A <see cref="Task"/> to await</returns>
Task Remove<T, TProperty>(string propertyName, string value); Task Remove<T, TProperty>(FileStream file, string propertyName, string value, CancellationToken cancellationToken);
} }
} }

@ -213,7 +213,7 @@ namespace Test.GBase.DataHandling
await xmlDataHandler.RemoveValue<XmlDataHandlerTest, string>("property", "SomeString"); await xmlDataHandler.RemoveValue<XmlDataHandlerTest, string>("property", "SomeString");
xmlDataHandlerCacheMock.Verify(c => c.TryRemoveValue<XmlDataHandlerTest, string>("property", "SomeString"), Times.Once); xmlDataHandlerCacheMock.Verify(c => c.TryRemoveValue<XmlDataHandlerTest, string>("property", "SomeString"), Times.Once);
xmlDataWriterMock.Verify(w => w.Remove<XmlDataHandlerTest, string>("property", "SomeString"), Times.Once); xmlDataWriterMock.Verify(w => w.Remove<XmlDataHandlerTest, string>(TODO, "property", "SomeString", TODO), Times.Once);
} }
[Test] [Test]
@ -238,7 +238,7 @@ namespace Test.GBase.DataHandling
await xmlDataHandler.RemoveValue<XmlDataHandlerTest, List<string>>("property", stringList); await xmlDataHandler.RemoveValue<XmlDataHandlerTest, List<string>>("property", stringList);
xmlDataHandlerCacheMock.Verify(c => c.TryRemoveValue<XmlDataHandlerTest, List<string>>("property", stringList), Times.Once); xmlDataHandlerCacheMock.Verify(c => c.TryRemoveValue<XmlDataHandlerTest, List<string>>("property", stringList), Times.Once);
xmlDataWriterMock.Verify(w => w.Remove<XmlDataHandlerTest, List<string>>("property", $"{stringList[0]},{stringList[1]},{stringList[2]}"), Times.Once); xmlDataWriterMock.Verify(w => w.Remove<XmlDataHandlerTest, List<string>>(TODO, "property", $"{stringList[0]},{stringList[1]},{stringList[2]}", TODO), Times.Once);
} }
[Test] [Test]
@ -262,7 +262,7 @@ namespace Test.GBase.DataHandling
await xmlDataHandler.RemoveValue<XmlDataHandlerTest, string>("property", null); await xmlDataHandler.RemoveValue<XmlDataHandlerTest, string>("property", null);
xmlDataHandlerCacheMock.Verify(c => c.TryRemoveValue<It.IsValueType, It.IsValueType>(It.IsAny<string>(), It.IsAny<It.IsValueType>()), Times.Never); xmlDataHandlerCacheMock.Verify(c => c.TryRemoveValue<It.IsValueType, It.IsValueType>(It.IsAny<string>(), It.IsAny<It.IsValueType>()), Times.Never);
xmlDataWriterMock.Verify(w => w.Remove<It.IsValueType, It.IsValueType>(It.IsAny<string>(), It.IsAny<string>()), Times.Never); xmlDataWriterMock.Verify(w => w.Remove<It.IsValueType, It.IsValueType>(TODO, It.IsAny<string>(), It.IsAny<string>(), TODO), Times.Never);
} }
[Test] [Test]

Loading…
Cancel
Save