I am working with Sagas in ReBus and from my experience with NServiceBus you could reply to the original creator of the Saga to give updates, something like this:
Saga<>.ReplyToOriginator
I do not see an equivalent way of doing this with ReBus. Is there a way to do this, and if not what is a good pattern (other than the originator polling) I can use that will achieve the same thing? An example is trying to create a customer and the client wanting to know when it is created before trying to change it's address.
Here is a trivial example of the Customer scenario I quickly put together:
public class CreateCustomerSaga : Saga<CreateCustomerData>,
IAmInitiatedBy<CreateCustomerCommand>,
IHandleMessages<CustomerUniqunessCheckResult>
{
private readonly IBus _bus;
private readonly ICustomerResourceAccess _customerResourceAccess;
public CreateCustomerSaga(IBus bus, ICustomerResourceAccess customerResourceAccess)
{
_bus = bus;
_customerResourceAccess = customerResourceAccess;
}
public override void ConfigureHowToFindSaga()
{
Incoming<CustomerUniqunessCheckResult>(x => x.IsCustomerUnique).CorrelatesWith(y => y.CustomerId);
}
public void Handle(CreateCustomerCommand message)
{
Data.CustomerId = message.CustomerId;
Data.CustomerName = message.CustomerName;
_bus.Send(new CheckCustomerUniquenessCommand(message.CustomerId));
}
public void Handle(CustomerUniqunessCheckResult message)
{
if (message.IsCustomerUnique)
{
_customerResourceAccess.CreateCustomer(Data.CustomerId, Data.CustomerName);
// This is what seems to be missing from ReBus to reply to the original sender
_bus.?(new CustomerCreatedEvent(Data.CustomerId));
}
else
{
// This is what seems to be missing from ReBus to reply to the original sender
_bus.?(new CustomerAlreadExistsEvent(Data.CustomerId));
}
}
}
public class CustomerCreatedEvent
{
public Guid CustomerId { get; set; }
public CustomerCreatedEvent(Guid customerId)
{
CustomerId = customerId;
}
}
public class CustomerAlreadExistsEvent
{
public Guid CustomerId { get; set; }
public CustomerAlreadExistsEvent(Guid customerId)
{
CustomerId = customerId;
}
}
public class CustomerUniqunessCheckResult
{
public bool IsCustomerUnique { get; set; }
}
public class CheckCustomerUniquenessCommand
{
public CheckCustomerUniquenessCommand(Guid customerId)
{ }
}
public interface ICustomerResourceAccess
{
void CreateCustomer(Guid customerId, string customerName);
}
public class CreateCustomerCommand
{
public Guid CustomerId { get; set; }
public string CustomerName { get; set; }
}
public class CreateCustomerData : ISagaData
{
public CreateCustomerData()
{
Id = Guid.NewGuid();
}
public Guid CustomerId { get; set; }
public string CustomerName { get; set; }
public Guid Id { get; set; }
public int Revision { get; set; }
}
No, unfortunately there's no reply to originator function in Rebus' sagas at the moment. You can easily do it though, by storing the originator's endpoint when your saga is created like this (in all Handle methods of messages that can initiate the saga):
if (IsNew) {
Data.Originator = MessageContext.GetCurrent().ReturnAddress;
}
and then when you want to reply back to the originator:
bus.Advanced.Routing.Send(Data.Originator, new HelloThereMyFriend());
I've often thought about adding this to Rebus though, either as an extra field on ISagaData, or as an extra interface ISagaDataWithOriginator that you could optionally apply to your saga data, but I've never had the need (enough) myself.
If each sender is a separate (virtual) machine, like in my implementation, you could get a guaranteed reply to originator by using their machine unique ID or MAC address in the reply queue name, like this:
Bus = Configure.With(adapter)
.Transport(t => t.UseSqlServer(DbConfiguration.DatabaseConnectionString,
sInstanceId, sInstanceId + ".Error")
.EnsureTableIsCreated())
...
If each originator has a unique queue ID, the consumer can reply to originator simply by using Bus.Reply.
The unique machine id can be determined using System.Management:
string uuid = string.Empty;
ManagementClass mc = new System.Management.ManagementClass("Win32_ComputerSystemProduct");
if (mc != null)
{
ManagementObjectCollection moc = mc.GetInstances();
if (moc != null)
{
foreach (ManagementObject mo in moc)
{
PropertyData pd = mo.Properties["UUID"];
if (pd != null)
{
uuid = (string)pd.Value;
break;
}
}
}
}
Or by using the MAC address of the machine (our fallback code):
if (string.IsNullOrEmpty(uuid))
{
uuid = NetworkInterface.GetAllNetworkInterfaces()
.Where(ni => ni.OperationalStatus == OperationalStatus.Up)
.FirstOrDefault()
.GetPhysicalAddress().ToString();
}
Related
I'm trying to recreate the sticky notes app in windows 10 using WPF.
I am using Entity Framework with an SQLite database. And EF can't retrieve an object which is the child of another. How can I do that?
I have basically 2 classes i wanna store in this database
StickyNote and SitckyNoteGroup. Each StickyNote has a StickyNoteGroup.
In order you to fully understand I'll post all the classes involved in my problem but here is the github repo if you want https://github.com/Kamigoro/StickyNotes.
namespace Todo.Models
{
public class StickyNote
{
public int Id { get; set; }
public string Name { get; set; }
public string Text { get; set; }
public StickyNoteGroup Group { get; set; }
public override string ToString()
{
return $"Name : {Name}\nText : {Text}";
}
}
}
namespace Todo.Models
{
public class StickyNoteGroup
{
public int Id { get; set; }
public string Name { get; set; }
public string Color { get; set; }
public override string ToString()
{
return $"Name : {Name}\nColor : {Color}";
}
}
}
My class using EntityFramework looks like this and is called NoteContext
using Microsoft.EntityFrameworkCore;
namespace Todo.Models.DataAccess
{
public class NoteContext : DbContext
{
public DbSet<StickyNote> Notes { get; set; }
public DbSet<StickyNoteGroup> NoteGroups { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseSqlite("Data Source=Todo.db");
}
}
And finally the class that I use to do CRUD operations is called DataAccessor and looks like this.
namespace Todo.Models.DataAccess
{
public static class DataAccessor
{
public static List<StickyNote> GetStickyNotes()
{
List<StickyNote> notes;
using (var database = new NoteContext())
{
notes = database.Notes.ToList();
}
return notes;
}
public static void SaveStickyNote(StickyNote stickyNote)
{
using (var database = new NoteContext())
{
database.Notes.Add(stickyNote);
database.SaveChanges();
}
}
public static List<StickyNoteGroup> GetStickyNoteGroups()
{
List<StickyNoteGroup> noteGroups;
using (var database = new NoteContext())
{
noteGroups = database.NoteGroups.ToList();
}
return noteGroups;
}
public static void SaveStickyNoteGroup(StickyNoteGroup stickyNoteGroup)
{
using (var database = new NoteContext())
{
database.NoteGroups.Add(stickyNoteGroup);
database.SaveChanges();
}
}
}
}
My question is why this part of code tell me that there is no StickyNoteGroup for the current StickyNote, even though there is one in the sqlite database?
private void btnAddStickyNote_Click(object sender, RoutedEventArgs e)
{
StickyNoteGroup group = new StickyNoteGroup() { Name = "Test Group", Color = "Red" };
StickyNote note = new StickyNote() { Name = "Note 1", Text = "An attempt to write a note", Group = group };
DataAccessor.SaveStickyNote(note);
foreach (StickyNote currentNote in DataAccessor.GetStickyNotes())
{
Debug.WriteLine(currentNote.Group.ToString());
}
}
StickyNote Table:
StickyNoteGroup Table:
Thanks a lot for your answers. And if you have other comments to do on my code, they are welcomed, because i don't really know the good patterns to work with an ORM like that.
It seems that you need to add Include call:
public static List<StickyNote> GetStickyNotes()
{
List<StickyNote> notes;
using (var database = new NoteContext())
{
notes = database.Notes
.Include(n => n.Group)
.ToList();
}
return notes;
}
BTW you can return evaluated query result data (for example via ToList, ToArray, First and so on) from inside using statement:
public static List<StickyNote> GetStickyNotes()
{
using (var database = new NoteContext())
{
return database.Notes
.Include(n => n.Group)
.ToList();
}
}
How to include a new item in the array of items in an object in MongoDB with C#?
I tried to use the AddToSet method, but I did not succeed.
I have the following code structure:
1 - Parent object (Revenda):
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using System.Collections.Generic;
namespace api.mstiDFE.Entidade.api.mstiDFE
{
public class Revenda : Notificavel, IEntidade
{
public Revenda(string Id, long Codigo, string CPF, string CNPJ, List<RevendaCliente> Clientes)
{
this.Id = Id;
this.Codigo = Codigo;
this.CPF = CPF;
this.CNPJ = CNPJ;
this.Clientes = Clientes;
}
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; private set; }
[BsonElement("Codigo")]
public long Codigo { get; private set; }
[BsonElement("Nome")]
public string Nome { get; private set; }
[BsonElement("CPF")]
public string CPF { get; private set; }
[BsonElement("CNPJ")]
public string CNPJ { get; private set; }
[BsonElement("Clientes")]
public ICollection<RevendaCliente> Clientes { get; private set; }
}
}
2 - Child object (RevendaCliente):
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using System.Collections.Generic;
namespace api.mstiDFE.Entidade.api.mstiDFE
{
public class RevendaCliente : Notificavel, IEntidade
{
public RevendaCliente(string Codigo, string Nome, string CPF, string CNPJ, ICollection<RevendaClienteToken> Tokens)
{
this.Codigo = Codigo;
this.Nome = Nome;
this.CPF = CPF;
this.CNPJ = CNPJ;
this.Tokens = Tokens;
}
[BsonElement("Codigo")]
public string Codigo { get; private set; }
[BsonElement("Nome")]
public string Nome { get; private set; }
[BsonElement("CPF")]
public string CPF { get; private set; }
[BsonElement("CNPJ")]
public string CNPJ { get; private set; }
[BsonElement("Tokens")]
public ICollection<RevendaClienteToken> Tokens { get; private set; }
}
}
3 - Code used to insert a complete parent object:
public Revenda Add(Revenda revenda)
{
Database.GetCollection<Revenda>("Revendas").InsertOne(revenda);
return revenda;
}
4 - Code used to recover a specific reseller:
public Revenda FindById(string id)
{
return CollRevendas.Find<Revenda>(revenda => revenda.Id == id).FirstOrDefault();
}
Everything works fine.
However, how can I only include a new child object (RevendaCliente) in a parent object (Revenda) already registered in MongoDB?
I am using the following environment:
-Microsoft.AspNetCore.App (2.1.1)
-MongoDB.Driver (2.8.0)
(as I mentioned in my comment) your problem seems pretty simple, as in MongoDB the related objects in hierarchy are part of the same document, so you need to update your object in-memory and update it.
var parentObject=CollRevendas.Find<Revenda>(revenda => revenda.Id == id).FirstOrDefault();
parentObject.Clientes.Add(newChildObject);
//now update the parent object
Code that worked for me: (Resolved with the support of Aarif)
public bool AddRevendaCliente(string revendaId, RevendaCliente requestRevendaClient)
{
try
{
var filter = Builders<Revenda>.Filter.Eq(s => s.Id, revendaId);
// Get a reference to the parent parent "Revenda"
var parentObject = CollRevendas.Find<Revenda>(filter).FirstOrDefault();
parentObject.Clientes.Add(requestRevendaClient);
// Update the parent object "Revenda"
var result = CollRevendas.ReplaceOneAsync(filter, parentObject);
}
catch (Exception ex)
{
throw;
}
return true;
}
I have a saga which checks on the status of an API calls every 30 seconds if the status returned back from the call is successful the saga is ended, if not
the saga waits 30 seconds and attempts again. If the API call has not returned a successful response within 60 minutes, the saga is timed out and ended.
I am having problems getting my 60 minute timeout to fire. The code I have is
public class MonitorSubmissionFeedSagaData: IContainSagaData
{
public Guid Id { get; set; }
public string Originator { get; set; }
public string OriginalMessageId { get; set; }
public bool TimeoutSet { get; set; }
[Unique]
public string JobId { get; set; }
}
public class MonitorSubmissionFeedSaga : Saga<MonitorSubmissionFeedSagaData>,
IAmStartedByMessages<MonitorFeedSubmissonCommand>,
IHandleMessages<StartCheckSubmissionCommand>,
IHandleTimeouts<MonitorSubmissionFeedSagaTimeout>
{
public const int SagaTimeoutInMinutes = 60;
public IEmpathyBrokerClientApi PostFileService { get; set; }
protected override void ConfigureHowToFindSaga(SagaPropertyMapper<MonitorSubmissionFeedSagaData> mapper)
{
mapper.ConfigureMapping<MonitorFeedSubmissonCommand>(x => x.JobId).ToSaga(saga => saga.JobId);
}
public void Handle(MonitorFeedSubmissonCommand message)
{
Data.JobId = message.JobId;
CheckTimeout();
Bus.Send(new StartCheckSubmissionCommand
{
JobId = Data.JobId
});
}
public void Handle(StartCheckSubmissionCommand message)
{
Log.Info("Saga with JobId {0} received", Data.JobId);
bool isCompleted = GetJobStatus(message.JobId);
while (isCompleted)
{
Thread.Sleep(30000);
isCompleted = GetJobStatus(message.JobId);
}
MarkAsComplete();
}
public void CheckTimeout()
{
RequestTimeout<MonitorSubmissionFeedSagaTimeout>(TimeSpan.FromMinutes(SagaTimeoutInMinutes));
}
public void Timeout(MonitorSubmissionFeedSagaTimeout state)
{
MarkAsComplete();
}
bool GetJobStatus(string jobId)
{
return false;
var status = PostFileService.GetJobIdStatus(jobId);
if (status.state == "FAILURE" || status.state == "DISCARDED")
{
return false;
}
return true;
}
}
Can anyone see where I am going wrong?
thanks
Your Saga should go idle. You're keeping it alive with a while loop. The timeout message arrives at some point and then you should check what should happen. Either another checkout or MarkAsComplete.
I wrote this in Notepad, so it might not compile. But it's to get an idea.
public class MonitorSubmissionFeedSagaData: IContainSagaData
{
public Guid Id { get; set; }
public string Originator { get; set; }
public string OriginalMessageId { get; set; }
[Unique]
public string JobId { get; set; }
public DateTime SagaStartTimeUtc { get; set; }
}
public class MonitorSubmissionFeedSaga : Saga<MonitorSubmissionFeedSagaData>,
IAmStartedByMessages<MonitorFeedSubmissonCommand>,
IHandleTimeouts<VerifyApiTimeOut>
{
public IEmpathyBrokerClientApi PostFileService { get; set; }
public void Handle(MonitorFeedSubmissonCommand message)
{
Data.JobId = message.JobId;
Data.SagaStartTimeUtc = DateTime.NowUtc;
CreateTimeoutRequest();
}
public void CreateTimeoutRequest()
{
RequestTimeout<VerifyApiTimeOut>(TimeSpan.FromSeconds(30));
}
public void Timeout(VerifyApiTimeOut state)
{
if (!GetJobStatus(Data.JobId) && DateTime.NowUtc < Data.SagaStartTimeUtc.AddMinutes(60))
{
CreateTimeoutRequest();
}
MarkAsComplete();
}
bool GetJobStatus(string jobId)
{
return false;
var status = PostFileService.GetJobIdStatus(jobId);
if (status.state == "FAILURE" || status.state == "DISCARDED")
{
return false;
}
return true;
}
}
Another comment could be that the Saga itself should not call out to external services. Preferably not even to some database. Delegate this to another service. Every 30 seconds, send out a message to another handler. This handler should call the WebService/WebAPI. When it can confirm that everything is correct, reply to the original Saga. When it's not correct, just let it be. The Saga will send out messages every 30 seconds to retry.
After 60 minutes, the Saga should stop sending messages and MarkAsComplete.
I'm trying to learn C# and I do not understand why I'm getting an error. I am getting the error "ServerList.servers' is a 'property' but is used like a 'type'". I've read several guidelines that state I should not have a publically accessible list which is why I am trying to use a method to return the list of servers.
How do I correctly return the "servers" collection? Am I doing it totally wrong? Also, is there anything else wrong with my code?
class Program
{
static void Main()
{
ServerList list = new ServerList();
list.AddServer("server", "test1", "test2");
}
}
public class ServerInformation
{
public string Name { get; set; }
public string IPv4 { get; set; }
public string IPv6 { get; set; }
}
public class ServerList
{
private List<ServerInformation> servers { get; set; }
public ServerList()
{
servers = new List<ServerInformation>;
}
public void AddServer(string name, string ipv4, string ipv6)
{
servers.Add(new Server { Name = name, IPv4 = ipv4, IPv6 = ipv6 });
}
public ReadOnlyCollection<servers> GetServers()
{
return servers;
}
}
Your ServerList class has several problems. I've included a comment for each one indicating what your code says and the corrected version below.
public class ServerList
{
private List<ServerInformation> servers { get; set; }
public ServerList()
{
//servers = new List<ServerInformation>;
// constructor must include parentheses
servers = new List<ServerInformation>();
}
public void AddServer(string name, string ipv4, string ipv6)
{
//servers.Add(new Server { Name = name, IPv4 = ipv4, IPv6 = ipv6 });
// Server does not exist, but ServerInformation does
servers.Add(new ServerInformation { Name = name, IPv4 = ipv4, IPv6 = ipv6 });
}
//public ReadOnlyCollection<servers> GetServers()
// The type is ServerInformation, not servers.
public ReadOnlyCollection<ServerInformation> GetServers()
{
//return servers;
// servers is not readonly
return servers.AsReadOnly();
}
}
public ReadOnlyCollection<ServerInformation> GetServers()
{
return new ReadOnlyCollection<ServerInformation>(servers);
}
You can't use a property as a generic type
I have application that host WCF service and I want to return this class object:
namespace classes
{
[DataContract]
public class NetworkAdapter
{
[DataMember]
public string Name { get; set; }
[DataMember]
public string ID { get; set; }
[DataMember]
public string Description { get; set; }
[DataMember]
public string IPAddress { get; set; }
[DataMember]
private string gatewayIpAddress;
[DataMember]
public string Speed { get; set; }
[DataMember]
public string NetworkInterfaceType { get; set; }
[DataMember]
public string MacAddress { get; set; }
[DataMember]
private LivePacketDevice livePacketDevice;
[DataMember]
public PacketDevice PacketDevice { get { return livePacketDevice; } }
public NetworkAdapter(LivePacketDevice packetDevice)
{
livePacketDevice = packetDevice;
}
public override string ToString()
{
return Description;
}
public static NetworkAdapter[] getAll()
{
List<NetworkAdapter> list = new List<NetworkAdapter>();
foreach (NetworkInterface adapter in NetworkInterface.GetAllNetworkInterfaces())
foreach (UnicastIPAddressInformation uniCast in adapter.GetIPProperties().UnicastAddresses)
{
if (!System.Net.IPAddress.IsLoopback(uniCast.Address) && uniCast.Address.AddressFamily != AddressFamily.InterNetworkV6)
{
StringBuilder gatewayIPAddresses = new StringBuilder();
string gatewayIPAddressesDisplay = string.Empty;
foreach (var address in adapter.GetIPProperties().GatewayAddresses)
{
gatewayIPAddresses.Append(address.Address);
gatewayIPAddresses.Append(" ");
}
if (gatewayIPAddresses.Length > 0)
{
gatewayIPAddressesDisplay = gatewayIPAddresses.ToString().TrimEnd(' ');
}
if (!list.Any(l => l.ID == adapter.Id))
{
list.Add(new NetworkAdapter(getDevice(adapter.Id))
{
Name = adapter.Name,
ID = adapter.Id,
Description = adapter.Description,
IPAddress = uniCast.Address.ToString(),
NetworkInterfaceType = adapter.NetworkInterfaceType.ToString(),
Speed = adapter.Speed.ToString("#,##0"),
MacAddress = getMacAddress(adapter.GetPhysicalAddress().ToString()),
gatewayIpAddress = gatewayIPAddressesDisplay
});
}
}
}
//return list.GroupBy(n => n.ID).Select(g => g.FirstOrDefault()).ToArray();
return list.ToArray();
}
private static LivePacketDevice getDevice(string id)
{
return LivePacketDevice.AllLocalMachine.First(x => x.Name.Contains(id));
}
private static string getMacAddress(string oldMAC)
{
int count = 0;
string newMAC = oldMAC;
for (int i = 2; i < oldMAC.Length; i += 2)
{
newMAC = newMAC.Insert(i + count++, ":");
}
return newMAC;
}
public string defaultGateway
{
get
{
if (gatewayIpAddress != "")
{
return gatewayIpAddress;
}
return "n/a";
}
}
private static string getIpFourthSegment(string ipAddress)
{
try
{
string[] arr = ipAddress.Split('.');
return arr[3];
}
catch (Exception)
{
return null;
}
}
}
}
This is my service:
[ServiceContract()]
public interface IService1
{
[OperationContract]
NetworkAdapter[] GetAdapters();
[OperationContract]
string GetDate();
}
[ServiceBehavior(
ConcurrencyMode = ConcurrencyMode.Multiple,
InstanceContextMode = InstanceContextMode.PerSession)]
public class service1 : IService1
{
public NetworkAdapter[] GetAdapters()
{
IEnumerable<NetworkAdapter> adapters = NetworkAdapter.getAll();
return adapters.ToArray();
}
public string GetDate()
{
return DateTime.Now.ToString();
}
}
When I am try to run GetAdapters function got this error:
An unhandled exception of type 'System.ServiceModel.CommunicationException' occurred in mscorlib.dll
Additional information: The socket connection was aborted. This could be caused by an error processing your message or a receive timeout being exceeded by the remote host, or an underlying network resource issue. Local socket timeout was '00:00:59.9599960'.
When try to run GetDate function it works fine and return simple string.
maybe I need to configure my class in other way ? I have added [DataMember] to each member
LivePacketDevice and PacketDevice need to be [DataContract]s, while it might also work if they are just [Serializable]. Otherwise WCF does not know how to transfer them to the client.
Also it is advisable to only transfer objects that just hold data, not functionality, as that functionality will not be available to the client. The stub created on the client side will only contain data fields, not methods, as code is not transferred/cloned.