I have a saga data class with one property marked by Unique attribute. However, this didn't prevent NServiceBus from creating several sagas with identical values in this field.
Here is my data class:
public class ModuleAliveSagaData : ContainSagaData
{
[Unique]
public string ModuleId { get; set; }
public string Endpoint { get; set; }
public string Module { get; set; }
public DateTime LastCheck { get; set; }
public bool Warning { get; set; }
public bool Error { get; set; }
}
Here is the mapping:
public override void ConfigureHowToFindSaga()
{
ConfigureMapping<ModuleAliveMessage>(m => m.Id).ToSaga(s => s.ModuleId);
}
Here is how data gets its values:
public void Handle(ModuleStartedMessage message)
{
Log.InfoFormat("Module {0} started on {1} at {2}", message.ModuleName, message.Endpoint, message.Timestamp);
Data.ModuleId = message.Id;
Data.Endpoint = message.Endpoint;
Data.Module = message.ModuleName;
Data.LastCheck = DateTime.Now;
Data.Warning = false;
Bus.SendLocal(new SendNotification
{
Subject = string.Format("Module {0} is online at {1}", Data.Module, Data.Endpoint)
});
RequestTimeout<ModuleCheckTimeout>(TimeSpan.FromMinutes(5));
Bus.Publish(new ModuleActivated
{
Endpoint = message.Endpoint,
Module = message.ModuleName
});
}
And here is what I see in the saga persistence table (Azure table storage):
Does it suppose to work like this or may be I am missing something?
Yves wrote this in comments, basically it is the proper answer:
Azure storage cannot check for uniquess besides the partitionkey/rowkey pair, so that attribute is ignored. If you need uniqueness you will have to consider another storage techology. PS: this is a known limitation of the underlying storage: http://github.com/Particular/NServiceBus.Azure/issues/21
Related
I've a situation where entity framework core (2.0) is performing additional work to a parent table when I update a row in a child table. I've pin-pointed the cause to a value not being set in the unflattened object tree produced by AutoMapper (I'm not saying it is an error in AutoMapper; it's probably more to do with my code).
I'm using ASP.NET Core 2.0, C#, EF Core 2.0 and AutoMapper for the API development side. The database already exists and the EF classes scaffolded from it.
To keep it short, the child table is Note and the parent table is NoteType. The EF classes (extraneous columns removed) are as follows :
//Entity classes
public partial class Note
{
public int NoteBookId { get; set; }
public short NoteSeqNo { get; set; }
public short NoteTypeId { get; set; }
public string NoteText { get; set; }
public NoteBook NoteBook { get; set; }
public NoteType NoteType { get; set; }
}
public partial class NoteType
{
public NoteType() { Note = new HashSet<Note>(); }
public short NoteTypeId { get; set; }
public string NoteTypeDesc { get; set; }
public ICollection<Note> Note { get; set; }
}
//DTO class
public class NoteDto
{
public int NoteBookId { get; set; }
public short NoteSeqNo { get; set; }
public short NoteTypeId { get; set; }
public string NoteTypeNoteTypeDesc { get; set; }
public string NoteText { get; set; }
}
public class NoteTypeDto
{
public short NoteTypeId { get; set; }
public string NoteTypeDesc { get; set; }
}
(NoteBookId + NoteSeqNo) is Note's primary key.
NoteTypeId is the NoteType's primary key.
Configuration
This is the AutoMapper configuration:
// config in startup.cs
config.CreateMap<Note,NoteDto>().ReverseMap();
config.CreateMap<NoteType,NoteTypeDto>().ReverseMap();
Read the data
As a result of data retrieval I get the expected result and the parent note type description is populated.
// EF get note in repository
return await _dbcontext.Note
.Where(n => n.NoteId == noteId && n.NoteSeqNo == noteSeqNo)
.Include(n => n.NoteType)
.FirstOrDefaultAsync();
// Get note function in API controller
Note note = await _repository.GetNoteAsync(id, seq);
NoteDto noteDto = Mapper.Map<NoteDto>(note);
Example JSON result:
{
"noteBookId": 29,
"noteSeqNo": 19,
"noteTypeId": 18,
"noteTypenoteTypeDesc": "ABCDE",
"noteText": "My notes here."
}
Update the data
When the process is reversed during an update, the API controller maps the dto to the entity
Mapper.Map<Note>(noteDto)
Then when it is passed to EF by the repository code, EF tries to add a NoteType row with id 0. The unflattened object tree looks like this:
Note
NoteBookId = 29
NoteSeqNo = 19
NoteTypeId = 18
NoteTypeNoteTypeDesc = "ABCDE"
NoteText = "My notes updated."
NoteType.NoteTypeDesc = "ABCDE"
NoteType.NoteTypeId = 0
The parent id column (NoteType.NoteTypeId) value is 0 and is not assigned the value of 18 which is what I expected.
(During debugging I manually set NoteType.NoteTypeId to 18 to ensure EF did nothing with it).
To work around this at the moment I nullify the NoteType in the Note in the repository code.
Should I expected AutoMapper to populate all the parent properties with setters or have I missed some configuration? Perhaps there is a glaring flaw in my approach?
When AutoMapper reverses the mapping, it has to collect all information for nested objects from the flat object. Your DTO only carries a value for the mapping NoteType -> NoteTypeDesc. Not for NoteType -> NoteTypeId, so AM really doesn't have any idea where to get that value from.
If you want to rely on flattening only, the only way to change that is to add a flattened NoteTypeId to the DTO besides the unflattened one:
public class NoteDto
{
public int NoteBookId { get; set; }
public short NoteSeqNo { get; set; }
public short NoteTypeId { get; set; } // Not flattened
public short NoteTypeNoteTypeId { get; set; } // Flattened
public string NoteTypeNoteTypeDesc { get; set; }
public string NoteText { get; set; }
}
The alternative is to add this to your mapping:
config.CreateMap<Note, NoteDto>()
.ForMember(dest => dest.NoteTypeId,
e => e.MapFrom(src => src.NoteType.NoteTypeId))
.ReverseMap();
MapFrom-s (including the default unflattening) are reversed now. You can drop ReverseMap and create the maps, ignore Note.NoteType or ignore the offending path, Note.NoteType.NoteTypeDesc.
I'm new to RavenDB and I'm struggling with this simple (i guess) issue.
I have a Subscriber with a collection of Subscriptions. And I want to make search by Subscription's fields, and return related Subscriber.
Here are simplified class examples:
public class Subscriber
{
public string Email { get; set; }
public string Name { get; set; }
public string Phone { get; set; }
public List<Subscription> Subscriptions { get; set; }
}
public class Subscription
{
public Guid Id { get; set; }
public string EventType { get; set; }
}
I've tried to make an index, as it is said in RavenDB docs:
public class Subscriber_BySubscription : AbstractIndexCreationTask<Subscriber>
{
public Subscriber_BySubscription()
{
Map = subscribers => from subscriber in subscribers
from subscription in subscriber.Subscriptions
select new
{
subscription.EventType,
subscription.QueueName
};
}
}
But I'm not sure that this is what I need, since query by collection using Select and Contains doesn't work. Moreover, the code looks so ugly that I feel that this is not the way how it should be.
So, I'd like to query Subscriptions by EventType, and have corresponding Subscriber as a result. In LINQ it would look like this: subscribers.Where(x => x.Subscriptions.Select(c => c.EventType).Contains(myEventType))
Managed to do it. Here is the right index:
public class Subscriber_BySubscription : AbstractIndexCreationTask<Subscriber>
{
public class Result
{
public string EventType { get; set; }
}
public Subscriber_BySubscription()
{
Map = subscribers => from subscriber in subscribers
from subscription in subscriber.Subscriptions
select new
{
subscription.EventType
};
}
}
And that's how it should be used:
var results = uow.Session
.Query<Subscriber_BySubscription.Result, Subscriber_BySubscription>()
.Where(x => x.EventType == eventType)
.OfType<Subscriber>()
.ToList();
I have two saga's namely a client saga and a client billing saga.
public class ClientSagaState:IContainSagaData
{
#region NserviceBus
public Guid Id { get; set; }
public string Originator { get; set; }
public string OriginalMessageId { get; set; }
#endregion
public Guid ClientRef { get; set; }
public ClientMessage ClientChangeMessage { get; set; }
public ClientContactChangeMessage ClientContactChange { get; set; }
}
public class ClientBillingSagaState:IContainSagaData
{
#region NserviceBus
public Guid Id { get; set; }
public string Originator { get; set; }
public string OriginalMessageId { get; set; }
#endregion
public Guid ClientRef { get; set; }
public Guid FunderRef { get; set; }
public Guid ClientBillingRef { get; set; }
public ClientBillingMessage ClientBillingMessage { get; set; }
}
public class ClientSaga:Saga<ClientSagaState>,
IAmStartedByMessages<ClientChangeMessage>,
IAmStartedByMessages<ClientContactChangeMessage>
{
public override void ConfigureHowToFindSaga()
{
ConfigureMapping<ClientChangeMessage>(s => s.ClientRef, m => m.EntityRef);
ConfigureMapping<ClientContactChangeMessage>(s => s.ClientRef, m => m.PrimaryEntityRef);
}
public void Handle(ServiceUserChangeMessage message)
{
if (BusRefTranslator.GetLocalRef(EntityTranslationNames.ClientChange, message.EntityRef.Value) != null)
{
GetHandler<ClientChangeMessage>().Handle(message);
CompleteTheSaga();
return;
}
HandleServiceUserChangeAndDependencies(message);
//MarkAsComplete();
CompleteTheSaga();
}
}
public class ClientBillingSaga:Saga<ClientBillingSagaState>
,IHandleMessages<ClientChangeMessage>,
IAmStartedByMessages<ClientBillingMessage>,
IHandleMessages<FunderChangeMessage>
{
public override void ConfigureHowToFindSaga()
{
ConfigureMapping<ClientChangeMessage>(s => s.ClientRef, m => m.EntityRef);
ConfigureMapping<FunderChangeMessage>(s => s.FunderRef, m => m.EntityRef);
ConfigureMapping<ClientBillingMessage>(s => s.ClientBillingRef, m => m.PrimaryEntityRef);
}
public void Handle(ClientChangeMessage message)
{
var state = this.Data;
if (state.ClientBillingMessage != null)
{
Handle(state.ClientBillingMessage);
}
}
public void Handle(CareSysInvoiceLineInsertMessage message)
{
//First check for the funder
//If funder is not there store the message in saga
//If funder is there then check for client
//If client is not there then put the message in saga
// if funder and client are there then execute the message delete the saga
}
}
Here is the scenario:
1)If i receive a ClientBillingMessage whose client and funder are not there , i store this message in saga.
2)If now the funder message comes then it gets executed , but the saga still persists as it is waiting for client message
3)Now if the client message comes it runs the ClientBillingSaga's client message handler first hence the already existing ClientBillingSaga still persists and after that it goes and executes the handler inside the ClientSaga for Client message.
My question: Is there any way i can order the execution of these two saga. I found ISpecifyMessageHandlerOrdering interface which deals with handling of messages but i don't think i can use it for saga.
Sagas are meant to help with synchronising events over time.
You can collect the data you need form the messages you handled in your saga state and once you you have handled all then proceed to send a command/publish an event so the business logic could be processed by a handler.
Note: The actual work (business logic and domain data manipulation) should be done in a separate handler (to comply with SRP and unit of work).
hope this helps
I think you should not be ordering the execution of two Sagas. Saga should be autonomous acting purely on the messages it receives, has an internal state and either sends out messages or complete. If you really want to interact with another Saga, you should be using messages(commands/events).
We're using RavenDB saga storage, but the saga data isn't being persisted after the starting message handler. I only see a handful of subscription documents in the database. I'm not sure what to check next. Ideas?
I have a saga:
public class BuyerWaitingOnDocumentsDistributor :
Saga<BuyerDocumentDistributorData>,
IAmStartedByMessages<DocumentVersionRequiresBuyerSignature>
{
public void Handle(DocumentVersionRequiresEmployeeSignature message)
{
Data.DocumentVersionId = message.DocumentVersionId.Value;
// Business logic goes here
}
// Other handlers & methods here
}
and saga entity:
public class BuyerDocumentDistributorData : IContainSagaData
{
public virtual Guid Id { get; set; }
public virtual string Originator { get; set; }
public virtual string OriginalMessageId { get; set; }
public virtual Guid DocumentVersionId { get; set; }
public virtual EmployeeId[] AuthorizedToSign { get; set; }
}
and saga finder:
public class BuyerWaitingOnDocumentsDistributorSagaFinder :
IFindSagas<BuyerDocumentDistributorData>.Using<DocumentVersionRequiresBuyerSignature>,
IFindSagas<BuyerDocumentDistributorData>.Using<DocumentVersionSignedByBuyer>,
IFindSagas<BuyerDocumentDistributorData>.Using<DocumentVersionNoLongerRequiresSignature>
{
private static readonly ILog Log = LogManager.GetLogger(typeof (BuyerWaitingOnDocumentsDistributorSagaFinder));
public ISagaPersister Persister { get; set; }
public BuyerDocumentDistributorData FindBy(DocumentVersionRequiresBuyerSignature message)
{
return Persister.Get<BuyerDocumentDistributorData>("DocumentVersionId", message.DocumentVersionId.Value);
}
public BuyerDocumentDistributorData FindBy(DocumentVersionSignedByBuyer message)
{
return Persister.Get<BuyerDocumentDistributorData>("DocumentVersionId", message.DocumentVersionId.Value);
}
public BuyerDocumentDistributorData FindBy(DocumentVersionNoLongerRequiresSignature message)
{
return Persister.Get<BuyerDocumentDistributorData>("DocumentVersionId", message.DocumentVersionId.Value);
}
}
I've also asked this question on the NServiceBus Yahoo group: http://tech.groups.yahoo.com/group/nservicebus/message/13265
You did not give your endpointconfig, so I'm unsure which container you are using.
We experience the same (saga not getting persisted) when using the NinjectBuilder. Using the DefaultBuilder works ok.
I'm learning WCF, and tried to make a small service that exposes a Project and its tasks (the standard Entity Framework hello world).
The class structure is the following:
public class Project
{
public int ProjectId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime CreationDate { get; set; }
public virtual ICollection<Task> Tasks { get; set; }
}
public class Task
{
public int TaskId { get; set; }
public string Title { get; set; }
public virtual Project RelatedProject { get; set; }
}
The DB context comes after:
public class ProjectContext : DbContext
{
public DbSet<Project> Projects { get; set; }
public DbSet<Task> Tasks { get; set; }
}
Finally, the service endpoint:
public IEnumerable<Project> getProjects()
{
ProjectContext p = new ProjectContext();
return p.Projects.AsEnumerable();
}
The problem is that this model will throw a System.ServiceModel.CommunicationException, but, If I remove the virtual properties from the model, It would work, but I would loose the entity framework links between Project and Task.
Anyone with a similar setup?
I banged my head against the wall several hours with this one. After extensive debugging, google gave the answer and I feel right to post it here since this was the first result I got in google.
Add this class on top of your [ServiceContract] interface declaration (typically IProjectService.cs
public class ApplyDataContractResolverAttribute : Attribute, IOperationBehavior
{
public void AddBindingParameters(OperationDescription description, BindingParameterCollection parameters)
{
}
public void ApplyClientBehavior(OperationDescription description, System.ServiceModel.Dispatcher.ClientOperation proxy)
{
var dataContractSerializerOperationBehavior =
description.Behaviors.Find<DataContractSerializerOperationBehavior>();
dataContractSerializerOperationBehavior.DataContractResolver =
new ProxyDataContractResolver();
}
public void ApplyDispatchBehavior(OperationDescription description, System.ServiceModel.Dispatcher.DispatchOperation dispatch)
{
var dataContractSerializerOperationBehavior =
description.Behaviors.Find<DataContractSerializerOperationBehavior>();
dataContractSerializerOperationBehavior.DataContractResolver =
new ProxyDataContractResolver();
}
public void Validate(OperationDescription description)
{
// Do validation.
}
}
Requirements are
using System.ServiceModel.Description;
using System.Data.Objects;
using System.ServiceModel.Channels;
Then under the [OperationContract] keyword add [ApplyDataContractResolver] keyword and you are set!
Big thanks to http://blog.rsuter.com/?p=286
For sending data trough WCF you should disable lazy loading (dataContext.ContextOptions.LazyLoadingEnabled = false;).
To be sure the data you want is loaded you need to use eager loading ( trough the Include method).
You need to change your function to:
public IEnumerable<Project> getProjects()
{
ProjectContext p = new ProjectContext();
p.ContextOptions.LazyLoadingEnabled = false;
return p.Projects.Include("Tasks").AsEnumerable();
}