I want to update my entity for just sending values.
public HttpResponseMessage UpdateDepartment(Department department)
{
var ok = _departmentDAL.Update(department);
return Request.CreateResponse(HttpStatusCode.OK, ok);
}
Im sending just 2 value with postman to my api.
In my generic repository base, my update function like.
public int Update(TEntity entity)
{
var updatedEntity = _context.Entry(entity);
updatedEntity.State = EntityState.Modified;
return _context.SaveChanges();
}
And I got entity validation error. Simply I just want to modify only not null values of entity.
Is it possible or should I take all entity with my Id property from database and then after changing the properties send to the entity framework ?
Cleanest solution is to not provide a general interface that can update any desired field of Department. Instead, provide an API that is tailored to the actual use cases you want to support. This API should receive commands that only contain the data allowed for the specific use case. Commands can also validate their data (here I use System.ComponentModel.DataAnnotations for validation). Also, you can handle authorization in a more granular way if the use cases are well defined and separated.
public class UpdateDepartmentDescriptionCommand {
[Required, Range(1, long.MaxValue)]
public long DepartmentId { get; set; }
[Required, StringLength(256)]
public string Description { get; set; }
}
public HttpResponseMessage UpdateDepartmentDescription(UpdateDepartmentDescriptionCommand cmd) {
// validate command
var validationResults = new List<ValidationResult>();
var isValid = Validator.TryValidateObject(cmd, new ValidationContext(cmd, null, null), validationResults, true);
if (!isValid) {
return Request.CreateResponse(HttpStatusCode.BadRequest, validationResults);
}
// retrieve Department from DB using the given ID
var department = _departmentDAL.Find(cmd.DepartmentId);
// only update values defined by the usecase
department.Description = cmd.Description;
var ok = _departmentDAL.Update(department);
return Request.CreateResponse(HttpStatusCode.OK, ok);
}
Related
I'm using the latest version of ABP from abp.io and have two entities with a many-many relationship. These are:
public class GroupDto : AuditedEntityDto<Guid>
{
public GroupDto()
{
this.Students = new HashSet<Students.StudentDto>();
}
public string Name { get; set; }
public bool IsActive { get; set; }
public virtual ICollection<Students.StudentDto> Students { get; set; }
}
and
public class StudentDto : AuditedEntityDto<Guid>
{
public StudentDto()
{
this.Groups = new HashSet<Groups.GroupDto>();
}
public string Name { get; set; }
public bool IsActive { get; set; }
public virtual ICollection<Groups.GroupDto> Groups { get; set; }
}
I set up the following test to check that I am retrieving the related entities, and unfortunately the Students property is always empty.
public async Task Should_Get_List_Of_Groups()
{
//Act
var result = await _groupAppService.GetListAsync(
new PagedAndSortedResultRequestDto()
);
//Assert
result.TotalCount.ShouldBeGreaterThan(0);
result.Items.ShouldContain(g => g.Name == "13Ck" && g.Students.Any(s => s.Name == "Michael Studentman"));
}
The same is true of the equivalent test for a List of Students, the Groups property is always empty.
I found one single related answer for abp.io (which is not the same as ABP, it's a newer/different framework) https://stackoverflow.com/a/62913782/7801941 but unfortunately when I add an equivalent to my StudentAppService I get the error -
CS1061 'IRepository<Student, Guid>' does not contain a definition for
'Include' and no accessible extension method 'Include' accepting a
first argument of type 'IRepository<Student, Guid>' could be found
(are you missing a using directive or an assembly reference?)
The code for this is below, and the error is being thrown on the line that begins .Include
public class StudentAppService :
CrudAppService<
Student, //The Student entity
StudentDto, //Used to show students
Guid, //Primary key of the student entity
PagedAndSortedResultRequestDto, //Used for paging/sorting
CreateUpdateStudentDto>, //Used to create/update a student
IStudentAppService //implement the IStudentAppService
{
private readonly IRepository<Students.Student, Guid> _studentRepository;
public StudentAppService(IRepository<Student, Guid> repository)
: base(repository)
{
_studentRepository = repository;
}
protected override IQueryable<Student> CreateFilteredQuery(PagedAndSortedResultRequestDto input)
{
return _studentRepository
.Include(s => s.Groups);
}
}
This implements this interface
public interface IStudentAppService :
ICrudAppService< // Defines CRUD methods
StudentDto, // Used to show students
Guid, // Primary key of the student entity
PagedAndSortedResultRequestDto, // Used for paging/sorting
CreateUpdateStudentDto> // Used to create/update a student
{
//
}
Can anyone shed any light on how I should be accessing the related entities using the AppServices?
Edit: Thank you to those who have responded. To clarify, I am looking for a solution/explanation for how to access entities that have a many-many relationship using the AppService, not the repository.
To aid with this, I have uploaded a zip file of my whole source code, along with many of the changes I've tried in order to get this to work, here.
You can lazy load, eagerly load or configure default behaviour for the entity for sub-collections.
Default configuration:
Configure<AbpEntityOptions>(options =>
{
options.Entity<Student>(studentOptions =>
{
studentOptions.DefaultWithDetailsFunc = query => query.Include(o => o.Groups);
});
});
Eager Load:
//Get a IQueryable<T> by including sub collections
var queryable = await _studentRepository.WithDetailsAsync(x => x.Groups);
//Apply additional LINQ extension methods
var query = queryable.Where(x => x.Id == id);
//Execute the query and get the result
var student = await AsyncExecuter.FirstOrDefaultAsync(query);
Or Lazy Load:
var student = await _studentRepository.GetAsync(id, includeDetails: false);
//student.Groups is empty on this stage
await _studentRepository.EnsureCollectionLoadedAsync(student, x => x.Groups);
//student.Groups is filled now
You can check docs for more information.
Edit:
You may have forgotten to add default repositories like:
services.AddAbpDbContext<MyDbContext>(options =>
{
options.AddDefaultRepositories();
});
Though I would like to suggest you to use custom repositories like
IStudentRepository:IRepository<Student,Guid>
So that you can scale your repository much better.
I'm having trouble retrieving the ID of newly added object in EF Core using the UoW pattern. I have this service:
public class OrderService : IOrderService
{
private IUnitOfWork _uow;
private IOrderRepository _orderRepository;
private IPaymentRepository _paymentRepository;
public OrderService(IUnitOfWork uow,
IOrderRepository orderRepository,
IPaymentRepository paymentRepository)
{
_uow = uow;
_orderRepository = orderRepository;
_paymentRepository = paymentRepository;
}
public int CreateOrder(Logic.Order order)
{
var id = _orderRepository.CreateOrder(order);
var payment = new Data.Payment();
payment.OrderId = id; // at this point, this is -2147353458 something
_paymentRepository.CreatePayment(payment);
// committed at this point but I can't get id unless I re-query
_uow.Commit();
// this is still -2147353458
return id;
}
}
So CreateOrder just adds a new order and then the newly generated ID is returned and used by the Payment object in CreatePayment. The problem with this since after adding, it is not committed yet so EF Core generates a temp id (something like -2147483324) so this is what I get. I then pass this to payment but this part is ok since I think EF is tracking it. The problem is what I return to the UI.
The service is called by the UI and after comitting, I can't get the ID. That's been my problem for hours now.
I've recently came across the same problem as well. Am here just to share my solution for reference.
Rather than to committing the transaction for the Id, you could try utilizing EF relationships.
Ex: the payment and order Model
public class Order
{
public int Id{ get; set; }
public Payment Payment { get; set; }
}
public class Payment
{
public int Id{ get; set; }
public int OrderId{ get; set; }
[ForeignKey("OrderId")]
public Order Order { get; set; }
}
Then in your transaction, you could simply assign the order to payment, EF will automatically insert the created Order Id to payment upon committing the transaction :
public int CreateOrder(Logic.Order order)
{
_orderRepository.CreateOrder(order);
var payment = new Data.Payment();
payment.Order = order;
_paymentRepository.CreatePayment(payment);
_uow.Commit();
return order.id;
}
You need to create an abstract method just like that "void Commit(EntityBase entity)" in your Uow, inside the method call your saveChanges this way you ensure that memory address is the same, so outside of the method you are able to access the property Id, be careful if you're using some Mapper because this may change you Memory Address. Make sure that your are using mapper only after Call UoW.Commit!
public class EntityBase
{
public int Id {get;set}
}
public class AnyOtherEntity : EntityBase
{
}
public void Commit(EntityBase entity)
{
yourContext.SaveChanges(entity);
}
Uow.Commit(AnyOtherEntity);
AnyOtherEntity.Id;
There is no option except commiting the transaction if you want to retrieve the generated unique Id immediately. Also,
// this is still -2147353458
return id;
You can't expect to be changed primitive type after commit. You should get it by order.Id, because after the transaction committed the EF will update entity because EF is tracking the entity.
public int CreateOrder(Logic.Order order)
{
_orderRepository.CreateOrder(order);
// commit the transaction
_uow.Commit();
var payment = new Data.Payment();
// Get the id of inserted row
payment.OrderId = order.Id;
_paymentRepository.CreatePayment(payment);
_uow.Commit();
return order.Id;
}
I'm struggling with using EF6 with DDD principles, namely value objects attached to aggregates. I can't seem to get migrations to generate that reflect the model and I feel like I'm fighting the tooling instead of actually being productive. Given that a NoSQL implementation is probably more appropriate, this is what I'm stuck with.
The first thing that I ran into was the lack of support for interface properties on an EF entity. The work around for that was to add concrete properties to the entity for each of the implementations, but not to the interface. When I implemented the interface, I added logic to return the right one. I had to do this in order to get any migrations to create the properties for the Policies. See Fund.LargestBalanceFirstAllocationPolicy and Fund.PercentageBasedAllocationPolicy This was annoyance one.
The current annoyance and the genesis of the question is the PercentageBasedAllocationPolicy.AllocationValues property. No matter what I do, when running add-migration, I don't get any tables or fields to represent the AllocationValues. This is basically a collection of DDD value objects hanging off of another value object, which hangs off of an aggregate.
I'm convinced that the model and code are correct to do what I want, but EF keeps getting in the way. In MongoDB, when dealing with an interface property, it actually stores the object type in a string so that it knows how to rehydrate the object. I'm considering serializing the problem areas here to a blob and storing it on the object now, which is just as evil...
public interface IFund
{
Guid Id {get;}
string ProperName {get;}
IAllocationPolicy AllocationPolicy{get;}
void ChangeAllocationPolicy(IAllocationPolicy newAllocationPolicy)
}
public class Fund : IFund
{
public Fund()
{
}
public Fund(Guid id, string nickName, string properName)
{
Id = id;
Nickname = nickName;
ProperName = properName;
// This is stupid too, but you have to instantiate these objects inorder to save or you get some EF errors. Make sure the properties on these objects are all defaulted to null.
LargestBalanceFirstAllocationPolicy = new LargestBalanceFirstAllocationPolicy();
PercentageBasedAllocationPolicy = new PercentageBasedAllocationPolicy();
}
public Guid Id { get; private set; }
public string ProperName { get; private set; }
// Do not add this to the interface. It's here for EF reasons only. Do not use internally either. Use the interface implemention of AllocationPolicy instead
public LargestBalanceFirstAllocationPolicy LargestBalanceFirstAllocationPolicy
{
get; private set;
}
// Do not add this to the interface. It's here for EF reasons only. Do not use internally either. Use the interface implemention of AllocationPolicy instead
public PercentageBasedAllocationPolicy PercentageBasedAllocationPolicy
{
get; private set;
}
public void ChangeAllocationPolicy(IAllocationPolicy newAllocationPolicy)
{
if (newAllocationPolicy == null) throw new DomainException("Allocation policy is required");
var allocationPolicy = newAllocationPolicy as PercentageBasedAllocationPolicy;
if (allocationPolicy != null) PercentageBasedAllocationPolicy = allocationPolicy;
var policy = newAllocationPolicy as LargestBalanceFirstAllocationPolicy;
if (policy != null ) LargestBalanceFirstAllocationPolicy = policy;
}
public IAllocationPolicy AllocationPolicy
{
get {
if (LargestBalanceFirstAllocationPolicy != null)
return LargestBalanceFirstAllocationPolicy;
if (PercentageBasedAllocationPolicy != null)
return PercentageBasedAllocationPolicy;
return null;
}
}
}
public interface IAllocationPolicy
{
T Accept<T>(IAllocationPolicyVisitor<T> allocationPolicyVisitor);
}
public class LargestBalanceFirstAllocationPolicy : IAllocationPolicy
{
public T Accept<T>(IAllocationPolicyVisitor<T> allocationPolicyVisitor)
{
return allocationPolicyVisitor.Visit(this);
}
}
[ComplexType]
public class PercentageBasedAllocationPolicy : IAllocationPolicy
{
public PercentageBasedAllocationPolicy()
{
AllocationValues = new List<PercentageAllocationPolicyInfo>();
}
public List<PercentageAllocationPolicyInfo> AllocationValues { get; private set; }
public T Accept<T>(IAllocationPolicyVisitor<T> allocationPolicyVisitor)
{
return allocationPolicyVisitor.Visit(this);
}
}
[ComplexType]
public class PercentageAllocationPolicyInfo
{
public Guid AssetId { get; private set; }
public decimal Percentage { get; private set; }
}
A value type (in EF marked as ComplexType) will never have any tables. The reason being is that a value types are (by definition) really just values. They don't have any Id( otherwise they would be enities) thus you can't create a table for them.
also if i review the requirements for complex type in entity framework https://msdn.microsoft.com/en-us/library/bb738472(v=vs.100).aspx i notice that you can't use inheritance on complex types. Thus if you want to use complex type in your entity framework as you've shown here then you need to make your property a PercentageBasedAllocationPolicy instead of an IAllocationPolicy.
Alternatively you could turn it into an entity with automatic generated keys.
In my Azure Mobile Service I have a controller class UserController : TableController<User> and in it is a get method:
// GET tables/User/48D68C86-6EA6-4C25-AA33-223FC9A27959
public SingleResult<User> GetUser(string id)
{
return Lookup(id);
}
I want to record each time a user is accessed and so I add a simple type to the model:
public class UserVisit : Microsoft.WindowsAzure.Mobile.Service.EntityData
{
public string VisitingUser { get; set; }
public DateTime TimeOfVisit { get; set; }
}
and include the property public DbSet<UserVisit> UserVisits { get; set; } in my VCollectAPIContext : DbContext class (and update the database with a code-first migration).
To add a UserVisit to the database when a user id is queried I change my controller method to
// GET tables/User/48D68C86-6EA6-4C25-AA33-223FC9A27959
public async Task<SingleResult<User>> GetUser(string id)
{
var userVisit = new UserVisit { VisitingUser = id, TimeOfVisit = DateTime.UtcNow };
context.UserVisits.Add(userVisit);
await context.SaveChangesAsync();
return Lookup(id);
}
But the SaveChangesAsync fails with a System.Data.Entity.Validation.DbEntityValidationException. Digging around in the exception's EntityValidationErrors property I find that the problem is "The Id field is required."
That's a little odd. The Id field is one of the properties in the base-class Microsoft.WindowsAzure.Mobile.Service.EntityData that I would expect to be added automatically on insert. No matter, I can add it and several of the other base-class's properties thus:
// GET tables/User/48D68C86-6EA6-4C25-AA33-223FC9A27959
public async Task<SingleResult<User>> GetUser(string id)
{
var userVisit = new UserVisit { Id = Guid.NewGuid().ToString(), Deleted = false, VisitingUser = id, TimeOfVisit = DateTime.UtcNow, CreatedAt = DateTimeOffset.Now };
context.UserVisits.Add(userVisit);
await context.SaveChangesAsync();
return Lookup(id);
}
This time I get a System.Data.Entity.Infrastructure.DbUpdateException because we "Cannot insert the value NULL into column 'CreatedAt'". It was not null in the call to Add. So CreatedAt has been set to null somewhere outside my code and then the insert fails as a result!
I also tried setting up an EntityDomainManager<UserVisit> userVisitDomainManager; instance variable in the controller's initializer, and then rewriting my controller get method as
// GET tables/User/48D68C86-6EA6-4C25-AA33-223FC9A27959
public async Task<SingleResult<User>> GetUser(string id)
{
var userVisit = new UserVisit { VisitingUser = id, TimeOfVisit = DateTime.UtcNow };
await userVisitDomainManager.InsertAsync(userVisit);
return Lookup(id);
}
That fails with the same message, "Cannot insert the value NULL into column 'CreatedAt'"
How should I perform the seemingly simple task of inserting a related data item within my controller method?
The solution is likely similar to this answer. I'm guessing that your migration is not using the Mobile Services SqlGenerator so some of the custom SQL settings aren't getting applied. What that means is that:
Id doesn't get a default value of NEWID() -- this explains your "Id field is required" error.
CreatedAt doesn't get a default value of SYSUTCDATETIME() -- this, combined with the [DatabaseGenerated] attribute on EntityData.CreatedAt, explains the "NULL CreatedAt" error.
Try updating your migration according to the link above and see if that works for you.
To fix the problem of "The Id field is required" following brettsam's instructions.
Add this in your model:
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[TableColumn(TableColumnType.Id)]
public new string Id { get; set; }
It will auto generate a GUID when you add an entity.
I feel like this should be a pretty common thing to do. I have a model with a related object on it. Let's say it's a User and a user has one Role.
public class User
{
public int Id { get; set; }
public virtual Role Role { get; set; }
/* other stuff that saves fine */
}
public class Role
{
public int Id {get;set;}
public string Name { get;set;}
}
So if I save a new user, or if I edit a user (but don't change his Role), I have no issues. If I have a user without a role, and add a role to him, again no problem (though I manually lookup the role and assign it). If I try and change a role, I get a modelstate error on the Role property that the ID is part of the object's key and can't be changed. So how do folks go about making updates like this? Whitelist the simple values and then manually update the Role?
My controller code in question is here:
[HttpPost]
public ActionResult Save(int id, FormCollection form)
{
var user = data.Users.FirstOrDefault(d=> d.Id == id);
if (user != null)
{
TryUpdateModel(user, form.ToValueProvider());
if (!ModelState.IsValid)
{
var messages = ModelState.Values.Where(m => m.Errors.Count() > 0).SelectMany(m=>m.Errors).Select(e => e.ErrorMessage);
if (Request.IsAjaxRequest())
return Json(new { message = "Error!", errors = messages });
return RedirectToAction("index"); // TODO: more robust Flash messaging
}
updateDependencies(user);
/* negotiate response */
}
}
I'll probably just do it manually for now, but it seems like a scenario that I would have expected to work out of the box, at least to some degree.
Your User model should have a foreign key:
public int? RoleId { get; set; }
public virtual Role Role { get; set; }
You can assign a Role.Id to this value, or make it null when the user does not have a role.
I'm also not sure if your Save function is correct. I'm always using this pattern (not sure if it is correct either...), but of course it depends on the data you post to the server:
[HttpPost]
public ActionResult Save(User model)
{
if (ModelState.IsValid)
{
// Save logic here, for updating an existing entry it is something like:
context.Entry(model).State = EntityState.Modified;
context.SaveChanges();
return View("Success");
}
return View("Edit", model);
}