I am trying to call multiple micro services in one saga workflow class. Below is my code which is not working please let me know how to fix it. Really appreciate your help.
I am able to make the notification service work if i publish the message from payment consumer method. but i want to call my notification dependency class from saga workflow.
public class PaymentSaga : MassTransitStateMachine<PaymentSagaStatus>,IPayment
{
public PaymentSaga()
{
InstanceState(s => s.CurrentState);
Event(() => PaymentCollectCommand, x =>
x.CorrelateBy(pay => pay.StudentId.ToString(),
context => context.Message.StudentId.ToString())
.SelectId(context => Guid.NewGuid()));
// Payment
Event(() => PaymentCollectedEvent, x => x.CorrelateById(context =>
context.Message.CorrelationId));
// Notification
Event(() => NotificationEvent, x => x.CorrelateById(context =>
context.Message.CorrelationId));
Initially(
When(PaymentCollectCommand)
.Then(context =>
{
// Will be done by auto mapper
context.Instance.ReceivedPaymentDate = DateTime.Now;
context.Instance.StudentId = context.Data.StudentId;
context.Instance.CourseName = context.Data.CourseName;
context.Instance.Amount = context.Data.Amount;
context.Instance.TransactionDate = context.Data.TransactionDate;
})
// Send to payment Service
.ThenAsync(
context => Console.Out.WriteLineAsync(
$"Student payment received. Id:{context.Instance.CorrelationId}"))
.TransitionTo(ReceivePayment)
.Publish(context => new PaymentReceivedEvent(context.Instance))
);
During(ReceivePayment,
When(PaymentCollectedEvent)
.Then(context => context.Instance.ReceivedPaymentDate = DateTime.Now)
.ThenAsync(
context => Console.Out.WriteLineAsync(
$"Student payment processed. Id: {context.Instance.CorrelationId}")),
When(NotificationEvent)
.Then(context =>
{
// Will be done by auto mapper
context.Instance.ReceivedPaymentDate = DateTime.Now;
context.Instance.Message = context.Data.Message;
})
.ThenAsync(context => Console.Out.WriteLineAsync(
$"Notification send. Id: {context.Instance.CorrelationId}"))
.TransitionTo(NotificationReceived)
.Publish(context => new NotificationReceivedEvent(context.Instance))
.Finalize());
SetCompletedWhenFinalized();
}
public State ReceivePayment { get; private set; }
public State NotificationReceived { get; private set; }
public Event<IPayment> PaymentCollectCommand { get; private set; }
public Event<IPaymentReceivedEvent> PaymentCollectedEvent { get; private set; }
public Event<INotification> NotificationEvent { get; private set; }
public int StudentId { get; set; }
public string CourseName { get; set; }
public DateTime TransactionDate { get; set; }
public double Amount { get; set; }
public string Message { get; set; }
}
public class PaymentReceivedEvent : IPaymentReceivedEvent
{
private readonly PaymentSagaStatus _instance;
public PaymentReceivedEvent(PaymentSagaStatus instance)
{
_instance = instance;
}
public Guid CorrelationId => _instance.CorrelationId;
public int StudentId => _instance.StudentId;
public string CourseName => _instance.CourseName;
public DateTime TransactionDate => _instance.TransactionDate;
public double Amount => _instance.Amount;
}
public class NotificationReceivedEvent : INotification
{
private readonly PaymentSagaStatus _instance;
public NotificationReceivedEvent(PaymentSagaStatus instance)
{
_instance = instance;
}
public Guid CorrelationId => _instance.CorrelationId;
public string Message => $"{_instance.StudentId} is being enrolled in {_instance.CourseName}. Fee paid: £{_instance.Amount}";
}
Related
I have two entities : Article and PrixVariation.
One Article has Many Prix variation.
One Prix variation has One Article.
public partial class Article
{
public Article()
{
PrixVariations = new HashSet<PrixVariation>();
}
public int Id { get; set; }
public DateTime DateTime { get; set; }
...
public virtual ICollection<PrixVariation> PrixVariations { get; set; }
}
public partial class PrixVariation
{
public int Id { get; set; }
public DateTime DateTime { get; set; }
public int Article { get; set; }
public double Prix { get; set; }
public virtual Article ArticleNavigation { get; set; } = null!;
}
My Context is as follow :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Article>(entity =>
{
entity.ToTable("articles");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.DateTime)
.HasColumnType("datetime")
.HasColumnName("dateTime")
.HasDefaultValueSql("(getdate())");
});
modelBuilder.Entity<PrixVariation>(entity =>
{
entity.ToTable("prix_variation");
entity.Property(e => e.Id)
.ValueGeneratedNever()
.HasColumnName("id");
entity.Property(e => e.Article).HasColumnName("article");
entity.Property(e => e.DateTime)
.HasColumnType("datetime")
.HasColumnName("dateTime")
.HasDefaultValueSql("(getdate())");
entity.Property(e => e.Prix).HasColumnName("prix");
entity.HasOne(d => d.ArticleNavigation)
.WithMany(p => p.PrixVariations)
.HasForeignKey(d => d.Article)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_prix_variation_articles");
});
And my controller is as follow :
public class ArticlesController : ControllerBase
{
private readonly STGEORGESContext _context;
public ArticlesController(STGEORGESContext context)
{
_context = context;
}
// GET: api/Articles
[HttpGet]
public async Task<ActionResult<IEnumerable<Article>>> GetArticles()
{
return await _context.Articles.ToListAsync();
}
....
There is something not working here because when I launch the debogger, the collection of PrixVaration is always empty :
{"id":1,"dateTime":"2021-11-28T08:37:17","prixVariations":[]}
And of course in the database there is one PrixVaration linked to that Article..
Can anyone can help ?? Thaks a lot !!!
Pierre
It is called lazy loading. Ef doesn't load any object collections if you don't ask about it explicitly. So try this
return await _context.Articles.Include(i=> i.PrixVariations).ToListAsync();
I am trying to return a json array of my 3rd level depth related data, the issue here is that I get the result with the right property name but with a non clear value content, I failed to find a similar case to solve it. From the returned value message it looks like I am returning a queryable instead of the final result and I need to iterate over it, I've tried several ways to achive that but failed to find the right one.
The json result:
[
{
"registeredYear": "System.Linq.Enumerable+SelectEnumerableIterator`2[MyPath.Groups.GroupYear,System.String]"
}
]
The api endpoint
public async Task<ActionResult<IEnumerable<UserGroup>>> GetUserGroupYears(string email, string groupName)
{
var groupYears = await _repo.GetUserGroupYears(email, groupName);
var mappedEntities = _mapper.Map<GroupYearsListDto[]>(groupYears);
return Ok(mappedEntities);
}
The Repository method
public async Task<IEnumerable<UserGroup>> GetUserGroupYears(string email, string groupName)
{
var userGroupYears = _context.UserGroups
.Include(uo => uo.Group.GroupYears)
.ThenInclude( oy => oy.Year)
.Where(uo => uo.Group.Name == groupName && uo.Email == email );
return await userGoupYears.ToArrayAsync();
}
The used classes:
public class UserGroup
{
public string Email { get; set; }
public string UserId { get; set; }
public virtual User User { get; set; }
public string GroupId { get; set; }
public virtual Group Group { get; set; }
}
public class Group
{
public string Name { get; set; }
public virtual ICollection<UserGroup> Users { get; set; }
public virtual ICollection<GroupYear> GroupYears { get; }
}
public class GroupYear {
public string GroupId { get; set; }
public virtual Group Group { get; set; }
public string YearId { get; set; }
public virtual Year Year { get; set; }
public string RegisteredYear { get; set; }
}
The data transfer object and the mapping:
public class GroupYearsListDto
{
public string RegisteredYear { get; set; }
}
public CoreMappingProfiles()
{
CreateMap<UserGroup, GroupYearsListDto>()
.ForMember(
dest => dest.RegisteredYear,
opt => opt.MapFrom(src => src.Group.GroupYears.Select(x => x.RegisteredYear))
);
}
Update: Attaching a debugger shows that the repository method is returning an IQueryable including the correct values and the controller method makes something wrong when mapping. So I think the following line is responsible of that wrong result:
var mappedEntities = _mapper.Map<GroupYearsListDto[]>(GroupYears);
You are getting this JSON result:
[
{
"registeredYear": "System.Linq.Enumerable+SelectEnumerableIterator`2[MyPath.Groups.GroupYear,System.String]"
}
]
Because you are mapping an IEnumerable<string> to a string, as I mentioned in my comment. So essentially you are getting the same as:
CreateMap<UserGroup, GroupYearsListDto>()
.ForMember(
dest => dest.RegisteredYear,
opt => opt.MapFrom(src =>
{
IEnumerable<string> registeredYears = src.Group.GroupYears.Select(x => x.RegisteredYear);
return registeredYears.ToString();
})
);
And registeredYears.ToString() is "System.Linq.Enumerable+SelectEnumerableIterator`2[MyPath.Groups.GroupYear,System.String]".
I imagine you will either:
Only have one - so do something like: src.Group.GroupYears.Select(x => x.RegisteredYear).Single()
Have multiples - so do something like: string.Join(", ", src.Group.GroupYears.Select(x => x.RegisteredYear))
You have many options, but you need to actually return a string to that property or else you will just get the ToString() version of IEnumerable<string>.
UPDATE:
Based on your comments below, you can try this:
Repository:
public IQueryable<GroupYear> GetGroupYears(string email, string groupName)
{
return _context
.UserGroups
.Where(x => x.Group.Name == groupName && x.Email == email)
.SelectMany(x => x.Group.GroupYears);
}
Controller:
public async Task<ActionResult<GroupYearsListDto[]>> GetGroupYears(string email, string groupName)
{
var groupYears = _repo.GetGroupYears(email, groupName);
var projection = _mapper.ProjectTo<GroupYearsListDto>(groupYears)
var mappedEntities = await projection.ToArrayAsync();
return Ok(mappedEntities);
}
Profile:
CreateMap<GroupYears, GroupYearsListDto>();
In an ASP.NET Core 1.1 Web API, I am trying to map an entity model to a DTO using AutoMapper.
The entity model:
namespace InspectionsData.Models
{
[Table("property")]
public class Property
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("property_id")]
public int? Id { get; set; }
[Column("show_inventory")]
public bool ShowInventory { get; set; }
[Column("latitude")]
public double? Latitude { get; set; }
[Column("longitude")]
public double? Longitude { get; set; }
[Column("property_type_id")]
public int? PropertyTypeId { get; set; }
[ForeignKey("PropertyTypeId")]
[Display(Name = "Property Type")]
public PropertyType PropertyType { get; set; }
[Column("inspection_frequency_id")]
public int? InspectionFrequencyId { get; set; }
[ForeignKey("InspectionFrequencyId")]
[Display(Name = "Inspection Frequency")]
public InspectionFrequency InspectionFrequency { get; set; }
[Column("group_id")]
public int? GroupId { get; set; }
[ForeignKey("GroupId")]
[Display(Name = "Group")]
public Group Group { get; set; }
[Column("added_by_id")]
public int? AddedById { get; set; }
[ForeignKey("AddedById")]
[Display(Name = "Added By")]
public virtual User AddedBy { get; set; }
[Column("added_date")]
[Display(Name = "Added Date")]
public DateTime AddedDate { get; set; }
[Column("deleted_by_id")]
public int? DeletedById { get; set; }
[ForeignKey("DeletedById")]
[Display(Name = "Deleted By")]
public virtual User DeletedBy { get; set; }
[Column("deleted_date")]
[Display(Name = "Deleted Date")]
public DateTime? DeletedDate { get; set; }
}
and the DTO:
namespace InspectionsData.DTOs
{
public class PropertyDto
{
public int? Id { get; set; }
public bool ShowInventory { get; set; }
public double? Latitude { get; set; }
public double? Longitude { get; set; }
public PropertyType PropertyType { get; set; }
public InspectionFrequency InspectionFrequency { get; set; }
public DateTime NextInspectionDate { get; set; }
}
}
The mapping is done in a configuration file:
public class AutoMapperProfileConfiguration : Profile
{
public AutoMapperProfileConfiguration()
{
// Add as many of these lines as you need to map your objects
var map = CreateMap<Property, PropertyDto>();
map.ForAllMembers(opt => opt.Ignore());
map.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id));
map.ForMember(dest => dest.ShowInventory, opt => opt.MapFrom(src => src.ShowInventory));
map.ForMember(dest => dest.Latitude, opt => opt.MapFrom(src => src.Latitude));
map.ForMember(dest => dest.Longitude, opt => opt.MapFrom(src => src.Longitude));
map.ForMember(dest => dest.PropertyType, opt => opt.MapFrom(src => src.PropertyType));
map.ForMember(dest => dest.InspectionFrequency, opt => opt.MapFrom(src => src.InspectionFrequency));
}
}
And the setting up of AutoMapper in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
var config = new AutoMapper.MapperConfiguration(cfg =>
{
cfg.AddProfile(new AutoMapperProfileConfiguration());
});
var mapper = config.CreateMapper();
services.AddSingleton(mapper);
}
In my controller action, I execute the mapping:
[HttpGet]
public async Task<IActionResult> GetProperty()
{
var properties = _context.Property
.Include(t => t.PropertyType)
.Include(f => f.InspectionFrequency)
.Where(a => a.DeletedDate == null && a.GroupId == 1);
var propertiesDto = _mapper.Map<IEnumerable<PropertyDto>>(properties);
return Ok(propertiesDto);
}
It doesn't give an error, but all the properties in all the objects in the propertiesDto list are default values (NULL for objects and nullable types, FALSE for booleans, 0 for integers, etc.) Any ideas where I'm going wrong?
It's because the following line
map.ForAllMembers(opt => opt.Ignore());
is letting AM ignore all member mappings, including the ones you have configured explicitly.
Simply use ForAllOtherMembers instead:
map.ForAllOtherMembers(opt => opt.Ignore());
I have pretty simple linq expression:
session.Query<Order>().Where(x => x.States.OrderByDescending(z => z.Date).FirstOrDefault().Name == "2").ToList();
Result: InvalidCastException Unable to cast object of type 'Antlr.Runtime.Tree.CommonTree' to type 'NHibernate.Hql.Ast.ANTLR.Tree.IASTNode'.
Same query with LinqPad works as expected: selects orders, which last state is OnTheWay. How can I circumvent this and get desired result?
Code to try yourself:
class Program
{
static void Main(string[] args)
{
ISessionFactory sessionFactory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008
.ConnectionString(x => x.FromConnectionStringWithKey("defaultConnectionStringForNhibernate")))
.Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.GetEntryAssembly()))
.BuildSessionFactory();
var session = sessionFactory.OpenSession();
var res2 =
session.Query<Order>().Where(x => x.States.OrderByDescending(z => z.Date).FirstOrDefault().Name == "2").ToList();
}
}
public class Order
{
public virtual int Id { get; set; }
public virtual IList<OrderState> States { get; set; }
public virtual string Name { get; set; }
}
public class OrderState
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual DateTime Date { get; set; }
public virtual Order Order { get; set; }
}
public class OrderMap : ClassMap<Order>
{
public OrderMap()
{
Id(x => x.Id)
.GeneratedBy.Identity();
HasMany(x => x.States)
.Inverse()
.AsBag()
.KeyColumn("OrderId");
Map(x => x.Name)
.Not.Nullable();
Table("Orders");
}
}
public class OrderStateMap : ClassMap<OrderState>
{
public OrderStateMap()
{
Id(x => x.Id)
.GeneratedBy.Identity();
References(x => x.Order)
.Column("OrderId");
Map(x => x.Name)
.Not.Nullable();
Map(x => x.Date)
.Not.Nullable();
Table("OrderStates");
}
}
After some time research I found solution:
var res = (from i in session.Query<Order>()
where ((from s in i.States
orderby s.Date descending
select s.Name).First()) == "2"
select i).ToList();
I'm getting the following error when running my application. I'm forcing the application to do a DropCreateDatabaseAlways for my context so I'm not sure why it would say that I'm trying to insert multiple entities with the same key.
There might be a problem with how User and Message are linked. I had to add:
modelBuilder.Entity<Message>()
.HasRequired(m => m.UserSentFrom)
.WithRequiredDependent()
.WillCascadeOnDelete(false);
Just because I was getting a warning about circular reference and had to set the willcascadeondelete to false.
If there is something wrong with my design, let me know, but this is what I'm trying to do:
User has many Messages,
Message has one FromUser
Message has many ToUsers
Any help will be appreciated as this is driving me crazy.
{"Conflicting changes detected. This may happen when trying to insert multiple entities with the same key."}
public class User
{
public int Id { get; set; }
[Required(ErrorMessage="Username is required")]
public string Username { get; set; }
[Required(ErrorMessage = "Password is required")]
[DataType(DataType.Password)]
public string Password { get; set; }
[Required(ErrorMessage="Email Address is required")]
[DataType(DataType.EmailAddress)]
public string UserEmailAddress { get; set; }
[DisplayFormat(DataFormatString="{0:d}",ApplyFormatInEditMode=true)]
public DateTime DateCreated { get; set; }
[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
public DateTime LastUpdated { get; set; }
public virtual ICollection<Message> Messages { get; set; }
public User()
{
Messages = new List<Message>();
}
}
public class Message
{
public int Id { get; set; }
public DateTime DateSent { get; set; }
public virtual ICollection<User> UserSentTo { get; set; }
public string Title { get; set; }
public string Body { get; set; }
public int UserSentFromId { get; set; }
public virtual User UserSentFrom { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Message>()
.HasMany(m => m.UserSentTo).WithMany(u => u.Messages)
.Map(t => t.MapLeftKey("MessageId")
.MapRightKey("UserId")
.ToTable("MessagesUserSentTo"));
modelBuilder.Entity<Message>()
.HasRequired(m => m.UserSentFrom)
.WithRequiredDependent()
.WillCascadeOnDelete(false);
}
protected override void Seed(WebsiteGameContext context)
{
var users = new List<User>
{
new User { Username = "Admin", UserEmailAddress="admin#temp.com", DateCreated = DateTime.Now, LastUpdated = DateTime.Now},
new User { Username = "Member", UserEmailAddress="member#temp.com",DateCreated = DateTime.Now, LastUpdated = DateTime.Now}
};
users.ForEach(u => context.Users.Add(u));
context.SaveChanges();
var messages = new List<Message>
{
new Message { DateSent=DateTime.Now, Title="Sent From Admin", Body="There should be 2 users that this was sent to", UserSentFromId=users[0].Id,UserSentTo= new List<User>()},
new Message { DateSent=DateTime.Now, Title="Sent From Member", Body="There should be 1 user that this was sent to", UserSentFromId=users[1].Id,UserSentTo= new List<User>()}
};
messages.ForEach(m => context.Messages.Add(m));
context.SaveChanges();
messages[0].UserSentTo.Add(users[0]);
messages[0].UserSentTo.Add(users[1]);
messages[1].UserSentTo.Add(users[0]);
context.SaveChanges();
}
To make it work change 2 things.
First delete the messages property in Users and make a MessagesSend and MessagesReceived property. Then make the mapping like this:
modelBuilder.Entity<Message>().HasMany(t => t.UserSentTo)
.WithMany(t => t.MessagesReceived)
.Map(m =>
{
m.ToTable("MessagesUserSentTo");
m.MapLeftKey("MessageId");
m.MapRightKey("UserId");
});
modelBuilder.Entity<Message>().HasRequired(t => t.UserSentFrom)
.WithMany(t => t.MessagesSend)
.HasForeignKey(d => d.UserSentFromId)
.WillCascadeOnDelete(false);