The exception message is not very helpful but here it is
'observableTeamMember.AssignedTaskEvents.Added' threw an exception of
type 'System.Security.VerificationException'
Method System.Reactive.Linq.Observable.FromEventPattern: type argument
'Bourne.iClean.Planning.Observables.NotifyChildAddedEventArgs`1[T]'
violates the constraint of type parameter 'TEventArgs'.
To give you a brief overview, We have a C#.NET windows service hosted server side which deals with requests from a web application. The service queries a 'planning model' which is a cached version of all the data objects, we maintain this cache in order to speed up responses to queries from the front end.
We are using reactive extensions to keep our data cache up to date whenever certain events occur (like updates to data objects). Some objects in the data cache have child events subscription and when any of these child objects are changed, we update the cache, for example- we have a TeamMemberObject as follows which has some child events associated with it
public interface IObservableTeamMember : IObservableEntity<TeamMember>
{
ChildEvents<TeamCalendar> TeamCalendarEvents {get;}
ChildEvents<AssignedTaskTeamMember> AssignedTaskEvents { get; }
ChildEvents<WorkplaceAssignedTaskTeamMember> WorkplaceAssignedTaskEvents { get; }
}
Whenever any of these child events/objects are updated, we update our cache using the following code. The block below in where I get the security exception
observableTeamMember.AssignedTaskEvents.Added.Subscribe((NotifyChildAddedEventArgs<AssignedTaskTeamMember> e) =>
{
this._addAssignedTaskEntry(e.AddedChild);
});
I am also including partial code for the Child Event Class below
public class ChildEvents<T> : IChildEvents<T>
{
public void Add(T child)
{
var args = new NotifyChildAddedEventArgs<T>(child);
_raiseChildAdded(args);
}
public void Remove(T child)
{
var args = new NotifyChildDeletedEventArgs<T>(child);
_raiseChildDeleted(args);
}
event NotifyChildAddedEventHandler<T> _baseTAdded;
protected void _raiseChildAdded(NotifyChildAddedEventArgs<T> args)
{
var childAdded = _baseTAdded;
if (childAdded != null)
childAdded(this, args);
}
private IObservable<NotifyChildAddedEventArgs<T>> _childAdded;
public IObservable<NotifyChildAddedEventArgs<T>> Added
{
get
{
if (_childAdded == null)
_childAdded = Observable.FromEventPattern<NotifyChildAddedEventHandler<T>, NotifyChildAddedEventArgs<T>>(
(handler) => _baseTAdded += handler,
(handler) => _baseTAdded -= handler)
.Select(e => e.EventArgs);
return _childAdded;
}
}
}
This exception only happens occasionally and we are a team of 3 and it has happened randomly to all of us at some point. We use reactive extensions throughout this project and we are unable to explain the cause. Any help is greatly appreciated.
Thanks
Tapashya
That error suggests that your Bourne.iClean.Planning.Observables.NotifyChildAddedEventArgs<T> does not inherit from System.EventArgs, which is required by Observable.FromEventPattern since it specifically expects you to confirm to the standard .NET event pattern.
Related
When my user adds an appointment, he wants to have 2 things happen:
Send an acknowledgement of the appointment to the customer's cell phone, with information about the appointment
24 hours before the appointment, to have a reminder sent out
The main issue is that the event screen displays dates as UTC. This, of course, confuses the customer. So, I need to change the date and format the text message via code.
When I first did this, there was no way to call a custom method from a business event -- so I actually send the message up to Twilio, catch that with a Webhook that sends it into my custom DLL. That massages the message, making everything look right, and then sends it back through Twilio to the customer.
But this is costly (2 messages sent and 1 received for every event) and needlessly complicated. I want to simplify it now, because I have been told there is added functionality in Business events now that allows a call out into custom code. Is this true?
I was told that this would be available starting in 2020 R2. I am looking for it in the docs and training classes, but I can't see anywhere that this is possible.
How do I call custom code from a business event? Can I set up a subscriber that is in a custom DLL?
Is there something that describes this process somewhere? Or did this never make it into the product?
If you're looking to implement a custom Business Event subscriber (coded in your own dll), I know that is possible in 2021 R1.
There are basic instructions in the Release Notes for Developers starting on page 15.
The upshot is you need to reference the PX.BusinessProcess.dll and implement the PX.BusinessProcess.Subscribers.ActionHandlers.IEventAction interface and either of the PX.BusinessProcess.Subscribers.Factories.IBPSubscriberActionHandlerFactory or PX.BusinessProcess.Subscribers.Factories.IBPSubscriberActionHandlerFactoryWithCreateAction interfaces.
Here is an example blog from crestwood to use business events to create an import scenario: https://www.crestwood.com/2020/05/19/using-business-events-to-create-transactions-employee-birthday-checks/
The gist of it would be to create the generic inquiry to monitor. Next, you would create a business event that ties to the generic inquiry. Go under subscribers, select Create New Subscriber, and then name it. It will load the import scenario, and attach the provider to the event.
For provider object in the business event, you can fill from Results or Previous Results.
This is based on the aborted shipments in sales demo, but it shows my custom action after matching shipment number.
once saved, you can see your business event shows up as a subscriber:
Just for the record, the code I was looking for I was in the Acumatica Help files, as TTook suggested. The reason I am publishing more on this is that the answer in help files aren't very reliable (sometimes) down the road, and I wanted a complete version of the code here -- including all of the using/includes. So, here it is:
This creates an event and a subscriber to respond to a Business Event. The code shows writing the screen data to a text file.
using System;
using System.Collections.Generic;
using System.Linq;
using PX.BusinessProcess.Subscribers.ActionHandlers;
using PX.BusinessProcess.Subscribers.Factories;
using PX.BusinessProcess.Event;
using PX.BusinessProcess.DAC;
using PX.BusinessProcess.UI;
using System.Threading;
using PX.Data;
using PX.Common;
using PX.SM;
using System.IO;
using PX.Data.Wiki.Parser;
using PX.PushNotifications;
namespace CustomSubscriber
{
//The custom subscriber that the system executes once the business event
//has occurred
public class CustomSMSEventAction : IEventAction
{
//The GUID that identifies a subscriber
public Guid Id { get; set; }
//The name of the subscriber of the custom type
public string Name { get; protected set; }
//The notification template
private readonly Notification _notificationTemplate;
//The method that writes the body of the notification to a text file
//once the business event has occurred
public void Process(MatchedRow[] eventRows, CancellationToken cancellation)
{
using (StreamWriter file =
new StreamWriter(#"C:\tmp\EventRows.txt"))
{
var graph = PXGenericInqGrph.CreateInstance(
_notificationTemplate.ScreenID);
var parameters = #eventRows.Select(
r => Tuple.Create<IDictionary<string, object>,
IDictionary<string, object>>(
r.NewRow?.ToDictionary(c => c.Key.FieldName, c => c.Value),
r.OldRow?.ToDictionary(c => c.Key.FieldName,
c => (c.Value as ValueWithInternal)?.ExternalValue ??
c.Value))).ToArray();
var body = PXTemplateContentParser.ScriptInstance.Process(
_notificationTemplate.Body, parameters, graph, null);
file.WriteLine(body);
}
}
//The CustomEventAction constructor
public CustomSMSEventAction(Guid id, Notification notification)
{
Id = id;
Name = notification.Name;
_notificationTemplate = notification;
}
}
//The class that creates and executes the custom subscriber
class CustomSubscriberHandlerFactory :
IBPSubscriberActionHandlerFactoryWithCreateAction
{
//The method that creates a subscriber with the specified ID
public IEventAction CreateActionHandler(Guid handlerId,
bool stopOnError, IEventDefinitionsProvider eventDefinitionsProvider)
{
var graph = PXGraph.CreateInstance<PXGraph>();
Notification notification = PXSelect<Notification,
Where<Notification.noteID,
Equal<Required<Notification.noteID>>>>
.Select(graph, handlerId).AsEnumerable().SingleOrDefault();
return new CustomSMSEventAction(handlerId, notification);
}
//The method that retrieves the list of subscribers of the custom type
public IEnumerable<BPHandler> GetHandlers(PXGraph graph)
{
return PXSelect<Notification, Where<Notification.screenID,
Equal<Current<BPEvent.screenID>>,
Or<Current<BPEvent.screenID>, IsNull>>>
.Select(graph).FirstTableItems.Where(c => c != null)
.Select(c => new BPHandler
{
Id = c.NoteID,
Name = c.Name,
Type = LocalizableMessages.CustomNotification
});
}
//The method that performs redirection to the subscriber
public void RedirectToHandler(Guid? handlerId)
{
var notificationMaint =
PXGraph.CreateInstance<SMNotificationMaint>();
notificationMaint.Message.Current =
notificationMaint.Notifications.
Search<Notification.noteID>(handlerId);
PXRedirectHelper.TryRedirect(notificationMaint,
PXRedirectHelper.WindowMode.New);
}
//A string identifier of the subscriber type that is
//exactly four characters long
public string Type
{
get { return "CTTP"; }
}
//A string label of the subscriber type
public string TypeName
{
get { return LocalizableMessages.CustomNotification; }
}
//A string identifier of the action that creates
//a subscriber of the custom type
public string CreateActionName
{
get { return "NewCustomNotification"; }
}
//A string label of the button that creates
//a subscriber of the custom type
public string CreateActionLabel
{
get { return LocalizableMessages.CreateCustomNotification; }
}
//The delegate for the action that creates
//a subscriber of the custom type
public Tuple<PXButtonDelegate, PXEventSubscriberAttribute[]>
getCreateActionDelegate(BusinessProcessEventMaint maintGraph)
{
PXButtonDelegate handler = (PXAdapter adapter) =>
{
if (maintGraph.Events?.Current?.ScreenID == null)
return adapter.Get();
var graph = PXGraph.CreateInstance<SMNotificationMaint>();
var cache = graph.Caches<Notification>();
var notification = (Notification)cache.CreateInstance();
var row = cache.InitNewRow(notification);
row.ScreenID = maintGraph.Events.Current.ScreenID;
cache.Insert(row);
var subscriber = new BPEventSubscriber();
var subscriberRow =
maintGraph.Subscribers.Cache.InitNewRow(subscriber);
subscriberRow.Type = Type;
subscriberRow.HandlerID = row.NoteID;
graph.Caches[typeof(BPEventSubscriber)].Insert(subscriberRow);
PXRedirectHelper.TryRedirect(graph,
PXRedirectHelper.WindowMode.NewWindow);
return adapter.Get();
};
return Tuple.Create(handler,
new PXEventSubscriberAttribute[]
{new PXButtonAttribute {
OnClosingPopup = PXSpecialButtonType.Refresh}});
}
}
//Localizable messages
[PXLocalizable]
public static class LocalizableMessages
{
public const string CustomNotification = "Custom SMS Notification";
public const string CreateCustomNotification = "Custom SMS Notification";
}
}
I've implemented the following pattern by jbogard:
http://lostechies.com/jimmybogard/2014/05/13/a-better-domain-events-pattern/
Imagine the following entity Coupon and event CouponActivatedEvent:
public class Coupon : DomainEntity
{
public virtual User User { get; private set; }
// ...omitted...
public void Activate(User user)
{
if (User != null)
throw new InvalidOperationException("Coupon already activated");
User = user;
Events.Add(new CouponActivatedEvent(this));
}
}
The following event handler CouponActivatedHandler:
public class CouponActivatedHandler : IDomainEventHandler<CouponActivatedEvent>
{
public void Handle(CouponActivatedEvent e)
{
// user gets 5 credits because coupon was activated
for (int i = 0; i < 5; i++)
{
e.Coupon.User.AddCredit(); // raises UserReceivedCreditEvent and CreditCreatedEvent
}
}
}
The following SaveChanges override on DbContext (Entity Framework 6), taken from jbogard's blog post:
public override int SaveChanges()
{
var domainEventEntities = ChangeTracker.Entries<IDomainEntity>()
.Select(po => po.Entity)
.Where(po => po.Events.Any())
.ToArray();
foreach (var entity in domainEventEntities)
{
var events = entity.Events.ToArray();
entity.Events.Clear();
foreach (var domainEvent in events)
{
_dispatcher.Dispatch(domainEvent);
}
}
return base.SaveChanges();
}
If we now activate a coupon, this will raise the CouponActivatedEvent. When calling SaveChanges, the handler will be executed, and UserReceivedCreditEvent and CreditCreatedEvent will be raised. They won't be handled though. Am I misunderstanding the pattern? Or is the SaveChanges override not appropriate?
I've considered creating a loop that will repeat until no new events are raised before moving on to base.SaveChanges();... but I'm worried that I will create endless loops accidentally. Like so:
public override int SaveChanges()
{
do
{
var domainEventEntities = ChangeTracker.Entries<IDomainEntity>()
.Select(po => po.Entity)
.Where(po => po.Events.Any())
.ToArray();
foreach (var entity in domainEventEntities)
{
var events = entity.Events.ToArray();
entity.Events.Clear();
foreach (var domainEvent in events)
{
_dispatcher.Dispatch(domainEvent);
}
}
}
while (ChangeTracker.Entries<IDomainEntity>().Any(po => po.Entity.Events.Any()));
return base.SaveChanges();
}
Yeah, you misunderstood things. A Domain Event is not like a C# event, it's a message about what changed in the Domain. One rule is that an event is something that happened, it's in the past. Thus, an event handler simply can't (it shouldn't) change the event, it's like changing the past.
You CouponActivatedHandler should at least get the User entity form a repository then update it with the number of credits, then save it, then publish an UserCreditsAdded event. Even better, the handler should just create and send a command AddCreditsToUser .
With Domain Events pattern, an operation is simply a chain of command->event-> command-> event etc. The event handler is usually a service (or a method in one) which takes care only of that bit. The sender of the event won't know anything about the handler and vice-versa.
As a thumb rule, a Domain object generates an event when its state has changed. A service will take those events then send them to a service bus (for a simple app, a DI Container is enough) which will publish them to be handled by anyone interested (this means services from the local app or other apps subscribed to that bus).
Never forget that Domain Events is a high level pattern used when doing the architecture of an app, it's not just another way to do object events (like C#'s events).
The linked implementation of domain events does not handle potentially nested events raised by event handlers. If you read through the authors comments, he himself once mentioned that he "tries to avoid it".
IMO, what you implemented with the loop makes sense. But if you are not certain about getting into an indefinite loop, I suggest you implement a circuit breaker pattern there.
Let us say I have this ReactiveUI view model structure, with Model being some arbitrary model type.
class ViewModel : ReactiveObject
{
private readonly ReactiveList<Model> _models;
public IReactiveList<Model> Models { get { return _models; } }
public IReactiveCommand LoadModels { get; private set; }
public bool LoadingModels { get; private set; } // Notifies;
}
And these models come from a task-based asynchronous API modeled by this interface:
interface ITaskApi
{
Task<IEnumerable<Model>> GetModelsAsync();
}
After taking a look at how Octokit.net's reactive library was written, I wrote the following class to adapt the API to a reactive world:
class ObservableApi
{
private readonly ITaskApi _taskApi;
public IObservable<Model> GetModels() {
return _taskApi.GetModelsAsync().ToObservable().SelectMany(c => c);
}
}
And now, I have written the following ways to implement loading of models inside of the the LoadModels command, in the ViewModel() constructor:
// In both cases, we want the command to be disabled when loading:
LoadModels = new ReactiveCommand(this.WhenAny(x => x.LoadingModels, x => !x.Value));
// First method, with the Observable API;
LoadModels.Subscribe(_ =>
{
LoadingModels = true;
_models.Clear();
observableClient.GetModels().ObserveOnDispatcher()
.Subscribe(
m => _models.Add(m),
onCompleted: () => { LoadingModels = false; });
});
// Second method, with the task API;
LoadModels.Subscribe(async _ =>
{
LoadingModels = true;
try {
var loadedModels = await taskClient.GetModelsAsync();
_models.Clear();
_models.AddRange(loadedModels);
} catch (Exception ex) {
RxApp.DefaultExceptionHandler.OnNext(ex);
} finally {
LoadingModels = false;
}
});
While I think that both ways will do the job, I have the following misgivings:
In the first sample, should I dispose the inner subscription or will that be done when the inner observable completes or errors?
In the second sample, I know that exceptions raised in the GetModelsAsync method will be swallowed.
What is the "best", most idiomatic way to populate a ReactiveList<T> from an asynchronous enumeration (either IObservable<T> or Task<IEnumerable<T>> (or would IObservable<IEnumerable<T>> be better?))?
After taking a look at how Octokit.net's reactive library was written, I wrote the following class to adapt the API to a reactive world:
While you sometimes want to do this (i.e. flatten the collection), it's usually more convenient to just leave it as IEnumerable<T> unless you then plan to invoke an async method on each item in the list. Since we just want to stuff everything in a List, we don't want to do this. Just leave it as Task<T>
In the first sample, should I dispose the inner subscription or will that be done when the inner observable completes or errors?
Any time you have a Subscribe inside another Subscribe, you probably instead want the SelectMany operator. However, there is a better way to do what you're trying to do, you should check out this docs article for more info.
So, here's how I would write your code:
// In both cases, we want the command to be disabled when loading:
LoadModels = new ReactiveCommand();
LoadModels.RegisterAsyncTask(_ => taskClient.GetModelsAsync())
.Subscribe(items =>
{
// This Using makes it so the UI only looks at the collection
// once we're totally done updating it, since we're basically
// changing it completely.
using (_models.SuppressChangeNotifications())
{
_models.Clear();
_models.AddRange(items);
}
});
LoadModels.ThrownExceptions
.Subscribe(ex => Console.WriteLine("GetModelsAsync blew up: " + ex.ToString());
// NB: _loadingModels is an ObservableAsPropertyHelper<bool>
LoadModels.IsExecuting
.ToProperty(this, x => x.LoadingModels, out _loadingModels);
I have been looking into using Rx in an MVVM framework. The idea is to use 'live' LINQ queries over in-memory datasets to project data into View Models to bind with.
Previously this has been possible with the use of INotifyPropertyChanged/INotifyCollectionChanged and an open source library called CLINQ. The potential with Rx and IObservable is to move to a much more declarative ViewModel using Subject classes to propagate changed events from the source model through to the View. A conversion from IObservable to the regular databinding interfaces would be needed for the last step.
The problem is that Rx doesn't seem to support the notification that an entity has been removed from the stream. Example below.
The code shows a POCO which uses BehaviorSubject class for the field state. The code goes onto to create a collection of these entities and use Concat to merge the filter streams together. This means that any changes to the POCOs are reported to a single stream.
A filter for this stream is setup to filter for Rating==0. The subscription simply outputs the result to the debug window when an even occurs.
Settings Rating=0 on any element will trigger the event. But setting Rating back to 5 will not see any events.
In the case of CLINQ the output of the query will support INotifyCollectionChanged - so that items added and removed from the query result will fire the correct event to indicate the query result has changed (an item added or removed).
The only way I can think of address this is to set-up two streams with oppossite (dual) queries. An item added to the opposite stream implies removal from the resultset. Failing that, I could just use FromEvent and not make any of the entity models observable - which makes Rx more of just an Event Aggregator. Any pointers?
using System;
using System.ComponentModel;
using System.Linq;
using System.Collections.Generic;
namespace RxTest
{
public class TestEntity : Subject<TestEntity>, INotifyPropertyChanged
{
public IObservable<string> FileObservable { get; set; }
public IObservable<int> RatingObservable { get; set; }
public string File
{
get { return FileObservable.First(); }
set { (FileObservable as IObserver<string>).OnNext(value); }
}
public int Rating
{
get { return RatingObservable.First(); }
set { (RatingObservable as IObserver<int>).OnNext(value); }
}
public event PropertyChangedEventHandler PropertyChanged;
public TestEntity()
{
this.FileObservable = new BehaviorSubject<string>(string.Empty);
this.RatingObservable = new BehaviorSubject<int>(0);
this.FileObservable.Subscribe(f => { OnNotifyPropertyChanged("File"); });
this.RatingObservable.Subscribe(f => { OnNotifyPropertyChanged("Rating"); });
}
private void OnNotifyPropertyChanged(string property)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(property));
// update the class Observable
OnNext(this);
}
}
public class TestModel
{
private List<TestEntity> collection { get; set; }
private IDisposable sub;
public TestModel()
{
this.collection = new List<TestEntity>() {
new TestEntity() { File = "MySong.mp3", Rating = 5 },
new TestEntity() { File = "Heart.mp3", Rating = 5 },
new TestEntity() { File = "KarmaPolice.mp3", Rating = 5 }};
var observableCollection = Observable.Concat<TestEntity>(this.collection.Cast<IObservable<TestEntity>>());
var filteredCollection = from entity in observableCollection
where entity.Rating==0
select entity;
this.sub = filteredCollection.Subscribe(entity =>
{
System.Diagnostics.Debug.WriteLine("Added :" + entity.File);
}
);
this.collection[0].Rating = 0;
this.collection[0].Rating = 5;
}
};
}
Actually I found the Reactive-UI library helpful for this (available in NuGet).
This library includes special IObservable subjects for collections and the facility to create one of these 'ReactiveCollections' over a a traditional INCC collection.
Through this I have streams for new, removed items and changing items in the collection. I then use a Zip to merge the streams together and modify a target ViewModel observable collection. This provides a live projection based on a query on the source model.
The following code solved the problem (this code would be even simpler, but there are some problems with the Silverlight version of Reactive-UI that needed workarounds). The code fires collection changed events by simply adjusting the value of 'Rating' on one of the collection elements:
using System;
using System.ComponentModel;
using System.Linq;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using ReactiveUI;
namespace RxTest
{
public class TestEntity : ReactiveObject, INotifyPropertyChanged, INotifyPropertyChanging
{
public string _File;
public int _Rating = 0;
public string File
{
get { return _File; }
set { this.RaiseAndSetIfChanged(x => x.File, value); }
}
public int Rating
{
get { return this._Rating; }
set { this.RaiseAndSetIfChanged(x => x.Rating, value); }
}
public TestEntity()
{
}
}
public class TestModel
{
private IEnumerable<TestEntity> collection { get; set; }
private IDisposable sub;
public TestModel()
{
this.collection = new ObservableCollection<TestEntity>() {
new TestEntity() { File = "MySong.mp3", Rating = 5 },
new TestEntity() { File = "Heart.mp3", Rating = 5 },
new TestEntity() { File = "KarmaPolice.mp3", Rating = 5 }};
var filter = new Func<int, bool>( Rating => (Rating == 0));
var target = new ObservableCollection<TestEntity>();
target.CollectionChanged += new NotifyCollectionChangedEventHandler(target_CollectionChanged);
var react = new ReactiveCollection<TestEntity>(this.collection);
react.ChangeTrackingEnabled = true;
// update the target projection collection if an item is added
react.ItemsAdded.Subscribe( v => { if (filter.Invoke(v.Rating)) target.Add(v); } );
// update the target projection collection if an item is removed (and it was in the target)
react.ItemsRemoved.Subscribe(v => { if (filter.Invoke(v.Rating) && target.Contains(v)) target.Remove(v); });
// track items changed in the collection. Filter only if the property "Rating" changes
var ratingChangingStream = react.ItemChanging.Where(i => i.PropertyName == "Rating").Select(i => new { Rating = i.Sender.Rating, Entity = i.Sender });
var ratingChangedStream = react.ItemChanged.Where(i => i.PropertyName == "Rating").Select(i => new { Rating = i.Sender.Rating, Entity = i.Sender });
// pair the two streams together for before and after the entity has changed. Make changes to the target
Observable.Zip(ratingChangingStream,ratingChangedStream,
(changingItem, changedItem) => new { ChangingRating=(int)changingItem.Rating, ChangedRating=(int)changedItem.Rating, Entity=changedItem.Entity})
.Subscribe(v => {
if (filter.Invoke(v.ChangingRating) && (!filter.Invoke(v.ChangedRating))) target.Remove(v.Entity);
if ((!filter.Invoke(v.ChangingRating)) && filter.Invoke(v.ChangedRating)) target.Add(v.Entity);
});
// should fire CollectionChanged Add in the target view model collection
this.collection.ElementAt(0).Rating = 0;
// should fire CollectionChanged Remove in the target view model collection
this.collection.ElementAt(0).Rating = 5;
}
void target_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine(e.Action);
}
}
}
What's wrong with using an ObservableCollection<T>? Rx is a very easy framework to overuse; I find that if you find yourself fighting against the basic premise of an asynchronous stream, you probably shouldn't be using Rx for that particular problem.
All of the INPC implementations that I've ever seen can be best labeled as shortcuts or hacks. However, I can't really fault the developers since the INPC mechanism that the .NET creators choose to support is terrible. With that said, I have recently discovered, in my opinion, the best implementation of INPC, and the best compliment to any MVVM framework around. In addition to providing dozens of extremely helpful functions and extensions, it also sports the most elegant INPC pattern I've seen. It somewhat resembles the ReactiveUI framework, but it wasn't designed to be a comprehensive MVVM platform. To create a ViewModel that supports INPC, it requires no base class, or interfaces, yes is still able to support complete change notification and Two Way binding, and best of all, all of your properties can be automatic!
It does NOT use a utility such as PostSharp or NotifyPropertyWeaver, but is built around the Reactive Extensions framework. The name of this new framework is ReactiveProperty. I suggest visiting the project site (on codeplex), and pulling down the NuGet package. Also, looks through the source code, because it's really a treat.
I'm in no way associated with the developer, and the project is still fairly new. I'm just really enthusiastic about the features it offers.
To my mind that is not a suitable usage of Rx. An Rx Observable is a stream of 'events' that you can subscribe to. You can react to these events in your View Model, for example adding them to an ObservableCollection which is bound to your view. However, an Observable cannot be used to represent a fixed set of items which you add / remove items from.
The problem is that you are looking at the notifications from a List of TestEntitys, not from the TestEntity themselves. So you see adds, but not changes in any TestEntity. To see this comment out:
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(property));
and you'll see that the program runs the same! Your notifications in your TestEntity's are not wired up to anything. As stated by others, using an ObservableCollection will add this wiring for you.
Are there any ways, besides throwing exceptions, that one can go about using the partial validation methods in LINQ to SQL to cancel the insert of a record?
I can understand that you don't want to throw an exception directly after a property is set with an invalid value. This approach makes it difficult to communicate correctly to the user what actually is wrong. However, I think it's better to keep away from using those partial validation methods. IMO you want to throw an exception when your model is invalid, but only just before you're persisting your model to the database.
I advise you to use a validation framework and integrate it with your LINQ to SQL DataContext class. Here's an example of how to do this with The Enterprise Library Validation Application Block, but the concept will work for every validation framework you pick:
public partial class NorthwindDataContext
{
public override void SubmitChanges(ConflictMode failureMode)
{
ValidationResult[] = this.Validate();
if (invalidResults.Length > 0)
{
// You should define this exception type
throw new ValidationException(invalidResults);
}
base.SubmitChanges(failureMode);
}
private ValidationResult[] Validate()
{
// here we use the Validation Application Block.
return invalidResults = (
from entity in this.GetChangedEntities()
let type = entity.GetType()
let validator = ValidationFactory.CreateValidator(type)
let results = validator.Validate(entity)
where !results.IsValid
from result in results
select result).ToArray();
}
private IEnumerable<object> GetChangedEntities()
{
ChangeSet changes = this.GetChangeSet();
return changes.Inserts.Concat(changes.Updates);
}
}
[Serializable]
public class ValidationException : Exception
{
public ValidationException(IEnumerable<ValidationResult> results)
: base("There are validation errors.")
{
this.Results = new ReadOnlyCollection<ValidationResult>(
results.ToArray());
}
public ReadOnlyCollection<ValidationResult> Results
{
get; private set;
}
}
There are several validation frameworks available, such as DataAnnotations and
the Enterprise Library Validation Application Block (VAB). VAB is very suited for doing this. With LINQ to SQL your entities are generated, so you'll need to use the configuration based approach that VAB offers (don’t try decorating your entities with attributes). By overriding the SubmitChanges method you can make sure the validation gets triggered just before entities are persisted. My SO answers here and here contain useful information about using VAB.
I've written a few interesting articles about integrating VAB with LINQ to SQL here and here. The nice thing about LINQ to SQL (compared to Entity Framework 1.0) is that a lot of useful metadata is generated. When combining this with VAB you can use this metadata to validate your model, without having to hook up every validation manually. Especially validations as maximum string length and not null can be extracted from the model. Read here how to do this.
VAB to the rescue!
Ultimately this indicates that at you last line of defence (before any database constraints, at least) your data was invalid. If you want to do something other than scream loudly, then perhaps verify the data (via any of a multitude of approaches) before adding it to the insert list.
As an additional thought, you could try overriding SubmitChanges (on the data-context); obtain the change-set, verify the inserts and remove (delete-on-submit, which IIRC checks the insert list and drops them) any that you've decided were mistakes. Then call the base.SubmitChanges. But to me this is a bit backwards.
To illustrate, this only does a single insert (not two as requested), but I don't like this approach. At all. As long as we're clear ;-p
namespace ConsoleApplication1 {
partial class DataClasses1DataContext { // extends the generated data-context
public override void SubmitChanges(
System.Data.Linq.ConflictMode failureMode) {
var delta = GetChangeSet();
foreach (var item in delta.Inserts.OfType<IEntityCheck>()) {
if (!item.IsValid()) {
GetTable(item.GetType()).DeleteOnSubmit(item);
}
}
base.SubmitChanges(failureMode);
}
}
public interface IEntityCheck { // our custom basic validation interface
bool IsValid();
}
partial class SomeTable : IEntityCheck { // extends the generated entity
public bool IsValid() { return this.Val.StartsWith("d"); }
}
static class Program {
static void Main() {
using (var ctx = new DataClasses1DataContext()) {
ctx.Log = Console.Out; // report what it does
ctx.SomeTables.InsertOnSubmit(new SomeTable { Val = "abc" });
ctx.SomeTables.InsertOnSubmit(new SomeTable { Val = "def" });
ctx.SubmitChanges();
}
}
}
}