I am learning the APB and I am analyzing and extending the code from the Introduction Part 2 tutorial (ASP.NET Core, EF). https://aspnetboilerplate.com/Pages/Documents/Articles/Introduction-With-AspNet-Core-And-Entity-Framework-Core-Part-2/index.html
First I introduced a new entity - Team. Team is a group of people. I added the foreign key to the Person entity.
[Table("AppPersons")]
public class Person : AuditedEntity<Guid>
{
public const int MaxNameLength = 32;
[Required]
[StringLength(MaxNameLength)]
public string Name { get; set; }
public Guid TeamId { get; set; }
[ForeignKey(nameof(TeamId))]
public Team Team { get; set; }
}
I would like to create an ApplicationService that will return the list of people for a specific team with the number of assigned tasks. I don't know how should I combine the Repository and Mapping infrastructure to achieve this.
public class FooAppService : ApplicationService, IFooAppService
{
private readonly IRepository<Task, Guid> _taskRepository;
public FooAppService(IRepository<Task, Guid> taskRepository)
{
_taskRepository = taskRepository;
}
public ListResultDto<PersonWithNumberOfTasksAssignedDto> FooMethod(Guid teamId)
{
...
}
}
I also don't know what is the best way to design the DTO object. Do I need a new DTO object for this service method or should I use a tuple? I do have a PersonDto object that is used to add/edit Person. Should I just wrap it with a count property?
public class PersonWithNumberOfTasksAssignedDto : EntityDto<Guid>
{
public PersonDto Person { get; set; }
public int NumberOfAssignedTasks { get; set; }
}
You can take reference from Abp free startup template.
public async Task<PersonDto> Create(PersonCreateInput input)
{
var person = ObjectMapper.Map<Person>(input);
await CurrentUnitOfWork.SaveChangesAsync();
return MapToEntityDto(person);
}
For example, UserAppService.Create() https://github.com/aspnetboilerplate/module-zero-core-template/blob/164a5c9e28cb29383551d0f3310986ab43d0ceed/aspnet-core/src/AbpCompanyName.AbpProjectName.Application/Users/UserAppService.cs#L55-L76
To retrieve list of items, you can leverage on the AsyncCrudAppService base class which provides sorting and pagination out of the box (via AsyncCrudAppService.GetAll().
See https://github.com/aspnetboilerplate/aspnetboilerplate/blob/14c4fe5a5408a66e913a434688b951815247827d/src/Abp/Application/Services/AsyncCrudAppService.cs#L112-L129
Related
I have around 50 master tables that requires simple and straight forward CRUD operations, my tables are already available in the sql database.
My question is how to make it generic so that I dont need to create manually each individual page for master tables. I saw some ABP CRUDEntityAscyn classes in Boilerplate framework, but I am wondering how to bring it at Presentation layer (.cshtml).
If you need to create an application service that will have Create, Update, Delete, Get, GetAll methods for a specific entity, you can inherit from CrudAppService (or AsyncCrudAppService if you want to create async methods) class to create it easier. CrudAppService base class is generic which gets related Entity and DTO types as generic arguments and is extensible which allows you to override functionality when you need to customize it.
public class Task : Entity, IHasCreationTime
{
public string Title { get; set; }
public string Description { get; set; }
public DateTime CreationTime { get; set; }
public TaskState State { get; set; }
public Person AssignedPerson { get; set; }
public Guid? AssignedPersonId { get; set; }
public Task()
{
CreationTime = Clock.Now;
State = TaskState.Open;
}
}
[AutoMap(typeof(Task))]
public class TaskDto : EntityDto, IHasCreationTime
{
public string Title { get; set; }
public string Description { get; set; }
public DateTime CreationTime { get; set; }
public TaskState State { get; set; }
public Guid? AssignedPersonId { get; set; }
public string AssignedPersonName { get; set; }
}
public class TaskAppService : AsyncCrudAppService<Task, TaskDto>
{
public TaskAppService(IRepository<Task> repository)
: base(repository)
{
}
}
public interface ITaskAppService : IAsyncCrudAppService<TaskDto>
{
}
public class TaskAppService : AsyncCrudAppService<Task, TaskDto>, ITaskAppService
{
public TaskAppService(IRepository<Task> repository)
: base(repository)
{
}
}
calling webapi from client code:
var _editionService = abp.services.app.edition
_editionService.deleteEdition({
id: edition.id
}).done(function () {
getEditions();
abp.notify.success(app.localize('SuccessfullyDeleted'));
});
read for more > https://aspnetboilerplate.com/Pages/Documents/Application-Services#crudappservice-and-asynccrudappservice-classes
Im my Web API project, am trying to map the id in DTO call to the Object. But I am facing issues in setting up dependecy using AutoFac.
Below is the classes I am using.
public abstract class DomainObject<IdT> : IDomainObject<IdT>
{
public virtual IdT Id { get; set; }
}
public class Course : DomainObject<long>
{
public string CourseName { get; set; }
public Teacher Teacher { get; set; }
}
public class Teacher : DomainObject<long>
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
I want to map CourseDTO to Course , the TeacherID has to be mapped to Teacher object
public class CourseDTO
{
public long Id{ get; set; }
public string CourseName { get; set; }
public long TeacherID { get; set; }
}
Below is the entity converter.
public class EntityConverter<T,IdT> : ITypeConverter<IdT, T>
{
private readonly IGenericRepository<T, IdT> _repository;
public EntityConverter(IGenericRepository<T, IdT> repository)
{
_repository = repository;
}
public T Convert(ResolutionContext context)
{
return _repository.GetById((IdT)context.SourceValue);
}
}
How can I define the mapping and inject the dependency using AutoFac.
It's not clear from your question what you are having trouble with. If you're just trying to map the TeacherId in your DTO, then there is no work to do at all:
Mapper.CreateMap<Course, CourseDTO>();
TeacherId will be mapped automatically from Teacher.Id.
If you're wanting to map a TeacherDTO by looking it up from the repository, then you can use the approach in the linked question. Registration will be very similar to the Windsor approach, and setting up the container for resolution is almost identical too:
ContainerBuilder builder = new ContainerBuilder();
var container = builder.Build();
Mapper.Initialize(cfg => cfg.ConstructServicesUsing(container.Resolve));
If you're trying to do more complicated things than this, then you'll need to post the actual DTOs you're trying to map to.
I'm using a property of my own class inside EF Core data model.
public class Currency
{
public string Code { get; set; }
public string Symbol { get; set; }
public string Format { get; set; }
}
[ComplexType]
public class Money
{
public int? CurrencyID { get; set; }
public virtual Currency Currency { get; set; }
public double? Amount { get; set; }
}
public class Rate
{
public int ID { get; set; }
public Money Price = new Money();
}
My problem is that when I try to create a migration, EF Core reports a error.
Microsoft.Data.Entity.Metadata.ModelItemNotFoundException: The entity type 'RentABike.Models.Money' requires a key to be defined.
If I declare a key, a separate table for "Money" is created, which is not what I'm looking for.
Is there any way to use ComplexType in EF Core and put it all into a single table?
Support for complex types is currently on the backlog https://github.com/aspnet/EntityFramework/issues/246
As an update based on one of your comments above, you now use the OwnsOne syntax for this using the Fluent API in your DbContext's OnModelCreating function.
[ComplexType]
public class Money
{
public double? Amount { get; set; }
}
public class Rate
{
[Key]
public long Id { get; set; }
public Money Price { get; set; }
}
public MyDbContext : DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Rate>(entity =>
{
entity.OwnsOne(e => e.Currency);
});
}
}
I'm not actually sure if it makes use of the ComplexTypeAttribute or not. But when I generated my migration via Add-Migration, it generated as expected for the old ComplexType documentation this way (i.e. table named Rate has column Price_Amount).
Diego Vega announced Owned Entities and Table Splitting, which is supposed to be a different approach and an alternative to complex types.
Can't share my personal impressions because I haven't checked this personally, but Julie Lerman, seems to have been satisfied...
Use:
modelBuilder.Owned<T>:
Example:
public MyDbContext : DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Owned<Rate>();
}
}
You can just put [NotMapped] above
public class Rate
{
public int ID { get; set; }
[NotMapped]
public Money Price = new Money();
}
like this.
I'm having a trouble do the following mapping:
Domain (simplified version):
public class Ad
{
// Primary properties
public int Kms { get; set; }
// Navigation properties
public virtual Model Model { get; set; }
}
DTO:
public class CreateAdDto
{
// Primary properties
public int Kms { get; set; }
// Navigation properties
public virtual ModelDto Model { get; set; }
}
ViewModel:
public class CreateAdViewModel
{
// Primary properties
public int Kms { get; set; }
public int Make_Id { get; set; }
public int Model_Id { get; set; }
// Navigation properties
public IEnumerable<SelectListItem> MakeList { get; set; }
public IEnumerable<SelectListItem> ModelList { get; set; }
}
In the Controller, when I do the Mapping I'm loosing the Make_ID from the Dropdownlist of the View:
public virtual ActionResult Create(CreateAdViewModel adViewModel)
{
if (ModelState.IsValid)
{
var adDto = Mapper.Map<CreateAdViewModel, CreateAdDto>(adViewModel);
_adService.CreateAd(adDto);
}
return RedirectToAction(MVC.Home.Index());
}
The mapping is:
Mapper.CreateMap<CreateAdViewModel, CreateAdDto>()
Thanks.
As you have mentionned, the Ad need to know the Model_Id and to set it into the Model
Mapper.CreateMap<CreateAdDto, Ad>()
.ForMember(dest => dest.Model, opt => opt.MapFrom(src => new Model { Id = src.Model_Id }));
You also need from the other mapping side to let the Dto know where to get the Model id.
Mapper.CreateMap<Ad, CreateAdDto>()
.ForMember(dest => dest.Model_Id, opt => opt.MapFrom(src => src.Model.Id}));
The code above is not secure because a validation to see if Model is null should be added.
For the rest of your code, you seem to do it right. The section with Entity Framework requires you to attach because the entity Model already exist, otherwise, EF would insert this entity to the database.
CreateAdDto doesn't have a Make navigation property or a Make_Id property.
Solution found after some research:
ViewDomain:
public class CreateAdViewModel
{
// Primary properties
public int Kms { get; set; }
public int Make_Id { get; set; }
public int Model_Id { get; set; }
// Navigation properties
public IEnumerable<SelectListItem> MakeList { get; set; }
public IEnumerable<SelectListItem> ModelList { get; set; }
}
DTO:
public class CreateAdDto
{
// Primary properties
public int Kms { get; set; }
public int Model_Id { get; set; }
// Navigation properties
//public virtual ModelDto Model { get; set; }
}
Domain:
public class Ad
{
// Primary properties
public int Kms { get; set; }
// Navigation properties
public virtual Model Model { get; set; }
}
Viewmodel -> Dto Mapping:
Mapper.CreateMap<CreateAdViewModel, CreateAdDto>();
Dto -> Domain Mapping:
Mapper.CreateMap<CreateAdDto, Ad>()
.ForMember(dest => dest.Model, opt => opt.MapFrom(src => new Model { Id = src.Model_Id }));
Atention:
To achieve this with Entity Framework, I had to attach first the Model Entity to the Context and then Ad the new Ad:
public void CreateAd(CreateAdDto adDto)
{
var adDomain = Mapper.Map<CreateAdDto, Ad>(adDto);
_modelRepository.Attach(adDomain.Model);
_adRepository.Add(adDomain);
_adRepository.Save();
}
Hope this is the best practice.
BTW I would like to have some opinions regarding this aproach.
Thanks.
Based on what is I see in your question, I suggest a simple approach. Your application is medium scale. You should very carefully about maintainability,my experience say.So try to create a simple an strain forward approach for yourself like below approach:
I can describe all layer in detail but with notice to title of your question I prefer describe only Model(bussiness Ojbect) layer:
Good! As you can see PM.Model include:
Tow sub folders contain our ViewModels and in root of Library we have a .tt file contain Entity framework Objects (POCO classes) and we have a Mapper folder(Since that i dont like use autoMapper or third party like this :) ).
You can see IListBox interface in Domain layer. I put all ListBox container to this interface.
I hope current approach useful for you but finally I suggest remove one of this layers DTO or ViewModel, because in the future will be very complex.
Good luck
Do you aware about cost of these mapping?! You have 2 layers mapping (before arrived to Entity framework) for an simple insert.We can do more complex CRUD(s) in less than 2 layers mapping.
How to think about maintainability of this code?
Please keep DRY,KISS,SOLID conventions in your mind and top of your everyday work.
Good luck
I'm building an ASP.NET MVC3 website with an code first database and have the following question:
Is it possible to make an instance of MyDbContext class with an additional argument set which will be used for filtering the results of calls to mydbcontext.
I want to use this for restricting the resultset to the current user that is logged in on my asp.net mvc website.
Any directions would be great!
I don't see why that should be a problem. Something like this should work:
public class Northwind : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
}
public class FilteredNorthwind : Northwind
{
public IQueryable<Products> GetFilteredProducts(string userRole)
{
return Products.Where(product => product.UserRole == userRole);
}
}
Update
To make it impossible for your MyDbContext to be abused, you could put all your database code and models into a separate project/assembly. Then make your DbContext an internal class (instead of public), then create a public class (FilteredDbContext) that wraps your MyDbContext and exposes methods that allow you to only grab the data your allowed to see. Then in your main assembly (your web project), you will only be able to use FilteredDbContext.
So, for example:
internal class Northwind : DbContext // note: internal class
{
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
}
public class FilteredNorthwind // note: does not inherit from `Northwind`
{
private readonly _dbContext = new Northwind();
public IQueryable<Products> GetProducts(string userRole)
{
return _dbContext.Products.Where(product => product.UserRole == userRole);
}
}
If Northwind and FilteredNorthwind are in a separate assembly from your web app, you can instantiate only FilteredNorthwind from your web app.
Update 2
If you use a ViewModel, then your web app can't get back to the list of all products for a category because you extract out only the properties you need (and only the properties the user is allowed to see).
public class ProductViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
public IEnumerable<Products> GetProducts(string userRole)
{
return _dbContext.Products
.Where(product => product.UserRole == userRole)
.Select(product => new ProductViewModel
{
Id = product.Id,
Name = product.Name,
Price = product.Price
};
}
You could make a layer above and hide the generated one and create a your own DbContext which derives from the generated MyDbContext. Just a wild guess but it seems logical to me and so you can implement your own argument set and still use the generated one.
I would do this:
public interface IUserContext {
string User { get; set; }
}
public class Database : DbContext {
public IDbSet<Product> Products { get; set; }
}
public class AuthorizedDatabase {
private readonly Database _database;
private readonly IUserContext _userContext;
public AuthorizedDatabase(Database database, IUserContext userContext) {
_database = database;
_userContext = userContext;
}
private bool Authorize<TEntity>(TEntity entity) {
// Some code here to look at the entity and the _userContext and decide if it should be accessible.
}
public IQueryable<Product> Products {
get {
return _database.Products.Where(Authorize);
}
}
}
This would allow me to cleanly abstract the actual logic around the authorization (and your IUserContext interface can be as simple or complex as required to suite your exact needs.)
To ensure that the user is unable is circumvert this protection using a navigation property (Product.Category.Products, for example.) you might need to turn off lazy loading and explicitly load the required related entities.
Have a look at this post from ADO.NET Team Blog for ideas: loading related entities