소스 검색

- Add Object.Dynamic.Dyna dynamic object to work with dynamic properties

- Add DataTransaction to work with simple transactional data operations
- Add TDyna dynamic object that supports DataTransaction
- update dependencies packages (System.IO.Abstractions)
Dalibor Votruba 1 년 전
부모
커밋
f0502b1f3e

+ 131 - 0
qdr.fnd.core.test/Object/DynaTests.cs

@@ -0,0 +1,131 @@
+using qdr.fnd.core.test.Object.Fake;
+using Quadarax.Foundation.Core.Object.Dynamic;
+
+
+namespace qdr.fnd.core.test.Object
+{
+    class DynaTests
+    {
+        private const string PROP_A = "A";
+        private const string PROP_B = "B";
+
+
+        [Test(TestOf = typeof(Dyna))]
+        public void DynaConstructorsCollectionOk()
+        {
+            var props = CreateProperties();
+            var dyna = new Dyna(CreateProperties());
+            Assert.That(dyna.Properties.Length, Is.EqualTo(props.Count()));
+        }
+
+        [Test(TestOf = typeof(Dyna))]
+        public void DynaConstructorsTypeOk()
+        {
+            var dyna = new Dyna(typeof(IDinaFake));
+            Assert.That(dyna.Properties.Length, Is.EqualTo(2));
+        }
+
+        [Test(TestOf = typeof(Dyna))]
+        public void DynaSetGetPropertyStringOk()
+        {
+            var props = CreateProperties();
+            var dyna = new Dyna(CreateProperties());
+            dyna[PROP_A] = PROP_A;
+            Assert.That(dyna.GetValue<string>(PROP_A), Is.EqualTo(PROP_A));
+            Assert.That(dyna.GetValue<int?>(PROP_B), Is.EqualTo(null));
+        }
+
+        [Test(TestOf = typeof(Dyna))]
+        public void DynaSetGetPropertyStringFail()
+        {
+            var props = CreateProperties();
+            var dyna = new Dyna(CreateProperties());
+            // ReSharper disable once ObjectCreationAsStatement
+            Assert.Throws<ArgumentOutOfRangeException>(() => dyna[PROP_A + PROP_B] = PROP_A);
+        }
+
+        [Test(TestOf = typeof(Dyna))]
+        public void DynaClearOk()
+        {
+            var props = CreateProperties();
+            var dyna = new Dyna(CreateProperties());
+            dyna[PROP_A] = PROP_A;
+            Assert.That(dyna.GetValue<string>(PROP_A), Is.EqualTo(PROP_A));
+            dyna.Clear();
+            Assert.That(dyna.GetValue<int?>(PROP_A), Is.EqualTo(null));
+        }
+
+        [Test(TestOf = typeof(Dyna))]
+        public void DynaTrackingOk()
+        {
+            var props = CreateProperties();
+            var dyna = new Dyna(CreateProperties());
+            dyna[PROP_A] = PROP_A;
+
+            Assert.That(dyna.ChangedProperties.Length, Is.EqualTo(1));
+            Assert.That(dyna.ChangedProperties.First(), Is.EqualTo(PROP_A));
+
+            dyna[PROP_A] = null;
+            Assert.That(dyna.ChangedProperties.Length, Is.EqualTo(0));
+
+            dyna[PROP_A] = PROP_A;
+            dyna[PROP_A] = PROP_B;
+            Assert.That(dyna.ChangedProperties.Length, Is.EqualTo(1));
+            Assert.That(dyna.ChangedProperties.First(), Is.EqualTo(PROP_A));
+
+            dyna[PROP_A] = PROP_A;
+            dyna.TrackChanges();
+            Assert.That(dyna.ChangedProperties.Length, Is.EqualTo(0));
+            dyna[PROP_A] = PROP_B;
+            dyna[PROP_A] = PROP_A;
+
+            Assert.That(dyna.ChangedProperties.Length, Is.EqualTo(0));
+            dyna.Clear();
+
+            Assert.That(dyna.ChangedProperties.Length, Is.EqualTo(0));
+        }
+
+        [Test(TestOf = typeof(Dyna))]
+        public void DynaCloneOk()
+        {
+            var props = CreateProperties();
+            var dynaSrc = new Dyna(CreateProperties());
+            dynaSrc[PROP_A] = PROP_A;
+            dynaSrc[PROP_B] = PROP_B;
+
+            var dynaClone = dynaSrc.Clone() as Dyna;
+            Assert.That(dynaClone.ChangedProperties.Length, Is.EqualTo(0));
+            Assert.That(dynaClone[PROP_A], Is.EqualTo(PROP_A));
+            Assert.That(dynaClone[PROP_B], Is.EqualTo(PROP_B));
+            Assert.That(dynaClone.IsEquals(dynaSrc), Is.True);
+        }
+
+        [Test(TestOf = typeof(Dyna))]
+        public void DynaCopyToOk()
+        {
+            var props = CreateProperties();
+            var dynaSrc = new Dyna(CreateProperties());
+            dynaSrc[PROP_A] = PROP_A;
+            dynaSrc[PROP_B] = PROP_B;
+
+            var dynaClone = new Dyna(CreateProperties());
+            dynaClone[PROP_A] = PROP_B;
+            dynaClone[PROP_B] = PROP_A;
+
+            dynaClone.CopyTo(dynaSrc);
+
+            Assert.That(dynaClone[PROP_A], Is.EqualTo(PROP_B));
+            Assert.That(dynaClone[PROP_B], Is.EqualTo(PROP_A));
+            Assert.That(dynaClone.IsSame(dynaSrc), Is.True);
+        }
+
+
+        private IEnumerable<Dyna.DynaProperty> CreateProperties()
+        {
+            var proCol = new List<Dyna.DynaProperty>();
+            proCol.Add(new Dyna.DynaProperty(PROP_A, typeof(string), DynaPropertyAccessability.Public, DynaPropertyAccessability.Public));
+            proCol.Add(new Dyna.DynaProperty(PROP_B, typeof(int), DynaPropertyAccessability.Public, DynaPropertyAccessability.Public));
+            return proCol;
+        }
+    }
+}

+ 9 - 0
qdr.fnd.core.test/Object/Fake/IDinaFake.cs

@@ -0,0 +1,9 @@
+
+namespace qdr.fnd.core.test.Object.Fake
+{
+    interface IDinaFake
+    {
+        string A { get; set; }
+        int B { get; set; }
+    }
+}

+ 93 - 0
qdr.fnd.core/Data/Transaction/DataTransaction.cs

@@ -0,0 +1,93 @@
+using System.Text;
+
+namespace Quadarax.Foundation.Core.Data.Transaction
+{
+    /// <summary>
+    /// Represents a single data change transaction for an entity property.
+    /// Tracks the type of change, the property affected, and the associated value.
+    /// </summary>
+    public class DataTransaction
+    {
+        #region *** Enumerations ***
+        /// <summary>
+        /// Defines the types of data change operations.
+        /// </summary>
+        public enum TransactionTypeEnum
+        {
+            /// <summary>
+            /// Indicates a new property or entity has been added.
+            /// </summary>
+            Add,
+            
+            /// <summary>
+            /// Indicates a property or entity has been removed.
+            /// </summary>
+            Remove,
+            
+            /// <summary>
+            /// Indicates a property value has been modified.
+            /// </summary>
+            Change
+        }
+        #endregion
+
+        #region *** Properties ***
+        /// <summary>
+        /// Gets the name of the property that was changed.
+        /// </summary>
+        public string PropertyName { get; private set; }
+        
+        /// <summary>
+        /// Gets the type of transaction (Add, Remove, or Change).
+        /// </summary>
+        public TransactionTypeEnum TransactionType { get; private set; }
+        
+        /// <summary>
+        /// Gets the value associated with the transaction. 
+        /// May be null for Remove operations.
+        /// </summary>
+        public string? Value { get; private set; }
+        
+        /// <summary>
+        /// Gets a value indicating whether the Value property contains a reference key
+        /// rather than a direct property value.
+        /// 
+        /// When true, the Value represents a reference to another entity (foreign key).
+        /// When false, the Value represents the actual property value.
+        /// </summary>
+        public bool IsValueKey { get; private set; }
+        #endregion
+
+        #region *** Constructor ***
+        /// <summary>
+        /// Initializes a new instance of the DataTransaction class.
+        /// </summary>
+        /// <param name="propertyName">The name of the property that was changed.</param>
+        /// <param name="transactionType">The type of transaction (Add/Remove/Change).</param>
+        /// <param name="value">The new value of the property, or null if the property was removed.</param>
+        /// <param name="isValueKey">True if the value represents a reference key; otherwise, false.</param>
+        public DataTransaction(string propertyName, TransactionTypeEnum transactionType, string? value, bool isValueKey)
+        {
+            PropertyName = propertyName;
+            TransactionType = transactionType;
+            Value = value;
+            IsValueKey = isValueKey;
+        }
+        #endregion
+
+        #region *** Public Overrides ***
+        /// <summary>
+        /// Returns a string representation of the data transaction.
+        /// </summary>
+        /// <returns>A formatted string containing the transaction details.</returns>
+        public override string ToString()
+        {
+            var sb = new StringBuilder();
+            sb.Append("Type=").Append(TransactionType).Append(";");
+            sb.Append("Field=").Append(PropertyName).Append(";");
+            sb.Append("Value=").Append(Value == null ? "<NULL>" : Value).Append(";");
+            return sb.ToString();
+        }
+        #endregion
+    }
+}

+ 80 - 0
qdr.fnd.core/Data/Transaction/DataTransactionCollection.cs

@@ -0,0 +1,80 @@
+
+
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Quadarax.Foundation.Core.Data.Transaction
+{
+    /// <summary>
+    /// Represents a collection of data transactions that track changes to entity properties.
+    /// Provides functionality to manage, deduplicate, and optimize change records.
+    /// </summary>
+    public class DataTransactionCollection : List<DataTransaction>, IDataTransactionCollection
+    {
+        /// <summary>
+        /// Adds a new data transaction to the collection.
+        /// </summary>
+        /// <param name="propertyName">The name of the property that was changed.</param>
+        /// <param name="transactionType">The type of transaction (Add/Remove/Change).</param>
+        /// <param name="value">The new value of the property, or null if the property was removed.</param>
+        public void Add(string propertyName, DataTransaction.TransactionTypeEnum transactionType, string? value)
+        {
+            // Implementation goes here
+        }
+
+        /// <summary>
+        /// Deduplicates the collection by removing redundant transactions while preserving
+        /// the effective changes to entity properties.
+        /// 
+        /// Deduplication follows these rules:
+        /// 1. If multiple changes exist for the same property, only the most recent one is kept.
+        /// 2. If both Add and Remove transactions exist for the same property, both are removed
+        ///    as they cancel each other out.
+        /// 3. Properties are uniquely identified by both name and whether they represent
+        ///    reference keys (IsValueKey).
+        /// </summary>
+        /// <returns>The number of transactions removed during deduplication.</returns>
+        public int Deduplicate()
+        {
+            var originalCount = Count;
+
+            // Group transactions by property name AND whether it's a value key
+            // This ensures we don't mix regular value changes with reference key changes
+            var groupedTransactions = this.GroupBy(x => new { x.PropertyName, x.IsValueKey })
+                                          .ToDictionary(g => g.Key, g => g.ToList());
+
+            // Clear the existing collection
+            Clear();
+
+            foreach (var propertyGroup in groupedTransactions)
+            {
+                string propertyName = propertyGroup.Key.PropertyName;
+                bool isValueKey = propertyGroup.Key.IsValueKey;
+                var transactions = propertyGroup.Value;
+
+                // If there's only one transaction for this property, just add it
+                if (transactions.Count == 1)
+                {
+                    Add(transactions[0]);
+                    continue;
+                }
+
+                // Check for Insert followed by Delete scenario - both can be removed
+                var hasInsert = transactions.Any(t => t.TransactionType == DataTransaction.TransactionTypeEnum.Add);
+                var hasDelete = transactions.Any(t => t.TransactionType == DataTransaction.TransactionTypeEnum.Remove);
+
+                if (hasInsert && hasDelete)
+                {
+                    // Skip this property entirely - the operations cancel each other out
+                    continue;
+                }
+
+                // For other cases, keep only the last transaction for this property
+                Add(transactions.Last());
+            }
+
+            // Return the number of items removed
+            return originalCount - Count;
+        }
+    }
+}

+ 12 - 0
qdr.fnd.core/Data/Transaction/IDataTransactionCollection.cs

@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+
+namespace Quadarax.Foundation.Core.Data.Transaction
+{
+    /// <summary>
+    /// Interface for a read-only collection of data transactions.
+    /// Provides access to a collection of change tracking events.
+    /// </summary>
+    public interface IDataTransactionCollection : IReadOnlyCollection<DataTransaction>
+    {
+    }
+}

+ 10 - 0
qdr.fnd.core/Data/Transaction/IDataTransactionProvider.cs

@@ -0,0 +1,10 @@
+namespace Quadarax.Foundation.Core.Data.Transaction
+{
+    public interface IDataTransactionProvider
+    {
+        void BeginTransactions();
+        void RollbackTransactions();
+        void ApplyTransactions(IDataTransactionCollection transactions);
+        IDataTransactionCollection CommitTransactions();
+    }
+}

+ 70 - 0
qdr.fnd.core/IO/FileSystem/Memory/MemoryFile.cs

@@ -496,6 +496,76 @@ namespace Quadarax.Foundation.Core.IO.FileSystem.Memory
             throw new NotImplementedException();
         }
 
+        public void AppendAllBytes(string path, byte[] bytes)
+        {
+            throw new NotImplementedException();
+        }
+
+        public void AppendAllBytes(string path, ReadOnlySpan<byte> bytes)
+        {
+            throw new NotImplementedException();
+        }
+
+        public Task AppendAllBytesAsync(string path, byte[] bytes, CancellationToken cancellationToken = default)
+        {
+            throw new NotImplementedException();
+        }
+
+        public Task AppendAllBytesAsync(string path, ReadOnlyMemory<byte> bytes, CancellationToken cancellationToken = default)
+        {
+            throw new NotImplementedException();
+        }
+
+        public void AppendAllText(string path, ReadOnlySpan<char> contents)
+        {
+            throw new NotImplementedException();
+        }
+
+        public void AppendAllText(string path, ReadOnlySpan<char> contents, Encoding encoding)
+        {
+            throw new NotImplementedException();
+        }
+
+        public Task AppendAllTextAsync(string path, ReadOnlyMemory<char> contents, CancellationToken cancellationToken = default)
+        {
+            throw new NotImplementedException();
+        }
+
+        public Task AppendAllTextAsync(string path, ReadOnlyMemory<char> contents, Encoding encoding, CancellationToken cancellationToken = default)
+        {
+            throw new NotImplementedException();
+        }
+
+        public void WriteAllBytes(string path, ReadOnlySpan<byte> bytes)
+        {
+            throw new NotImplementedException();
+        }
+
+        public Task WriteAllBytesAsync(string path, ReadOnlyMemory<byte> bytes, CancellationToken cancellationToken = default)
+        {
+            throw new NotImplementedException();
+        }
+
+        public void WriteAllText(string path, ReadOnlySpan<char> contents)
+        {
+            throw new NotImplementedException();
+        }
+
+        public void WriteAllText(string path, ReadOnlySpan<char> contents, Encoding encoding)
+        {
+            throw new NotImplementedException();
+        }
+
+        public Task WriteAllTextAsync(string path, ReadOnlyMemory<char> contents, CancellationToken cancellationToken = default)
+        {
+            throw new NotImplementedException();
+        }
+
+        public Task WriteAllTextAsync(string path, ReadOnlyMemory<char> contents, Encoding encoding, CancellationToken cancellationToken = default)
+        {
+            throw new NotImplementedException();
+        }
+
         // Implement other methods as needed
     }
 

+ 12 - 0
qdr.fnd.core/IO/FileSystem/Memory/MemoryPath.cs

@@ -244,6 +244,18 @@ namespace Quadarax.Foundation.Core.IO.FileSystem.Memory
             throw new NotImplementedException();
         }
 
+        public string Combine(params ReadOnlySpan<string> paths)
+        {
+            return Path.Combine(paths);
+        }
+
+        public string Join(params ReadOnlySpan<string?> paths)
+        {
+           return Join(paths);
+        }
+
+
+
         // Implement other methods as needed
     }
 }

+ 357 - 0
qdr.fnd.core/Object/Dynamic/Dyna.cs

@@ -0,0 +1,357 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Quadarax.Foundation.Core.Object.Dynamic
+{
+    /// <summary>
+    /// Provides a dynamic object implementation that allows for runtime definition and manipulation of properties.
+    /// </summary>
+    /// <remarks>
+    /// The Dyna class offers a flexible way to create objects with dynamic property sets that can be
+    /// defined at runtime. It supports property access control, value comparison, and cloning capabilities.
+    /// </remarks>
+    public class Dyna : IDyna
+    {
+        #region *** Private Fields ***
+        /// <summary>
+        /// Thread-safe dictionary storing the dynamic properties and their values.
+        /// </summary>
+        private IDictionary<string, DynaPropertyWithValue> _properties = new ConcurrentDictionary<string, DynaPropertyWithValue>();
+
+        private IList<string> _changedPropertes = new List<string>();
+        private IList<Tuple<string, object?>> _defaultValues = new List<Tuple<string, object?>>();
+        #endregion
+
+        #region *** Properties ***
+        /// <summary>
+        /// Gets an array of all property definitions contained in this Dyna instance.
+        /// </summary>
+        /// <returns>An array of <see cref="IDynaProperty"/> objects representing the properties.</returns>
+        public IDynaProperty[] Properties => _properties.Values.Select(x => x.Property).ToArray();
+
+        /// <summary>
+        /// Gets an array of all property names changed after calling TrackChanges.
+        /// </summary>
+        /// <returns>An array of <see cref="string"/> contains property names.</returns>
+        public string[] ChangedProperties => _changedPropertes.ToArray();
+
+        /// <summary>
+        /// Gets or sets the value of a property by its name.
+        /// </summary>
+        /// <param name="propertyName">The name of the property to access.</param>
+        /// <returns>The value of the specified property.</returns>
+        /// <exception cref="ArgumentOutOfRangeException">Thrown when the property does not exist.</exception>
+        public object? this[string propertyName]
+        {
+            get => GetValue<object>(propertyName);
+            set => SetValue(propertyName, value);
+        }
+        #endregion
+
+        #region *** Constructors ***
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Dyna"/> class with no properties.
+        /// </summary>
+        public Dyna()
+        {
+            TrackChanges();
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Dyna"/> class with properties derived from the specified type.
+        /// </summary>
+        /// <param name="templateType">The type whose properties should be reflected into this Dyna instance.</param>
+        /// <exception cref="ArgumentException">Thrown when the template type does not have a valid FullName.</exception>
+        public Dyna(Type templateType)
+        {
+            AnalyzeType(templateType);
+            TrackChanges();
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Dyna"/> class with the specified properties.
+        /// </summary>
+        /// <param name="properties">A collection of <see cref="DynaProperty"/> objects defining the properties for this instance.</param>
+        public Dyna(IEnumerable<DynaProperty> properties) : this()
+        {
+            AnalyzeProperties(properties);
+            TrackChanges();
+        }
+        #endregion
+
+        #region *** Public Operations ***
+        /// <summary>
+        /// Gets the value of a property as a specified type.
+        /// </summary>
+        /// <typeparam name="TValue">The type to convert the property value to.</typeparam>
+        /// <param name="propertyName">The name of the property to get the value from.</param>
+        /// <returns>The value of the property cast to the specified type, or null if the property value is null.</returns>
+        /// <exception cref="ArgumentOutOfRangeException">Thrown when the property does not exist.</exception>
+        public TValue? GetValue<TValue>(string propertyName)
+        {
+            if (_properties.TryGetValue(propertyName, out var property))
+            {
+                if (property.Value == null) return default;
+                return (TValue)property.Value;
+            }
+
+            throw new ArgumentOutOfRangeException(nameof(propertyName), $"Property '{propertyName}' not found in Dyna collection.");
+        }
+
+        /// <summary>
+        /// Sets the value of a property.
+        /// </summary>
+        /// <typeparam name="TValue">The type of the value being set.</typeparam>
+        /// <param name="propertyName">The name of the property to set.</param>
+        /// <param name="value">The value to set the property to.</param>
+        /// <exception cref="ArgumentOutOfRangeException">Thrown when the property does not exist.</exception>
+        public void SetValue<TValue>(string propertyName, TValue? value)
+        {
+            if (_properties.TryGetValue(propertyName, out var property))
+            {
+                if (!object.Equals(property.Value, value))
+                {
+                    // check if new value is the same as the default value, then discard the change
+                    if (object.Equals(value, _defaultValues.First(x=>x.Item1 == propertyName).Item2))
+                    {
+                        property.Value = value;
+                        _changedPropertes.Remove(propertyName);
+                        return;
+                    }
+                     
+                    if (OnChangingProperty(propertyName, property.Value, value))
+                    {        
+                        property.Value = value;
+
+                        // track changes
+                        if (!_changedPropertes.Contains(propertyName))
+                            _changedPropertes.Add(propertyName);
+                    }
+                }
+            }
+            else
+                throw new ArgumentOutOfRangeException(nameof(propertyName), $"Property '{propertyName}' not found in Dyna collection.");
+        }
+
+        /// <summary>
+        /// Clears all property values, setting them to null.
+        /// </summary>
+        public void Clear()
+        {
+            foreach (var property in _properties)
+                property.Value.Value = null;
+
+            OnClear();
+
+            TrackChanges();
+        }
+
+        /// <summary>
+        /// Tracks all property changes after calling this method.
+        /// </summary>
+        public void TrackChanges()
+        {
+            _changedPropertes.Clear();
+            _defaultValues.Clear();
+
+            // create new snapshot of default values
+            foreach (var property in _properties)
+                _defaultValues.Add(new Tuple<string, object?>(property.Key, property.Value.Value));
+        }
+
+        /// <summary>
+        /// Determines whether this Dyna instance has the same property structure as another Dyna instance.
+        /// </summary>
+        /// <param name="target">The Dyna instance to compare with.</param>
+        /// <returns>
+        /// <c>true</c> if both instances have the same property names and types; otherwise, <c>false</c>.
+        /// </returns>
+        public bool IsSame(Dyna target)
+        {
+            if (_properties.Count != target._properties.Count)
+                return false;
+            foreach (var property in _properties)
+            {
+                if (!target._properties.TryGetValue(property.Key, out var targetProperty))
+                    return false;
+            }
+            return true;
+        }
+
+        /// <summary>
+        /// Determines whether this Dyna instance has the same properties and values as another Dyna instance.
+        /// </summary>
+        /// <param name="target">The Dyna instance to compare with.</param>
+        /// <returns>
+        /// <c>true</c> if both instances have the same properties and values; otherwise, <c>false</c>.
+        /// </returns>
+        public bool IsEquals(Dyna target)
+        {
+            if (_properties.Count != target._properties.Count)
+                return false;
+            foreach (var property in _properties)
+            {
+                if (!target._properties.TryGetValue(property.Key, out var targetProperty))
+                    return false;
+                if (!Equals(property.Value.Value, targetProperty.Value))
+                    return false;
+            }
+            return true;
+        }
+
+        /// <summary>
+        /// Determines whether this Dyna instance contains a property with the specified name.
+        /// </summary>
+        /// <param name="propertyName">The name of the property to check for.</param>
+        /// <returns><c>true</c> if the property exists; otherwise, <c>false</c>.</returns>
+        public bool ContainsProperty(string propertyName)
+        {
+            return _properties.ContainsKey(propertyName);
+        }
+
+        /// <summary>
+        /// Copies all property values from this Dyna instance to a target Dyna instance.
+        /// </summary>
+        /// <param name="target">The target Dyna instance to copy values to.</param>
+        /// <remarks>
+        /// This method only copies values for properties that exist in both instances.
+        /// It does not create new properties in the target instance.
+        /// </remarks>
+        public void CopyTo(IDyna target)
+        {
+            foreach (var property in _properties)
+                target.SetValue(property.Key, property.Value.Value);
+        }
+
+        /// <summary>
+        /// Creates a deep clone of this Dyna instance.
+        /// </summary>
+        /// <returns>A new Dyna instance with the same properties and values.</returns>
+        public object Clone()
+        {
+            var clone = new Dyna(_properties.Values.Select(x => x.Property));
+            foreach (var property in _properties)
+                clone.SetValue(property.Key, property.Value.Value);
+
+            clone.TrackChanges();
+            return clone;
+        }
+        #endregion
+
+        #region *** Virtual Operations ***
+        public virtual bool OnChangingProperty(string propertyName, object? oldValue, object? newValue)
+        {
+            return true;
+        }
+
+        public virtual void OnClear()
+        {
+
+        }
+
+        #endregion
+
+        #region *** Private Operations ***
+        /// <summary>
+        /// Analyzes a type using reflection and creates corresponding dynamic properties.
+        /// </summary>
+        /// <param name="templateType">The type to analyze.</param>
+        /// <exception cref="ArgumentException">Thrown when the template type does not have a valid FullName.</exception>
+        private void AnalyzeType(Type templateType)
+        {
+            if (templateType.FullName == null)
+                throw new ArgumentException("Type must have a FullName", nameof(templateType));
+
+            foreach (var property in templateType.GetProperties())
+                _properties.Add(property.Name, new DynaPropertyWithValue(new DynaProperty(property.Name, property.PropertyType, DynaPropertyAccessability.Public, DynaPropertyAccessability.Public), null));
+        }
+
+        /// <summary>
+        /// Creates dynamic properties from a collection of property definitions.
+        /// </summary>
+        /// <param name="properties">The collection of property definitions to use.</param>
+        private void AnalyzeProperties(IEnumerable<DynaProperty> properties)
+        {
+            foreach (var property in properties)
+                _properties.Add(property.Name, new DynaPropertyWithValue(property, null));
+        }
+        #endregion
+
+        #region *** Nested Classes ***
+        /// <summary>
+        /// Represents a dynamic property with its metadata.
+        /// </summary>
+        public sealed class DynaProperty : IDynaProperty
+        {
+            #region *** Properties ***
+            /// <summary>
+            /// Gets the name of the property.
+            /// </summary>
+            public string Name { get; private set; }
+
+            /// <summary>
+            /// Gets the type of the property.
+            /// </summary>
+            public Type ValueType { get; private set; }
+
+            /// <summary>
+            /// Gets the accessibility level for reading the property.
+            /// </summary>
+            public DynaPropertyAccessability GetAccessability { get; private set; }
+
+            /// <summary>
+            /// Gets the accessibility level for writing to the property.
+            /// </summary>
+            public DynaPropertyAccessability SetAccessability { get; private set; }
+
+            #endregion
+
+            #region *** Constructors ***
+            /// <summary>
+            /// Initializes a new instance of the <see cref="DynaProperty"/> class.
+            /// </summary>
+            /// <param name="name">The name of the property.</param>
+            /// <param name="valueType">The type of the property.</param>
+            /// <param name="getAccessability">The accessibility level for reading the property.</param>
+            /// <param name="setAccessability">The accessibility level for writing to the property.</param>
+            public DynaProperty(string name, Type valueType, DynaPropertyAccessability getAccessability, DynaPropertyAccessability setAccessability)
+            {
+                Name = name;
+                ValueType = valueType;
+                GetAccessability = getAccessability;
+                SetAccessability = setAccessability;
+            }
+            #endregion
+        }
+
+        /// <summary>
+        /// Internal class that pairs a property definition with its value.
+        /// </summary>
+        private sealed class DynaPropertyWithValue
+        {
+            /// <summary>
+            /// Gets the property definition.
+            /// </summary>
+            public DynaProperty Property { get; private set; }
+
+            /// <summary>
+            /// Gets or sets the current value of the property.
+            /// </summary>
+            public object? Value { get; set; }
+
+            /// <summary>
+            /// Initializes a new instance of the <see cref="DynaPropertyWithValue"/> class.
+            /// </summary>
+            /// <param name="property">The property definition.</param>
+            /// <param name="value">The initial value of the property.</param>
+            public DynaPropertyWithValue(DynaProperty property, object? value)
+            {
+                Property = property;
+                Value = value;
+            }
+        }
+        #endregion
+    }
+}

+ 27 - 0
qdr.fnd.core/Object/Dynamic/DynaPropertyAccessabilitycs.cs

@@ -0,0 +1,27 @@
+namespace Quadarax.Foundation.Core.Object.Dynamic
+{
+    /// <summary>
+    /// Defines the accessibility levels for dynamic properties.
+    /// </summary>
+    /// <remarks>
+    /// This enumeration is used to specify the access restrictions for
+    /// reading and writing dynamic properties, similar to C# access modifiers.
+    /// </remarks>
+    public enum DynaPropertyAccessability
+    {
+        /// <summary>
+        /// Indicates that the property can only be accessed within the defining class.
+        /// </summary>
+        Private,
+        
+        /// <summary>
+        /// Indicates that the property can be accessed within the defining class and derived classes.
+        /// </summary>
+        Protected,
+        
+        /// <summary>
+        /// Indicates that the property can be accessed from any context.
+        /// </summary>
+        Public
+    }
+}

+ 47 - 0
qdr.fnd.core/Object/Dynamic/IDyna.cs

@@ -0,0 +1,47 @@
+using System;
+
+namespace Quadarax.Foundation.Core.Object.Dynamic
+{
+    /// <summary>
+    /// Defines the interface for dynamic objects that support runtime property management.
+    /// </summary>
+    /// <remarks>
+    /// This interface provides a contract for objects that can have their properties
+    /// dynamically added, accessed, and modified at runtime.
+    /// </remarks>
+    public interface IDyna : ICloneable
+    {
+        /// <summary>
+        /// Gets the value of a property as a specified type.
+        /// </summary>
+        /// <typeparam name="TValue">The type to convert the property value to.</typeparam>
+        /// <param name="propertyName">The name of the property to get the value from.</param>
+        /// <returns>The value of the property cast to the specified type, or null if the property value is null.</returns>
+        /// <exception cref="ArgumentOutOfRangeException">Thrown when the property does not exist.</exception>
+        TValue? GetValue<TValue>(string propertyName);
+        
+        /// <summary>
+        /// Sets the value of a property.
+        /// </summary>
+        /// <typeparam name="TValue">The type of the value being set.</typeparam>
+        /// <param name="propertyName">The name of the property to set.</param>
+        /// <param name="value">The value to set the property to.</param>
+        /// <exception cref="ArgumentOutOfRangeException">Thrown when the property does not exist.</exception>
+        void SetValue<TValue>(string propertyName, TValue? value);
+        
+        /// <summary>
+        /// Clears all property values, setting them to null.
+        /// </summary>
+        void Clear();
+        
+        /// <summary>
+        /// Copies all property values from this instance to a target instance.
+        /// </summary>
+        /// <param name="target">The target instance to copy values to.</param>
+        /// <remarks>
+        /// This method only copies values for properties that exist in both instances.
+        /// It does not create new properties in the target instance.
+        /// </remarks>
+        void CopyTo(IDyna target);
+    }
+}

+ 34 - 0
qdr.fnd.core/Object/Dynamic/IDynaProperty.cs

@@ -0,0 +1,34 @@
+using System;
+
+namespace Quadarax.Foundation.Core.Object.Dynamic
+{
+    /// <summary>
+    /// Defines the interface for a dynamic property definition.
+    /// </summary>
+    /// <remarks>
+    /// This interface provides access to metadata about a dynamic property,
+    /// including its name, type, and access modifiers.
+    /// </remarks>
+    public interface IDynaProperty
+    {
+        /// <summary>
+        /// Gets the name of the property.
+        /// </summary>
+        string Name { get; }
+        
+        /// <summary>
+        /// Gets the type of the property.
+        /// </summary>
+        Type ValueType { get; }
+        
+        /// <summary>
+        /// Gets the accessibility level for reading the property.
+        /// </summary>
+        DynaPropertyAccessability GetAccessability { get; }
+        
+        /// <summary>
+        /// Gets the accessibility level for writing to the property.
+        /// </summary>
+        DynaPropertyAccessability SetAccessability { get; }
+    }
+}

+ 19 - 0
qdr.fnd.core/Object/Dynamic/TDyna.cs

@@ -0,0 +1,19 @@
+using Quadarax.Foundation.Core.Data.Transaction;
+
+namespace Quadarax.Foundation.Core.Object.Dynamic
+{
+
+    public class TDyna : Dyna
+    {
+        public IDataTransactionCollection GetTransactionCollection()
+        {
+            return new DataTransactionCollection();
+        }
+
+        public void ApplyTransactionCollection(IDataTransactionCollection transactions)
+        {
+
+        }
+
+    }
+}

+ 9 - 7
qdr.fnd.core/qdr.fnd.core.csproj

@@ -15,12 +15,14 @@
     <Copyright>Copyright © 2023,2024,2025 Quadarax</Copyright>
     <PackageIcon>quadarax_dll.png</PackageIcon>
     <PackageTags>QDR;FND;CORE</PackageTags>
-    <AssemblyVersion>0.0.7.0</AssemblyVersion>
-    <FileVersion>0.0.7.0</FileVersion>
-    <Version>0.0.7.0-alpha</Version>
-    <PackageReleaseNotes>Date:13.1.2025
-		- extends IError/Error add AsCodeException
-		- extends Result add ThrowIfError, ToAggregateException
+    <AssemblyVersion>0.0.8.0</AssemblyVersion>
+    <FileVersion>0.0.8.0</FileVersion>
+    <Version>0.0.8.0-alpha</Version>
+    <PackageReleaseNotes>Date:13.3.2025
+		- Add Object.Dynamic.Dyna dynamic object to work with dynamic properties
+		- Add DataTransaction to work with simple transactional data operations
+		- Add TDyna dynamic object that supports DataTransaction
+		- update dependencies packages (System.IO.Abstractions)
     </PackageReleaseNotes>
     <PackageReadmeFile>releasenotes.md</PackageReadmeFile>
     <Nullable>enable</Nullable>
@@ -46,7 +48,7 @@
   </ItemGroup>
 
   <ItemGroup>
-    <PackageReference Include="System.IO.Abstractions" Version="21.2.1" />
+    <PackageReference Include="System.IO.Abstractions" Version="22.0.12" />
   </ItemGroup>
 
   <ItemGroup>

+ 8 - 0
qdr.fnd.core/releasenotes.md

@@ -1,6 +1,14 @@
 # Quadarax.Foundation.Core * Release notes
 Ordered by version descending
 
+## 0.0.8.0
+Date:__13.3.2025__
+- Add Object.Dynamic.Dyna dynamic object to work with dynamic properties
+- Add DataTransaction to work with simple transactional data operations
+- Add TDyna dynamic object that supports DataTransaction
+- update dependencies packages (System.IO.Abstractions)
+---
+
 ## 0.0.7.0
 Date:__13.1.2025__
 - extends IError/Error add AsCodeException