-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathCollectionPropertyHolder.cs
174 lines (154 loc) · 5.47 KB
/
CollectionPropertyHolder.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Havit.Business;
/// <summary>
/// Třída pro objekt, který nese kolekci property BusinessObjectu.
/// </summary>
/// <typeparam name="CollectionType">typ kolekce, jíž je CollectionPropertyHolder nosičem</typeparam>
/// <typeparam name="BusinessObjectType">typ prvku kolekce</typeparam>
public class CollectionPropertyHolder<CollectionType, BusinessObjectType> : PropertyHolderBase
where BusinessObjectType : BusinessObjectBase
where CollectionType : BusinessObjectCollection<BusinessObjectType, CollectionType>, new()
{
private readonly Func<int, BusinessObjectType> getObjectFunc;
private CollectionType _value;
private BusinessObjectType[] _loadedValue;
private string itemIDsWithDelemiter;
/// <summary>
/// Založí instanci CollectionPropertyHolderu.
/// </summary>
/// <param name="owner">Objekt, kterému CollectionPropertyHolder patří.</param>
/// <param name="getObjectFunc">Delegát na funkci GetObject příslušného business objektu.</param>
public CollectionPropertyHolder(BusinessObjectBase owner, Func<int, BusinessObjectType> getObjectFunc)
: base(owner)
{
this.getObjectFunc = getObjectFunc;
}
/// <summary>
/// Hodnota, kterou CollectionPropertyHolder nese.
/// </summary>
public CollectionType Value
{
get
{
// Zde jen zkontrolujeme, zda došlo k požadavku inicializaci.
// Díky odloženému provedení inicializace ve skutečnosti nemusí být hodnoty kolekce ještě nastaveny.
// Díky konvenci pojmenování v předkovi zde může dojík ke zmatení: initialization vs. value initialization
// CheckInitialization - zkontroluje, zda byla hodnota nastavena zvenčí, nemusí být nutně ve value, to zajistí následující
// EnsureLazyValueInitialization - zajistí, aby se hodnota nastavená z venčí dostala do _value (a _loadedValues)
CheckInitialization();
EnsureLazyValueInitialization();
return _value;
}
}
/// <summary>
/// Originální hodnoty (prvky kolekce) načtené z databáze.
/// </summary>
public BusinessObjectType[] LoadedValue
{
get
{
EnsureLazyValueInitialization();
return _loadedValue;
}
}
/// <summary>
/// Inicilizuje hodnotu property holderu.
/// Určeno pro instance nových objektů (objektů k uložení).
/// </summary>
public void Initialize()
{
if (_value == null)
{
_value = new CollectionType();
_value.DisallowDuplicatesWithoutCheckDuplicates();
_value.CollectionChanged += delegate (object sender, EventArgs e)
{
IsDirty = true;
};
}
else
{
_value.Clear();
}
_loadedValue = new BusinessObjectType[0];
IsInitialized = true;
}
/// <summary>
/// Inicilizuje hodnotu property holderu.
/// Určeno pro instance objektů načtených z databáze.
/// </summary>
/// <param name="itemIDsWithDelemiter">Identifikátory objektů v kolekci serializované pomocí FOR XML (např. "1|2|3|4|" - obsahuje na separátor i na konci).</param>
public void Initialize(string itemIDsWithDelemiter) // může být null
{
if (itemIDsWithDelemiter == null)
{
// i když nemáme v databázi žádnou hodnotu, musíme buď
// zajistit lazy initialization kolekce
// nebo kolekci rovnou inicializovat (zvoleno toto řešení).
Initialize();
}
else
{
// řekneme, že property holder byl inicializován
IsInitialized = true;
// Avšak nestaráme se o kolekci - použijeme strategii odložení inicializace do okamžiku prvníhop použití.
// Řekneme jen, jaké hodnoty mají být nastaveny, až (a pokud) budou potřeba.
this.itemIDsWithDelemiter = itemIDsWithDelemiter;
}
}
/// <summary>
/// Zajistí nastavení hodnoty do _value a _lazyValue.
/// </summary>
private void EnsureLazyValueInitialization()
{
if (itemIDsWithDelemiter != null)
{
// cachované readonly objekty mohou inicializovat objekt paralelně, proti čemuž se potřebujeme ochránit
// nechci zakládat nový zámek, použiji instanci, nezamykáme nikde nic jiného
// (úvaha, že bychom pro zámek použili itemIDsWithDelemiter je chybná, neboť v extrémním případě již může být null)
lock (this)
{
if (itemIDsWithDelemiter != null)
{
_value = new CollectionType();
if (itemIDsWithDelemiter.Length > 25)
{
Span<byte> itemIDsSpan = Encoding.UTF8.GetBytes(itemIDsWithDelemiter);
while (itemIDsSpan.Length > 0)
{
System.Buffers.Text.Utf8Parser.TryParse(itemIDsSpan, out int id, out int bytesConsumed);
_value.Add(getObjectFunc(id));
itemIDsSpan = itemIDsSpan.Slice(bytesConsumed + 1); // za každou (i za poslední) položkou je oddělovač
}
}
else
{
string[] itemIDs = itemIDsWithDelemiter.Split('|');
int itemIDsLength = itemIDs.Length - 1; // za každou (i za poslední) položkou je oddělovač
for (int i = 0; i < itemIDsLength; i++)
{
_value.Add(getObjectFunc(BusinessObjectBase.FastIntParse(itemIDs[i])));
}
}
}
_value.DisallowDuplicatesWithoutCheckDuplicates();
_value.CollectionChanged += delegate (object sender, EventArgs e)
{
IsDirty = true;
};
itemIDsWithDelemiter = null;
UpdateLoadedValue(); // musí být až na konci - implementace volá Value a pokud by nebylo až za předchozím řádkem, provedli bychom StackOverflowException }
}
}
}
/// <summary>
/// Nastaví hodnotu LoadedValue dle Value.
/// </summary>
public void UpdateLoadedValue()
{
_loadedValue = Value.ToArray();
}
}