/
LocalView.cs
861 lines (773 loc) · 41.3 KB
/
LocalView.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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
using Microsoft.EntityFrameworkCore.Internal;
namespace Microsoft.EntityFrameworkCore.ChangeTracking;
/// <summary>
/// A collection that stays in sync with entities of a given type being tracked by
/// a <see cref="DbContext" />. Call <see cref="DbSet{TEntity}.Local" /> to obtain a
/// local view.
/// </summary>
/// <remarks>
/// <para>
/// This local view will stay in sync as entities are added or removed from the context. Likewise, entities
/// added to or removed from the local view will automatically be added to or removed
/// from the context.
/// </para>
/// <para>
/// Adding an entity to this collection will cause it to be tracked in the <see cref="EntityState.Added" />
/// state by the context unless it is already being tracked.
/// </para>
/// <para>
/// Removing an entity from this collection will cause it to be marked as <see cref="EntityState.Deleted" />,
/// unless it was previously in the Added state, in which case it will be detached from the context.
/// </para>
/// <para>
/// The collection implements <see cref="INotifyCollectionChanged" />,
/// <see cref="INotifyPropertyChanging" />, and <see cref="INotifyPropertyChanging" /> such that
/// notifications are generated when an entity starts being tracked by the context or is
/// marked as <see cref="EntityState.Deleted" /> or <see cref="EntityState.Detached" />.
/// </para>
/// <para>
/// Do not use this type directly for data binding. Instead call <see cref="ToObservableCollection" />
/// for WPF binding, or <see cref="ToBindingList" /> for WinForms.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-local-views">Local views of tracked entities in EF Core</see> for more information and
/// examples.
/// </para>
/// </remarks>
/// <typeparam name="TEntity">The type of the entity in the local view.</typeparam>
public class LocalView<[DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] TEntity> :
ICollection<TEntity>,
INotifyCollectionChanged,
INotifyPropertyChanged,
INotifyPropertyChanging,
IListSource
where TEntity : class
{
private ObservableBackedBindingList<TEntity>? _bindingList;
private ObservableCollection<TEntity>? _observable;
private readonly DbContext _context;
private readonly IEntityType _entityType;
private int _countChanges;
private IEntityFinder<TEntity>? _finder;
private int? _count;
private bool _triggeringStateManagerChange;
private bool _triggeringObservableChange;
private bool _triggeringLocalViewChange;
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
[EntityFrameworkInternal]
public LocalView(DbSet<TEntity> set)
{
_context = set.GetService<ICurrentDbContext>().Context;
_entityType = set.EntityType;
set.GetService<ILocalViewListener>().RegisterView(StateManagerChangedHandler);
}
/// <summary>
/// Returns an <see cref="ObservableCollection{T}" /> implementation that stays in sync with this collection.
/// Use this for WPF data binding.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-local-views">Local views of tracked entities in EF Core</see> for more information and
/// examples.
/// </remarks>
/// <returns>The collection.</returns>
public virtual ObservableCollection<TEntity> ToObservableCollection()
{
if (_observable == null)
{
_observable = new ObservableCollection<TEntity>(this);
_observable.CollectionChanged += ObservableCollectionChanged;
CollectionChanged += LocalViewCollectionChanged;
}
return _observable;
}
private void LocalViewCollectionChanged(object? _, NotifyCollectionChangedEventArgs args)
{
Check.DebugAssert(
args.Action == NotifyCollectionChangedAction.Add || args.Action == NotifyCollectionChangedAction.Remove,
"action is not Add or Remove");
if (_triggeringLocalViewChange)
{
return;
}
try
{
_triggeringObservableChange = true;
if (args.Action == NotifyCollectionChangedAction.Remove)
{
Check.DebugAssert(args.OldItems!.Count == 1, $"OldItems.Count is {args.OldItems.Count}");
_observable!.Remove((TEntity)args.OldItems[0]!);
}
else
{
Check.DebugAssert(args.NewItems!.Count == 1, $"NewItems.Count is {args.NewItems.Count}");
_observable!.Add((TEntity)args.NewItems[0]!);
}
}
finally
{
_triggeringObservableChange = false;
}
}
private void ObservableCollectionChanged(object? _, NotifyCollectionChangedEventArgs args)
{
if (_triggeringObservableChange)
{
return;
}
try
{
_triggeringLocalViewChange = true;
if (args.Action == NotifyCollectionChangedAction.Reset)
{
Clear();
}
else
{
if (args.Action == NotifyCollectionChangedAction.Remove
|| args.Action == NotifyCollectionChangedAction.Replace)
{
foreach (TEntity entity in args.OldItems!)
{
Remove(entity);
}
}
if (args.Action == NotifyCollectionChangedAction.Add
|| args.Action == NotifyCollectionChangedAction.Replace)
{
foreach (TEntity entity in args.NewItems!)
{
Add(entity);
}
}
}
}
finally
{
_triggeringLocalViewChange = false;
}
}
/// <summary>
/// Returns an <see cref="IEnumerator{T}" /> for all tracked entities of type TEntity
/// that are not marked as deleted.
/// </summary>
/// <returns>An enumerator for the collection.</returns>
public virtual IEnumerator<TEntity> GetEnumerator()
=> _context.GetDependencies().StateManager.GetNonDeletedEntities<TEntity>().GetEnumerator();
/// <summary>
/// Returns an <see cref="IEnumerator{T}" /> for all tracked entities of type TEntity
/// that are not marked as deleted.
/// </summary>
/// <returns>An enumerator for the collection.</returns>
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
/// <summary>
/// Adds a new entity to the <see cref="DbContext" />. If the entity is not being tracked or is currently
/// marked as deleted, then it becomes tracked as <see cref="EntityState.Added" />.
/// </summary>
/// <remarks>
/// <para>
/// Note that only the given entity is tracked. Any related entities discoverable from
/// the given entity are not automatically tracked.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-local-views">Local views of tracked entities in EF Core</see> for more information and
/// examples.
/// </para>
/// </remarks>
/// <param name="item">The item to start tracking.</param>
public virtual void Add(TEntity item)
{
// For something that is already in the state manager as Unchanged or Modified we don't try
// to Add it again since doing so would change its state to Added, which is probably not what
// was wanted in this case.
var entry = _context.GetDependencies().StateManager.GetOrCreateEntry(item, _entityType);
if (entry.EntityState == EntityState.Deleted
|| entry.EntityState == EntityState.Detached)
{
try
{
_triggeringStateManagerChange = true;
OnCountPropertyChanging();
_context.Add(item);
_countChanges++;
OnCollectionChanged(NotifyCollectionChangedAction.Add, item);
OnCountPropertyChanged();
}
finally
{
_triggeringStateManagerChange = false;
}
}
}
/// <summary>
/// Marks all entities of type TEntity being tracked by the <see cref="DbContext" />
/// as <see cref="EntityState.Deleted" />.
/// </summary>
/// <remarks>
/// <para>
/// Entities that are currently marked as <see cref="EntityState.Added" /> will be marked
/// as <see cref="EntityState.Detached" /> since the Added state indicates that the entity
/// has not been saved to the database and hence it does not make sense to attempt to
/// delete it from the database.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-local-views">Local views of tracked entities in EF Core</see> for more information and
/// examples.
/// </para>
/// </remarks>
public virtual void Clear()
{
foreach (var entity in _context.GetDependencies().StateManager.GetNonDeletedEntities<TEntity>().ToList())
{
Remove(entity);
}
}
/// <summary>
/// Returns <see langword="true" /> if the entity is being tracked by the context and has not been
/// marked as Deleted.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-local-views">Local views of tracked entities in EF Core</see> for more information and
/// examples.
/// </remarks>
/// <param name="item">The entity to check.</param>
/// <returns><see langword="true" /> if the entity is being tracked by the context and has not been marked as Deleted.</returns>
public virtual bool Contains(TEntity item)
{
var entry = _context.GetDependencies().StateManager.TryGetEntry(item);
return entry != null
&& entry.EntityState != EntityState.Deleted
&& entry.EntityState != EntityState.Detached;
}
/// <summary>
/// Copies to an array all entities of type TEntity that are being tracked and are
/// not marked as Deleted.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-local-views">Local views of tracked entities in EF Core</see> for more information and
/// examples.
/// </remarks>
/// <param name="array">The array into which to copy entities.</param>
/// <param name="arrayIndex">The index into the array to start copying.</param>
public virtual void CopyTo(TEntity[] array, int arrayIndex)
{
foreach (var entity in _context.GetDependencies().StateManager.GetNonDeletedEntities<TEntity>())
{
array[arrayIndex++] = entity;
}
}
/// <summary>
/// Marks the given entity as <see cref="EntityState.Deleted" />.
/// </summary>
/// <remarks>
/// <para>
/// Entities that are currently marked as <see cref="EntityState.Added" /> will be marked
/// as <see cref="EntityState.Detached" /> since the Added state indicates that the entity
/// has not been saved to the database and hence it does not make sense to attempt to
/// delete it from the database.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-local-views">Local views of tracked entities in EF Core</see> for more information and
/// examples.
/// </para>
/// </remarks>
/// <param name="item">The entity to delete.</param>
/// <returns><see langword="true" /> if the entity was being tracked and was not already Deleted.</returns>
public virtual bool Remove(TEntity item)
{
var entry = _context.GetDependencies().StateManager.TryGetEntry(item);
if (entry != null
&& entry.EntityState != EntityState.Deleted)
{
try
{
_triggeringStateManagerChange = true;
OnCountPropertyChanging();
_context.Remove(item);
_countChanges--;
OnCollectionChanged(NotifyCollectionChangedAction.Remove, item);
OnCountPropertyChanged();
}
finally
{
_triggeringStateManagerChange = false;
}
return true;
}
return false;
}
private void StateManagerChangedHandler(InternalEntityEntry entry, EntityState previousState)
{
if (_triggeringStateManagerChange)
{
return;
}
if (entry.Entity is TEntity entity)
{
var wasIn = previousState != EntityState.Detached
&& previousState != EntityState.Deleted;
var isIn = entry.EntityState != EntityState.Detached
&& entry.EntityState != EntityState.Deleted;
if (wasIn != isIn)
{
OnCountPropertyChanging();
if (isIn)
{
_countChanges++;
OnCollectionChanged(NotifyCollectionChangedAction.Add, entity);
}
else
{
_countChanges--;
OnCollectionChanged(NotifyCollectionChangedAction.Remove, entity);
}
OnCountPropertyChanged();
}
}
}
/// <summary>
/// The number of entities of type TEntity that are being tracked and are not marked
/// as Deleted.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-local-views">Local views of tracked entities in EF Core</see> for more information and
/// examples.
/// </remarks>
public virtual int Count
{
get
{
if (!_count.HasValue)
{
var stateManager = _context.GetDependencies().StateManager;
var count = 0;
foreach (var _ in stateManager.GetNonDeletedEntities<TEntity>())
{
count++;
}
_count = count;
_countChanges = 0;
}
return _count.Value + _countChanges;
}
}
/// <summary>
/// False, since the collection is not read-only.
/// </summary>
public virtual bool IsReadOnly
=> false;
/// <summary>
/// Occurs when a property of this collection (such as <see cref="Count" />) changes.
/// </summary>
public event PropertyChangedEventHandler? PropertyChanged;
/// <summary>
/// Occurs when a property of this collection (such as <see cref="Count" />) is changing.
/// </summary>
public event PropertyChangingEventHandler? PropertyChanging;
/// <summary>
/// Occurs when the contents of the collection changes, either because an entity
/// has been directly added or removed from the collection, or because an entity
/// starts being tracked, or because an entity is marked as Deleted.
/// </summary>
public event NotifyCollectionChangedEventHandler? CollectionChanged;
/// <summary>
/// Raises the <see cref="PropertyChanged" /> event.
/// </summary>
/// <param name="e">Details of the property that changed.</param>
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
=> PropertyChanged?.Invoke(this, e);
/// <summary>
/// Raises the <see cref="PropertyChanging" /> event.
/// </summary>
/// <param name="e">Details of the property that is changing.</param>
protected virtual void OnPropertyChanging(PropertyChangingEventArgs e)
=> PropertyChanging?.Invoke(this, e);
/// <summary>
/// Raises the <see cref="CollectionChanged" /> event.
/// </summary>
/// <param name="e">Details of the change.</param>
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
=> CollectionChanged?.Invoke(this, e);
private void OnCountPropertyChanged()
=> OnPropertyChanged(ObservableHashSetSingletons.CountPropertyChanged);
private void OnCountPropertyChanging()
=> OnPropertyChanging(ObservableHashSetSingletons.CountPropertyChanging);
private void OnCollectionChanged(NotifyCollectionChangedAction action, object item)
=> OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item));
/// <summary>
/// Returns a <see cref="BindingList{T}" /> implementation that stays in sync with this collection.
/// Use this for WinForms data binding.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-local-views">Local views of tracked entities in EF Core</see> for more information and
/// examples.
/// </remarks>
/// <returns>The binding list.</returns>
[RequiresUnreferencedCode(
"BindingList raises ListChanged events with PropertyDescriptors. PropertyDescriptors require unreferenced code.")]
public virtual BindingList<TEntity> ToBindingList()
=> _bindingList ??= new ObservableBackedBindingList<TEntity>(ToObservableCollection());
/// <summary>
/// This method is called by data binding frameworks when attempting to data bind
/// directly to a <see cref="LocalView{TEntity}" />.
/// </summary>
/// <remarks>
/// This implementation always throws an exception as <see cref="LocalView{TEntity}" />
/// does not maintain an ordered list with indexes. Instead call <see cref="ToObservableCollection" />
/// for WPF binding, or <see cref="ToBindingList" /> for WinForms.
/// </remarks>
/// <exception cref="NotSupportedException">Always thrown.</exception>
/// <returns>Never returns, always throws an exception.</returns>
IList IListSource.GetList()
=> throw new NotSupportedException(CoreStrings.DataBindingToLocalWithIListSource);
/// <summary>
/// Gets a value indicating whether the collection is a collection of System.Collections.IList objects.
/// Always returns <see langword="false" />.
/// </summary>
bool IListSource.ContainsListCollection
=> false;
/// <summary>
/// Resets this view, clearing any <see cref="IBindingList" /> created with <see cref="ToBindingList" /> and
/// any <see cref="ObservableCollection{T}" /> created with <see cref="ToObservableCollection" />, and clearing any
/// events registered on <see cref="PropertyChanged" />, <see cref="PropertyChanging" />, or <see cref="CollectionChanged" />.
/// </summary>
public virtual void Reset()
{
_bindingList = null;
_observable = null;
_countChanges = 0;
_count = 0;
_triggeringStateManagerChange = false;
_triggeringObservableChange = false;
_triggeringLocalViewChange = false;
PropertyChanged = null;
PropertyChanging = null;
CollectionChanged = null;
}
/// <summary>
/// Finds an <see cref="EntityEntry{TEntity}" /> for the entity with the given primary key value in the change tracker, if it is
/// being tracked. <see langword="null" /> is returned if no entity with the given key value is being tracked.
/// This method never queries the database.
/// </summary>
/// <remarks>
/// <para>
/// By default, accessing <see cref="DbSet{TEntity}.Local" /> will call <see cref="ChangeTracker.DetectChanges" /> to
/// ensure that all entities searched and returned are up-to-date. Calling this method will not result in another call to
/// <see cref="ChangeTracker.DetectChanges" />. Since this method is commonly used for fast lookups, consider reusing
/// the <see cref="DbSet{TEntity}.Local" /> object for multiple lookups and/or disabling automatic detecting of changes using
/// <see cref="ChangeTracker.AutoDetectChangesEnabled" />.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-change-tracking">EF Core change tracking</see> for more information and examples.
/// </para>
/// </remarks>
/// <typeparam name="TKey">The type of the primary key property.</typeparam>
/// <param name="keyValue">The value of the primary key for the entity to be found.</param>
/// <returns>An entry for the entity found, or <see langword="null" />.</returns>
public virtual EntityEntry<TEntity>? FindEntry<TKey>(TKey keyValue)
{
var internalEntityEntry = Finder.FindEntry(keyValue);
return internalEntityEntry == null ? null : new EntityEntry<TEntity>(internalEntityEntry);
}
/// <summary>
/// Finds an <see cref="EntityEntry{TEntity}" /> for the entity with the given primary key values in the change tracker, if it is
/// being tracked. <see langword="null" /> is returned if no entity with the given key values is being tracked.
/// This method never queries the database.
/// </summary>
/// <remarks>
/// <para>
/// By default, accessing <see cref="DbSet{TEntity}.Local" /> will call <see cref="ChangeTracker.DetectChanges" /> to
/// ensure that all entities searched and returned are up-to-date. Calling this method will not result in another call to
/// <see cref="ChangeTracker.DetectChanges" />. Since this method is commonly used for fast lookups, consider reusing
/// the <see cref="DbSet{TEntity}.Local" /> object for multiple lookups and/or disabling automatic detecting of changes using
/// <see cref="ChangeTracker.AutoDetectChangesEnabled" />.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-change-tracking">EF Core change tracking</see> for more information and examples.
/// </para>
/// </remarks>
/// <param name="keyValues">The values of the primary key for the entity to be found.</param>
/// <returns>An entry for the entity found, or <see langword="null" />.</returns>
public virtual EntityEntry<TEntity>? FindEntryUntyped(IEnumerable<object?> keyValues)
{
Check.NotNull(keyValues, nameof(keyValues));
var internalEntityEntry = Finder.FindEntry(keyValues);
return internalEntityEntry == null ? null : new EntityEntry<TEntity>(internalEntityEntry);
}
/// <summary>
/// Returns an <see cref="EntityEntry{TEntity}" /> for the first entity being tracked by the context where the value of the
/// given property matches the given value. The entry provide access to change tracking information and operations for the entity.
/// </summary>
/// <remarks>
/// <para>
/// This method is frequently used to get the entity with a given non-null foreign key, primary key, or alternate key value.
/// Lookups using a key property like this are more efficient than lookups on other property value.
/// </para>
/// <para>
/// By default, accessing <see cref="DbSet{TEntity}.Local" /> will call <see cref="ChangeTracker.DetectChanges" /> to
/// ensure that all entities searched and returned are up-to-date. Calling this method will not result in another call to
/// <see cref="ChangeTracker.DetectChanges" />. Since this method is commonly used for fast lookups, consider reusing
/// the <see cref="DbSet{TEntity}.Local" /> object for multiple lookups and/or disabling automatic detecting of changes using
/// <see cref="ChangeTracker.AutoDetectChangesEnabled" />.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-change-tracking">EF Core change tracking</see> for more information and examples.
/// </para>
/// </remarks>
/// <param name="propertyName">The name of the property to match.</param>
/// <param name="propertyValue">The value of the property to match.</param>
/// <typeparam name="TProperty">The type of the property value.</typeparam>
/// <returns>An entry for the entity found, or <see langword="null" />.</returns>
public virtual EntityEntry<TEntity>? FindEntry<TProperty>(string propertyName, TProperty? propertyValue)
=> FindEntry(FindAndValidateProperty<TProperty>(propertyName), propertyValue);
/// <summary>
/// Returns an <see cref="EntityEntry{TEntity}" /> for the first entity being tracked by the context where the value of the
/// given property matches the given values. The entry provide access to change tracking information and operations for the entity.
/// </summary>
/// <remarks>
/// <para>
/// This method is frequently used to get the entity with a given non-null foreign key, primary key, or alternate key values.
/// Lookups using a key property like this are more efficient than lookups on other property value.
/// </para>
/// <para>
/// By default, accessing <see cref="DbSet{TEntity}.Local" /> will call <see cref="ChangeTracker.DetectChanges" /> to
/// ensure that all entities searched and returned are up-to-date. Calling this method will not result in another call to
/// <see cref="ChangeTracker.DetectChanges" />. Since this method is commonly used for fast lookups, consider reusing
/// the <see cref="DbSet{TEntity}.Local" /> object for multiple lookups and/or disabling automatic detecting of changes using
/// <see cref="ChangeTracker.AutoDetectChangesEnabled" />.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-change-tracking">EF Core change tracking</see> for more information and examples.
/// </para>
/// </remarks>
/// <param name="propertyNames">The name of the properties to match.</param>
/// <param name="propertyValues">The values of the properties to match.</param>
/// <returns>An entry for the entity found, or <see langword="null" />.</returns>
public virtual EntityEntry<TEntity>? FindEntry(IEnumerable<string> propertyNames, IEnumerable<object?> propertyValues)
{
Check.NotNull(propertyNames, nameof(propertyNames));
return FindEntry(propertyNames.Select(n => _entityType.GetProperty(n)), propertyValues);
}
/// <summary>
/// Returns an <see cref="EntityEntry{TEntity}" /> for each entity being tracked by the context where the value of the given
/// property matches the given value. The entries provide access to change tracking information and operations for each entity.
/// </summary>
/// <remarks>
/// <para>
/// This method is frequently used to get the entities with a given non-null foreign key, primary key, or alternate key values.
/// Lookups using a key property like this are more efficient than lookups on other property values.
/// </para>
/// <para>
/// By default, accessing <see cref="DbSet{TEntity}.Local" /> will call <see cref="ChangeTracker.DetectChanges" /> to
/// ensure that all entities searched and returned are up-to-date. Calling this method will not result in another call to
/// <see cref="ChangeTracker.DetectChanges" />. Since this method is commonly used for fast lookups, consider reusing
/// the <see cref="DbSet{TEntity}.Local" /> object for multiple lookups and/or disabling automatic detecting of changes using
/// <see cref="ChangeTracker.AutoDetectChangesEnabled" />.
/// </para>
/// <para>
/// Note that modification of entity state while iterating over the returned enumeration may result in
/// an <see cref="InvalidOperationException" /> indicating that the collection was modified while enumerating.
/// To avoid this, create a defensive copy using <see cref="Enumerable.ToList{TSource}" /> or similar before iterating.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-change-tracking">EF Core change tracking</see> for more information and examples.
/// </para>
/// </remarks>
/// <param name="propertyName">The name of the property to match.</param>
/// <param name="propertyValue">The value of the property to match.</param>
/// <typeparam name="TProperty">The type of the property value.</typeparam>
/// <returns>An entry for each entity being tracked.</returns>
public virtual IEnumerable<EntityEntry<TEntity>> GetEntries<TProperty>(string propertyName, TProperty? propertyValue)
=> GetEntries(FindAndValidateProperty<TProperty>(propertyName), propertyValue);
/// <summary>
/// Returns an <see cref="EntityEntry" /> for each entity being tracked by the context where the values of the given properties
/// matches the given values. The entries provide access to change tracking information and operations for each entity.
/// </summary>
/// <remarks>
/// <para>
/// This method is frequently used to get the entities with a given non-null foreign key, primary key, or alternate key values.
/// Lookups using a key property like this are more efficient than lookups on other property values.
/// </para>
/// <para>
/// By default, accessing <see cref="DbSet{TEntity}.Local" /> will call <see cref="ChangeTracker.DetectChanges" /> to
/// ensure that all entities searched and returned are up-to-date. Calling this method will not result in another call to
/// <see cref="ChangeTracker.DetectChanges" />. Since this method is commonly used for fast lookups, consider reusing
/// the <see cref="DbSet{TEntity}.Local" /> object for multiple lookups and/or disabling automatic detecting of changes using
/// <see cref="ChangeTracker.AutoDetectChangesEnabled" />.
/// </para>
/// <para>
/// Note that modification of entity state while iterating over the returned enumeration may result in
/// an <see cref="InvalidOperationException" /> indicating that the collection was modified while enumerating.
/// To avoid this, create a defensive copy using <see cref="Enumerable.ToList{TSource}" /> or similar before iterating.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-change-tracking">EF Core change tracking</see> for more information and examples.
/// </para>
/// </remarks>
/// <param name="propertyNames">The name of the properties to match.</param>
/// <param name="propertyValues">The values of the properties to match.</param>
/// <returns>An entry for each entity being tracked.</returns>
public virtual IEnumerable<EntityEntry<TEntity>> GetEntries(IEnumerable<string> propertyNames, IEnumerable<object?> propertyValues)
{
Check.NotNull(propertyNames, nameof(propertyNames));
return GetEntries(propertyNames.Select(n => _entityType.GetProperty(n)), propertyValues);
}
/// <summary>
/// Returns an <see cref="EntityEntry{TEntity}" /> for the first entity being tracked by the context where the value of the
/// given property matches the given value. The entry provide access to change tracking information and operations for the entity.
/// </summary>
/// <remarks>
/// <para>
/// This method is frequently used to get the entity with a given non-null foreign key, primary key, or alternate key value.
/// Lookups using a key property like this are more efficient than lookups on other property value.
/// </para>
/// <para>
/// By default, accessing <see cref="DbSet{TEntity}.Local" /> will call <see cref="ChangeTracker.DetectChanges" /> to
/// ensure that all entities searched and returned are up-to-date. Calling this method will not result in another call to
/// <see cref="ChangeTracker.DetectChanges" />. Since this method is commonly used for fast lookups, consider reusing
/// the <see cref="DbSet{TEntity}.Local" /> object for multiple lookups and/or disabling automatic detecting of changes using
/// <see cref="ChangeTracker.AutoDetectChangesEnabled" />.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-change-tracking">EF Core change tracking</see> for more information and examples.
/// </para>
/// </remarks>
/// <param name="property">The property to match.</param>
/// <param name="propertyValue">The value of the property to match.</param>
/// <typeparam name="TProperty">The type of the property value.</typeparam>
/// <returns>An entry for the entity found, or <see langword="null" />.</returns>
public virtual EntityEntry<TEntity>? FindEntry<TProperty>(IProperty property, TProperty? propertyValue)
{
Check.NotNull(property, nameof(property));
var internalEntityEntry = Finder.FindEntry(property, propertyValue);
return internalEntityEntry == null ? null : new EntityEntry<TEntity>(internalEntityEntry);
}
/// <summary>
/// Returns an <see cref="EntityEntry{TEntity}" /> for the first entity being tracked by the context where the value of the
/// given property matches the given values. The entry provide access to change tracking information and operations for the entity.
/// </summary>
/// <remarks>
/// <para>
/// This method is frequently used to get the entity with a given non-null foreign key, primary key, or alternate key values.
/// Lookups using a key property like this are more efficient than lookups on other property value.
/// </para>
/// <para>
/// By default, accessing <see cref="DbSet{TEntity}.Local" /> will call <see cref="ChangeTracker.DetectChanges" /> to
/// ensure that all entities searched and returned are up-to-date. Calling this method will not result in another call to
/// <see cref="ChangeTracker.DetectChanges" />. Since this method is commonly used for fast lookups, consider reusing
/// the <see cref="DbSet{TEntity}.Local" /> object for multiple lookups and/or disabling automatic detecting of changes using
/// <see cref="ChangeTracker.AutoDetectChangesEnabled" />.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-change-tracking">EF Core change tracking</see> for more information and examples.
/// </para>
/// </remarks>
/// <param name="properties">The properties to match.</param>
/// <param name="propertyValues">The values of the properties to match.</param>
/// <returns>An entry for the entity found, or <see langword="null" />.</returns>
public virtual EntityEntry<TEntity>? FindEntry(IEnumerable<IProperty> properties, IEnumerable<object?> propertyValues)
{
Check.NotNull(properties, nameof(properties));
Check.NotNull(propertyValues, nameof(propertyValues));
var internalEntityEntry = Finder.FindEntry(properties, propertyValues);
return internalEntityEntry == null ? null : new EntityEntry<TEntity>(internalEntityEntry);
}
/// <summary>
/// Returns an <see cref="EntityEntry{TEntity}" /> for each entity being tracked by the context where the value of the given
/// property matches the given value. The entries provide access to change tracking information and operations for each entity.
/// </summary>
/// <remarks>
/// <para>
/// This method is frequently used to get the entities with a given non-null foreign key, primary key, or alternate key values.
/// Lookups using a key property like this are more efficient than lookups on other property values.
/// </para>
/// <para>
/// By default, accessing <see cref="DbSet{TEntity}.Local" /> will call <see cref="ChangeTracker.DetectChanges" /> to
/// ensure that all entities searched and returned are up-to-date. Calling this method will not result in another call to
/// <see cref="ChangeTracker.DetectChanges" />. Since this method is commonly used for fast lookups, consider reusing
/// the <see cref="DbSet{TEntity}.Local" /> object for multiple lookups and/or disabling automatic detecting of changes using
/// <see cref="ChangeTracker.AutoDetectChangesEnabled" />.
/// </para>
/// <para>
/// Note that modification of entity state while iterating over the returned enumeration may result in
/// an <see cref="InvalidOperationException" /> indicating that the collection was modified while enumerating.
/// To avoid this, create a defensive copy using <see cref="Enumerable.ToList{TSource}" /> or similar before iterating.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-change-tracking">EF Core change tracking</see> for more information and examples.
/// </para>
/// </remarks>
/// <param name="property">The property to match.</param>
/// <param name="propertyValue">The value of the property to match.</param>
/// <typeparam name="TProperty">The type of the property value.</typeparam>
/// <returns>An entry for each entity being tracked.</returns>
public virtual IEnumerable<EntityEntry<TEntity>> GetEntries<TProperty>(IProperty property, TProperty? propertyValue)
{
Check.NotNull(property, nameof(property));
return Finder.GetEntries(property, propertyValue).Select(e => new EntityEntry<TEntity>(e));
}
/// <summary>
/// Returns an <see cref="EntityEntry" /> for each entity being tracked by the context where the values of the given properties
/// matches the given values. The entries provide access to change tracking information and operations for each entity.
/// </summary>
/// <remarks>
/// <para>
/// This method is frequently used to get the entities with a given non-null foreign key, primary key, or alternate key values.
/// Lookups using a key property like this are more efficient than lookups on other property values.
/// </para>
/// <para>
/// By default, accessing <see cref="DbSet{TEntity}.Local" /> will call <see cref="ChangeTracker.DetectChanges" /> to
/// ensure that all entities searched and returned are up-to-date. Calling this method will not result in another call to
/// <see cref="ChangeTracker.DetectChanges" />. Since this method is commonly used for fast lookups, consider reusing
/// the <see cref="DbSet{TEntity}.Local" /> object for multiple lookups and/or disabling automatic detecting of changes using
/// <see cref="ChangeTracker.AutoDetectChangesEnabled" />.
/// </para>
/// <para>
/// Note that modification of entity state while iterating over the returned enumeration may result in
/// an <see cref="InvalidOperationException" /> indicating that the collection was modified while enumerating.
/// To avoid this, create a defensive copy using <see cref="Enumerable.ToList{TSource}" /> or similar before iterating.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-change-tracking">EF Core change tracking</see> for more information and examples.
/// </para>
/// </remarks>
/// <param name="properties">The the properties to match.</param>
/// <param name="propertyValues">The values of the properties to match.</param>
/// <returns>An entry for each entity being tracked.</returns>
public virtual IEnumerable<EntityEntry<TEntity>> GetEntries(IEnumerable<IProperty> properties, IEnumerable<object?> propertyValues)
{
Check.NotNull(properties, nameof(properties));
Check.NotNull(propertyValues, nameof(propertyValues));
return Finder.GetEntries(properties, propertyValues).Select(e => new EntityEntry<TEntity>(e));
}
private IProperty FindAndValidateProperty<TProperty>(string propertyName)
{
Check.NotEmpty(propertyName, nameof(propertyName));
var property = _entityType.GetProperty(propertyName);
if (property.ClrType != typeof(TProperty))
{
throw new ArgumentException(
CoreStrings.WrongGenericPropertyType(
property.Name,
property.DeclaringEntityType.DisplayName(),
property.ClrType.ShortDisplayName(),
typeof(TProperty).ShortDisplayName()));
}
return property;
}
private IEntityFinder<TEntity> Finder
=> _finder ??= (IEntityFinder<TEntity>)_context.GetDependencies().EntityFinderFactory.Create(_entityType);
}