i'm using actually abp 4.4.0, hello, I am trying to recode the https://community.abp.io/articles/creating-an-event-organizer-application-with-the-blazor-ui-wbe0sf2z but for EfCore here is the code of the tables:
public class Event : FullAuditedAggregateRoot<Guid>
{
[Required]
public string Title { get; set; }
public string Description { get; set; }
public DateTime StartTime { get; set; }
public bool IsFree { get; set; }
public ICollection<EventAttendee> Attendees { get; set; }
public Event()
{
Attendees = new Collection<EventAttendee>();
}
}
public class EventAttendee : Entity<int>
{
public Event Event { get; set; }
public Guid EventId { get; set; }
public Guid AttendeeId { get; set; }
}
Here the DbContextModelCreatingExtensions:
builder.Entity<Event>(t =>
{
t.ToTable("Events");
t.ConfigureByConvention();
t.HasMany(x => x.Attendees)
.WithOne(x => x.Event)
.HasForeignKey(x => x.EventId)
.IsRequired(false);
});
builder.Entity<EventAttendee>(t =>
{
t.ToTable("Attendees");
t.ConfigureByConvention();
});
the DBContext referemcement
public DbSet<Event> Events { get; set; }
public DbSet<EventAttendee> Attendees { get; set; }
and the seed
public async Task SeedAsync(DataSeedContext context)
{
await _eventRepository.InsertAsync(new Event()
{
Title = "First Event",
Description = "This is a test",
IsFree = true,
StartTime = DateTime.Now.AddDays(2),
Attendees = new List<EventAttendee>()
{
new EventAttendee(){AttendeeId = Guid.NewGuid()},
new EventAttendee(){AttendeeId = Guid.NewGuid()},
new EventAttendee(){AttendeeId = Guid.NewGuid()}
}
});
}
And the EventAppService
public class EventAppService : ManagerAppService, IEventAppService
{
private readonly IRepository<Event, Guid> _eventRepository;
private readonly IRepository<IdentityUser> _userRepository;
public EventAppService(IRepository<Event, Guid> eventRepository, IRepository<IdentityUser> userRepository)
{
_eventRepository = eventRepository;
_userRepository = userRepository;
}
public async Task<EventDetailDto> GetAsync(Guid id)
{
var #event = await _eventRepository.GetAsync(id);
var attendeeIds = #event.Attendees.Select(a => a.AttendeeId).ToList();
var queryable = await _userRepository.GetQueryableAsync();
var query = queryable
.Where(u => attendeeIds.Contains(u.Id));
var attendees = (await AsyncExecuter.ToListAsync(query))
.ToDictionary(x => x.Id);
var result = ObjectMapper.Map<Event.Event, EventDetailDto>(#event);
foreach (var attendeeDto in result.Attendees)
{
attendeeDto.UserName = attendees[attendeeDto.UserId].UserName;
}
return result;
}
But i have a problem, when i execute the DBMigrator, the seed is created correctely but when i want to get my events, the attendees list is empty
{
"title": "First Event",
"description": "This is a test",
"isFree": true,
"startTime": "2021-09-23T07:48:34.663988",
"attendees": [],
"creationTime": "2021-09-21T07:48:35.656599",
"creatorId": null,
"id": "39ff1912-edee-0d2a-9aca-00a2ff5ed128"
}
and I don't understand why he can't get the attendees back, if I forgot something ?
Thank you in advance
For relational DB (EF Core), define DefaultWithDetailsFunc:
Configure<AbpEntityOptions>(options =>
{
options.Entity<Event>(eventOptions =>
{
eventOptions.DefaultWithDetailsFunc = query => query.Include(e => e.Attendees);
});
});
Alternatively, explicitly load the collection:
var #event = await _eventRepository.GetAsync(id);
await _eventRepository.EnsureCollectionLoadedAsync(#event, e => e.Attendees); // Add this
var attendeeIds = #event.Attendees.Select(a => a.AttendeeId).ToList();
Reference: https://docs.abp.io/en/abp/4.4/Entity-Framework-Core
Related
I'm currently building a program that processes tickets (agile) in different stages. For some reason I'm having a hard time resolving an error with my home controller and model references. I am being told that model.DueFilter = Filter.DueFilterValue is a method (Filter) which is not valid in the given context.
Below is my Home Controller:
public class HomeController : Controller
{
private TicketContext context;
public HomeController(TicketContext ctx) => context = ctx;
public IActionResult Index(string ID)
{
AgileViewModel model = new AgileViewModel();
var filter = new Filter(ID);
model.Filter = new Filter(ID);
model.Sprints = context.Sprints.ToList();
model.TicketStatuses = context.TicketStatuses.ToList();
model.DueFilter = Filter.DueFilterValue;
IQueryable<Ticket> query = context.Tickets.Include(t => t.Sprint).Include(t => t.TicketStatus);
if (filter.HasSprint)
{
query = query.Where(t => t.SprintID == filter.SprintID);
}
if (filter.HasTicketStatus)
{
query = query.Where(t => t.TicketStatusID == filter.TicketStatusID);
}
if (filter.HasDue)
{
var currentDate = DateTime.Today;
if (filter.isPast)
query = query.Where(t => t.Deadline < currentDate);
else if (filter.isFuture)
query = query.Where(t => t.Deadline > currentDate);
else if (filter.isToday)
query = query.Where(t => t.Deadline == currentDate);
}
var tasks = query.OrderBy(t => t.Deadline).ToList();
model.Tasks = tasks;
return View(model);
}
}
Here is my Filter model:
public class Filter
{
public Filter(string filterstring)
{
FilterString = filterstring ?? "all-all-all";
string[] filter = FilterString.Split('-');
SprintID = filter[0];
Due = filter[1];
TicketStatusID = filter[2];
}
public string FilterString { get; }
public string SprintID { get; }
public string Due { get; }
public string TicketStatusID { get; }
public bool HasSprint => SprintID.ToLower() != "all";
public bool HasDue => Due.ToLower() != "all";
public bool HasTicketStatus => TicketStatusID.ToLower() != "all";
public static Dictionary<string, string> DueFilterValue =>
new Dictionary<string, string>
{
{"future", "Future" },
{"past", "Past" },
{"today", "Today" }
};
public bool isPast => Due.ToLower() == "past";
public bool isFuture => Due.ToLower() == "future";
public bool isToday => Due.ToLower() == "today";
}
Add lastly my ModelView:
public class AgileViewModel
{
public AgileViewModel()
{
CurrentTask = new Ticket();
}
public Filter Filter { get; set; }
public List<TicketStatus> TicketStatuses { get; set; }
public List<Sprint> Sprints { get; set; }
public Dictionary<string, string> DueFilter { get; set; }
public List<Ticket> Tasks { get; set; }
public Ticket CurrentTask { get; set; }
}
I have a problem with testing my Context.
My app is running in .NET Core 2.2 and I've installed EFCore v2.2.6.
When I launch my test I get this error:
System.NotSupportedException : Unsupported expression: c => c.Prices
Non-overridable members (here: MyContext.get_Prices) may not be used in setup / verification expressions.
This is my context class:
using MyProject.Model;
using Microsoft.EntityFrameworkCore;
namespace MyProject.Persistence
{
public class MyContext : DbContext
{
public MyContext(DbContextOptions<MyContext> options) : base(options) {}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Price>()
.HasKey(p => new { p.CustomerAccount, p.ItemId, p.Amount });
}
public DbSet<Price> Prices { get; set; }
}
}
This is my repository:
using MyProject.Model;
using MyProject.Persistence;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace MyProject.Repository
{
public class PriceRepository : IPriceRepository
{
private readonly MyContext _myContext;
public PriceRepository(MyContext myContext)
{
_myContext = myContext;
}
public async Task<List<Price>> GetPricesAsync(List<string> items, string customerAccount)
=> await _myContext.Prices.Where(price => price.CustomerAccount == customerAccount && items.Contains(price.ItemId)).ToListAsync();
}
}
My Price class:
[Table("Price")]
public class Price
{
public string CustomerAccount { get; set; }
public string ItemId { get; set; }
public double Amount { get; set; }
[NotMapped]
public int Id { get; set; }
[NotMapped]
public string ItemInternalId { get; set; }
[NotMapped]
public DateTime ModifiedDateTime { get; set; }
}
My test:
[Fact]
public async Task Test1Async()
{
IQueryable<Price> prices = new List<Price>
{
new Price
{
Amount = 39.71,
CustomerAccount = "010324",
ItemId = "10103001",
Id = 1,
ItemInternalId = "test",
ModifiedDateTime = new System.DateTime()
},
new Price
{
Amount = 57.09,
CustomerAccount = "010324",
ItemId = "10103001",
Id = 2,
ItemInternalId = "test2",
ModifiedDateTime = new System.DateTime()
}
}.AsQueryable();
var mockSet = new Mock<DbSet<Price>>();
var options = new DbContextOptionsBuilder<MyContext>()
.UseInMemoryDatabase(databaseName: "FekaConnectionString")
.Options;
mockSet.As<IQueryable<Price>>().Setup(m => m.Provider).Returns(prices.Provider);
mockSet.As<IQueryable<Price>>().Setup(m => m.Expression).Returns(prices.Expression);
mockSet.As<IQueryable<Price>>().Setup(m => m.ElementType).Returns(prices.ElementType);
mockSet.As<IQueryable<Price>>().Setup(m => m.GetEnumerator()).Returns(prices.GetEnumerator());
var mockContext = new Mock<MyContext>(options);
mockContext.Setup(c => c.Prices).Returns(mockSet.Object);
var repository = new PriceRepository(mockContext.Object);
var list = new List<string>
{
"10103001"
};
var result = await repository.GetPricesAsync(list, "010324");
Assert.Single(result);
}
Can anyone help me?
Thanks :)
No need to mock the context if using in-memory.
[Fact]
public async Task Test1Async() {
//Arrange
var prices = new List<Price> {
new Price {
Amount = 39.71,
CustomerAccount = "010324",
ItemId = "10103001",
Id = 1,
ItemInternalId = "test",
ModifiedDateTime = new System.DateTime()
},
new Price
{
Amount = 57.09,
CustomerAccount = "010324",
ItemId = "10103001",
Id = 2,
ItemInternalId = "test2",
ModifiedDateTime = new System.DateTime()
}
};
var options = new DbContextOptionsBuilder<MyContext>()
.UseInMemoryDatabase(databaseName: "FekaConnectionString")
.Options;
var context = new MyContext(options);
//populate
foreach(var price in prices) {
context.Prices.Add(price);
}
await context.SaveChangesAsync();
var repository = new PriceRepository(mockContext.Object);
var list = new List<string>
{
"10103001"
};
//Act
var result = await repository.GetPricesAsync(list, "010324");
//Assert
Assert.Single(result);
}
In an Action Result that does a HttpPost i get an error from EF
"ModelState.Errors Internal error in the expression evaluator"
My model in View is OrdineOmaggio
public partial class OrdineOmaggio
{
public int Id { get; set; }
public string Id_Gioielleria { get; set; }
public System.DateTime Data_Ordine { get; set; }
public virtual Consumatore MD_CONSUMATORE { get; set; }
public virtual Omaggio MD_OMAGGIO { get; set; }
public virtual CodiceRandomConsumatore MD_RANDOM_CONSUMATORE { get; set; }
}
My Action is so
public async Task<ActionResult> ChooseGift(
[Bind(Include ="Data_Ordine,MD_RANDOM_CONSUMATORE,MD_OMAGGIO,Id_Gioielleria")]
OrdineOmaggio ordineOmaggio,
string codiceOmaggio, string codice)
{
var randomConsumatore = _context.CodiciRandomConsumatori
.SingleOrDefault(c => c.Codice == codice) ??
new CodiceRandomConsumatore
{
Id = -1,
Codice = "",
Assegnato = null,
Distinzione = ""
};
var consumatore = _context.CodiciRandomConsumatori
.Where(c => c.Codice == codice)
.Select(c => c.MD_CONSUMATORE)
.SingleOrDefault();
var omaggio = _context.Omaggi
.SingleOrDefault(c => c.CodiceOmaggio == codiceOmaggio);
if (ModelState.IsValid)
{
ordineOmaggio.Data_Ordine = DateTime.Now;
ordineOmaggio.Id_Gioielleria = ordineOmaggio.Id_Gioielleria;
ordineOmaggio.MD_CONSUMATORE = consumatore; // FK
ordineOmaggio.MD_OMAGGIO = omaggio; // FK
ordineOmaggio.MD_RANDOM_CONSUMATORE = randomConsumatore; // FK
_context.OrdiniOmaggio.Add(ordineOmaggio);
randomConsumatore.Assegnato = true;
_context.SaveChanges();
return RedirectToAction("Success");
}
return View(ordineOmaggio);
}
The error is about dataAnnotation: it say that not all field all filled
The metadata is
public class OrdineOmaggioMetadata
{
[Required(ErrorMessage = "Scegli la gioiellereia.")]
public string Id_Gioielleria;
[Required(ErrorMessage = "Seleziona una foto.")]
public Omaggio MD_OMAGGIO;
...
}
In my view i placed
#Html.HiddenFor(m=> m.MD_OMAGGIO.CodiceOmaggio)
#Html.ValidationMessageFor(m => m.MD_OMAGGIO.CodiceOmaggio)
but this helper pass null to ActionResult
MD_OMAGGIO is a table foreign key for product codes.
what i wrong ?
I have code that works, but I worked around a 'Join' in Linq to Entities, because I could not figure it out.
Could you please show me how to succesfully apply it to my code?
My desired result is a dictionary:
Dictionary<string, SelectedCorffData> dataSelectedForDeletion = new Dictionary<string, SelectedCorffData>();
The above mentioned class:
public class SelectedCorffData
{
public long CorffId { get; set; }
public string ReportNumber { get; set; }
public DateTime CorffSubmittedDateTime { get; set; }
}
Please note the 'intersectResult' I am looping through is just a string collection.
Here is my code:
DateTime dateToCompare = DateTime.Now.Date;
Dictionary<string, SelectedCorffData> dataSelectedForDeletion = new Dictionary<string, SelectedCorffData>();
foreach (var mafId in intersectResult)
{
var corffIdsPerMaf = context
.Mafs
.Where(m => m.MafId == mafId)
.Select(m => m.CorffId);
var corffIdForMaf = context
.Corffs
.Where(c => corffIdsPerMaf.Contains(c.Id))
.OrderByDescending(c => c.CorffSubmittedDateTime)
.Select(c => c.Id)
.First();
//Selected close-out forms, whose MAF's may be up for deletion, based on date.
var corffData = context
.Corffs
.Where(c => c.Id == corffIdForMaf && System.Data.Entity.DbFunctions.AddYears(c.CorffSubmittedDateTime, 1).Value > dateToCompare)
.Select(c => new SelectedCorffData () { CorffId = c.Id, ReportNumber = c.ReportNumber, CorffSubmittedDateTime = c.CorffSubmittedDateTime })
.FirstOrDefault();
if(corffData != null)
{
dataSelectedForDeletion.Add(mafId, corffData);
}
}
Please note: this is not just a simple join. If it can't be simplified, please tell me. Also please explain why.
The code below I don't think is exactly right but it is close to what you need. I simulated the database so I could get the syntax correct.
namespace System
{
namespace Data
{
namespace Entity
{
public class DbFunctions
{
public static Data AddYears(DateTime submittedTime, int i)
{
return new Data();
}
public class Data
{
public int Value { get; set; }
}
}
}
}
}
namespace ConsoleApplication23
{
class Program
{
static void Main(string[] args)
{
Context context = new Context();
int dateToCompare = DateTime.Now.Year;
var corffIdsPerMaf = context.Mafs.Select(m => new { id = m.CorffId, mafs = m}).ToList();
var corffIdForMaf = context.Corffs
.Where(c => System.Data.Entity.DbFunctions.AddYears(c.CorffSubmittedDateTime, 1).Value > dateToCompare)
.OrderByDescending(c => c.CorffSubmittedDateTime).Select(c => new { id = c.Id, corff = c}).ToList();
var intersectResult = from p in corffIdsPerMaf
join f in corffIdForMaf on p.id equals f.id
select new SelectedCorffData() { CorffId = p.id, ReportNumber = f.corff.ReportNumber, CorffSubmittedDateTime = f.corff.CorffSubmittedDateTime };
Dictionary<string, SelectedCorffData> dataSelectedForDeletion = intersectResult.GroupBy(x => x.ReportNumber, y => y).ToDictionary(x => x.Key, y => y.FirstOrDefault());
}
}
public class Context
{
public List<cMafs> Mafs { get; set;}
public List<cCorffs> Corffs { get; set;}
}
public class cMafs
{
public int CorffId { get; set; }
}
public class cCorffs
{
public DateTime CorffSubmittedDateTime { get; set; }
public int Id { get; set; }
public string ReportNumber { get; set; }
}
public class Test
{
}
public class SelectedCorffData
{
public long CorffId { get; set; }
public string ReportNumber { get; set; }
public DateTime CorffSubmittedDateTime { get; set; }
}
}
I have the following entities (I18N is a localized entity):
public class Post {
public Int32 Id { get; set; }
public Boolean IsPublished { get; set; }
public List<PostI18N> PostsI18N { get; set; }
public List<Tag> Tags { get; set; }
public Author { get; set; }
}
public class Tag {
public List<TagI18N> TagsI18N { get; set; }
}
public class Author {
public Int32 Id { get; set; }
public String Name { get; set; }
}
public class PostI18N {
public Int32 Id { get; set; }
public String Text { get; set; }
public String Title { get; set; }
}
public class TagI18N {
public Int32 Id { get; set; }
public String Name { get; set; }
}
I need to get all information of 4 posts so I tried to flatten the query:
var posts = await _context
.Posts
.SelectMany(x => x.PostsI18N, (Post, PostI18N) =>
new { Post, PostI18N, Post.Tags, Post.Author })
.Where(x => x.PostI18N.Language == "en")
.Select(x => new PostDTO {
Id = x.Post.Id,
Title = x.PostI18N.Title,
Text = x.PostI18N.Text,
AuthorName = x.Author.Name
TagsNames = // Names taken from x.Tags.TagsI18N where TagsI18N
// language is "en" ... So, for each tag look the
// one Tag.TagI18N which Tag.TagI18N.Language = "en"
// and get Tag.TagI18N.Name
})
.Take(4)
.ToListAsync();
PROBLEM:
The problem is that I also need the TagsI18N flatten so I can take their names for English language ...
It this possible with SelectMany? How should I do this?
Try it in query syntax instead:
var posts = await (
from p in _context.Posts
from pn in p.PostsI18N
where pn.Language == "en"
select new PostDTO {
Id = p.Id,
Title = pn.Title,
Text = pn.Text,
AuthorName = p.Author.Name,
TagsNames = from t in p.Tags
from tn in t.TagsI18N
where tn.Language == "en"
select tn.Name
}).Take(4).ToListAsync();
The SelectMany syntax should work as well, but it gets a bit "nested":
var posts = await _context
.Posts
.SelectMany(x => x.PostsI18N, (Post, PostI18N) =>
new { Post, PostI18N, Post.Tags, Post.Author })
.Where(x => x.PostI18N.Language == "en")
.Select(x => new PostDTO {
Id = x.Post.Id,
Title = x.PostI18N.Title,
Text = x.PostI18N.Text,
AuthorName = x.Author.Name
TagsNames =
x.Tags.SelectMany(t => t.TagsI18N, (Tag, TagI18N) =>
new { Tag, TagI18N })
.Where(t => t.TagI18N.Language == "en")
.Select(t => t.TagI18N.Name)
})
.Take(4)
.ToListAsync();