I'm using Web API + AbpOData + EF and need to calculate some properties of the objects returned from the database on the server.
The basic code looks something like this:
[AbpApiAuthorize(AppPermissions.OData_Permission_Consume)]
public class ActivityLogsController : AbpODataEntityController<ActivityLogs>
{
[EnableQuery(PageSize = 50000)]
public override IQueryable<ActivityLogs> Get()
{
var objectContext = new MyObjectContext(); //EF
return objectContext.ActivityLogs.GetAll();
}
}
I'm just returning values from database, all's fine.
However what I need is to Convert two datetime value to local time. Like below
[AbpApiAuthorize(AppPermissions.OData_Permission_Consume)]
public class ActivityLogsController : AbpODataEntityController<ActivityLogs>
{
[EnableQuery(PageSize = 50000)]
public override IQueryable<ActivityLogs> Get()
{
var objectContext = new MyObjectContext(); //EF
return objectContext.ActivityLogs.Select(d => new ActivityLogs()
{
Id = d.ID,
Activity = d.Activity,
StartTime = d.StartTime.Value.AddHours(5),
EndTime = d.EndTime.Value.AddHours(5),
Duration = d.Duration
});
}
}
I getting below error
The entity or complex type 'ActivityLogs' cannot be constructed in a LINQ to Entities query.
how i can impliment this using abp odata framework(.net zero). keeping in mind that we need to return the same IQueryable that's returned from EF call.
The error is caused by impossibility to transform AddHours method to SQL.
You have 2 options:
Create a view in DB where you will keep your additional logic.
Add your business for DateTime properties in your client side.
Related
In an api I have built using dotnet 3.1, I have the Data access model for entity "Quotation" as follows.
public class QuotationDao : BaseEntity
{
public double Amount { get; set; }
public DateTime FromDate { get; set; }
public DateTime ToDate { get; set; }
public string Status { get; set; }
public Guid CustomerId { get; set; }
public CustomerDao Customer { get; set; }
public virtual ICollection<QuoteItemDao> Items { get; set; }
}
When I create a new instance of the entity 'QuoteItem', I want to increase the 'amount' attribute of the 'Quotation' entity by 1. And update the database.
The implementation of the BaseController.cs is shown below.
public class BaseController<TDao, TCreateRq, TUpdateRq, TResponse> : ControllerBase
where TDao : BaseEntity where TResponse : BaseResponse
{
private readonly IRepositoryWrapper _repositoryWrapper;
private readonly IRepositoryBase<TDao> _repository;
private readonly IMapper _mapper;
public BaseController(IRepositoryWrapper repositoryWrapper, IRepositoryBase<TDao> repository, IMapper mapper)
{
_repositoryWrapper = repositoryWrapper;
_repository = repository;
_mapper = mapper;
}
[HttpGet]
public virtual async Task<ActionResult<PagedResponse<TResponse>>> GetAll([FromQuery] StringQueryRequest request)
{
var paginationFilter = _mapper.Map<PaginationFilter>(request);
var pagedResponse = await _repository.ToPagedList
(_repository.FindAll(request.SearchString), paginationFilter);
var mapPagination = pagedResponse.MapPagination<TResponse, TDao>(_mapper);
return mapPagination.HandleToResponse();
}
[HttpGet("{id:guid}")]
public virtual async Task<ActionResult<Response<TResponse>>> GetById(Guid id)
{
var result = await GetDtoById(id);
return result.HandleGetToResponse();
}
[HttpPost]
public virtual async Task<ActionResult<Response<TResponse>>> Create([FromBody] TCreateRq request)
{
var entity = _mapper.Map<TDao>(request);
_repository.Create(entity);
await _repositoryWrapper.SaveAsync();
var baseResponse = _mapper.Map<TResponse>(entity);
var result = new Result<TResponse>(baseResponse);
return result.HandleToResponse();
}
[HttpPut("{id:guid}")]
public virtual async Task<ActionResult<Response<TResponse>>> UpdateById([FromRoute] Guid id,
[FromBody] TUpdateRq updateRq)
{
var getResult = await GetDtoById(id);
if (getResult.IsNone)
{
return getResult.HandleGetToResponse();
}
var value = await _repository.FindById(id);
//override the current DB objet from the values received from the request
//Not replacing the full object from the requests as some fields may be missing from the update RQ model (eg user password)
var entity = _mapper.Map(updateRq, value);
_repository.Update(entity);
await _repositoryWrapper.SaveAsync();
var baseResponse = _mapper.Map<TResponse>(entity);
var result = new Result<TResponse>(baseResponse);
return result.HandleToResponse();
}
[HttpDelete("{id:guid}")]
public virtual async Task<ActionResult<Response<TResponse>>> DeleteById([FromRoute] Guid id)
{
var getResult = await GetDtoById(id);
if (getResult.IsNone)
{
return getResult.HandleGetToResponse();
}
var baseResponse = getResult.ValueUnsafe();
var entity = _mapper.Map<TDao>(baseResponse);
_repository.Delete(entity);
await _repositoryWrapper.SaveAsync();
var result = new Result<TResponse>(baseResponse);
return result.HandleToResponse();
}
private async Task<Option<TResponse>> GetDtoById(Guid id)
{
var findById = await _repository.FindById(id);
return _mapper.Map<TResponse>(findById);
}
}
I cant figure out a way to implement this and I'd be grateful if someone could give some help.
This is the problem with relying on a purely Generic approach. Generic implies that regardless of what you pass in, they can be treated within the scope of the Generic class as identical. That said, as a base implementation, what you have isn't bad, but you are going to hit some limitations since a Generic implementation represents a "lowest common denominator" in terms of leveraging what EF can bring to the table.
Your issue can be addressed by structuring your controllers around aggregate roots and performing operations through those roots. The trouble you will find when working with related entities is that your controllers will need to know about these relationships in order to ensure required details are eager loaded to work with. You can leverage a Generic base class for controllers and repositories, but the actual controllers and repositories would be scoped around the aggregate root such as the Quotation. Adding quotation items would be done through its root (Quotation) rather than having something like a QuotationItemController and QuotationItemRepository. The goal of the QuotationController would be to support methods that can load a Quotation with it's respective items and have a method like AddQuotationItem. AddQuotationItem would then load the Quotation by ID, eager loading the items, validate the input for a QuotationItem, Create the QuotationItem, append it to the quote.Items, and increment the quote's Amount before saving the changes.
The key here would be to not think of fitting everything into a Generic pattern, but more-so leveraging a Generic if needed for elements of the controller or repositories etc. that are identical.
Some other tips: Which mapper are you using? If it is Automapper then consider using ProjectTo<TDestination>(config) rather than Map<TDestination>() when projecting to the view model. The benefit here is that the projection works across the IQueryable rather than introducing the overhead of fetching Entities and counting on related entities to be eager loaded or waking the performance troll that is lazy loading. This will be problematic with methods like FindAll if that is returning IEnumerable<TEntity> but if it is returning IQueryable<TEntity> then you should be set. Passing FindAll to something like ToPagedList won't save performance hits if FindAll is returning IEnumerable. When converting back from a view model to a DTO, provided your entities are tracked you should avoid using Update and just use Automapper's
mapper.Map(updateRq, value) which will copy the values from source to destination. If Destination is a tracked entity, then just call SaveChanges and EF will build an appropriate UPDATE statement for just the values that changed if any values actually changed. (Update will generate an UPDATE statement for all values even if nothing actually changed.)
That Update pattern is more a case of using:
var dao = mapper.Map<TEntity>(dto);
context.Update(dao);
context.SaveChanges();
which I would never recommend using over:
var dao = context.Entities.Single(x => x.Id == dto.Id);
mapper.Map(dto, dao);
context.SaveChanges();
As the later pattern validates that the dto passed in has a corresponding entity, Automapper should be configured to only copy across allowed properties according to configured rules, then SaveChanges would produce an efficient UPDATE SQL statement only if anything actually changed.
Following is the action that is adding a Loan request to the database:
[HttpPost]
public ActionResult Add(Models.ViewModels.Loans.LoanEditorViewModel loanEditorViewModel)
{
if (!ModelState.IsValid)
return View(loanEditorViewModel);
var loanViewModel = loanEditorViewModel.LoanViewModel;
loanViewModel.LoanProduct = LoanProductService.GetLoanProductById(loanViewModel.LoanProductId); // <-- don't want to add to this table in database
loanViewModel.Borrower = BorrowerService.GetBorrowerById(loanViewModel.BorrowerId); //<-- don't want to add to this table in database
Models.Loans.Loan loan = AutoMapper.Mapper.Map<Models.Loans.Loan>(loanEditorViewModel.LoanViewModel);
loanService.AddNewLoan(loan);
return RedirectToAction("Index");
}
Following is the AddNewLoan() method:
public int AddNewLoan(Models.Loans.Loan loan)
{
loan.LoanStatus = Models.Loans.LoanStatus.PENDING;
_LoanService.Insert(loan);
return 0;
}
And here is the code for Insert()
public virtual void Insert(TEntity entity)
{
if (entity == null)
throw new ArgumentNullException(nameof(entity));
try
{
entity.DateCreated = entity.DateUpdated = DateTime.Now;
entity.CreatedBy = entity.UpdatedBy = GetCurrentUser();
Entities.Add(entity);
context.SaveChanges();
}
catch (DbUpdateException exception)
{
throw new Exception(GetFullErrorTextAndRollbackEntityChanges(exception), exception);
}
}
It is adding one row successfully in Loans table but it is also adding rows to LoanProduct and Borrower table as I showed in first code comments.
I checked the possibility of multiple calls to this action and Insert method but they are called once.
UPDATE
I am facing similar problem but opposite in functioning problem here: Entity not updating using Code-First approach
I think these two have same reason of Change Tracking. But one is adding other is not updating.
The following code seems a bit odd:
var loanViewModel = loanEditorViewModel.LoanViewModel;
loanViewModel.LoanProduct = LoanProductService.GetLoanProductById(loanViewModel.LoanProductId); // <-- don't want to add to this table in database
loanViewModel.Borrower = BorrowerService.GetBorrowerById(loanViewModel.BorrowerId); //<-- don't want to add to this table in database
Models.Loans.Loan loan = AutoMapper.Mapper.Map<Models.Loans.Loan>(loanEditorViewModel.LoanViewModel);
You are setting entity references on the view model, then calling automapper. ViewModels should not hold entity references, and automapper should effectively be ignoring any referenced entities and only map the entity structure being created. Automapper will be creating new instances based on the data being passed in.
Instead, something like this should work as expected:
// Assuming these will throw if not found? Otherwise assert that these were returned.
var loanProduct = LoanProductService.GetLoanProductById(loanViewModel.LoanProductId);
var borrower = BorrowerService.GetBorrowerById(loanViewModel.BorrowerId);
Models.Loans.Loan loan = AutoMapper.Mapper.Map<Models.Loans.Loan>(loanEditorViewModel.LoanViewModel);
loan.LoanProduct = loanProduct;
loan.Borrower = borrower;
Edit:
The next thing to check is that your Services are using the exact same DbContext reference. Are you using Dependency Injection with an IoC container such as Autofac or Unity? If so, make sure that the DbContext is set registered as Instance Per Request or similar lifetime scope. If the Services effectively new up a new DbContext then the LoanService DbContext will not know about the instances of the Product and Borrower that were fetched by another service's DbContext.
If you are not using a DI library, then you should consider adding one. Otherwise you will need to update your services to accept a single DbContext with each call or leverage a Unit of Work pattern such as Mehdime's DbContextScope to facilitate the services resolving their DbContext from the Unit of Work.
For example to ensure the same DbContext:
using (var context = new MyDbContext())
{
var loanProduct = LoanProductService.GetLoanProductById(context, loanViewModel.LoanProductId);
var borrower = BorrowerService.GetBorrowerById(context, loanViewModel.BorrowerId);
Models.Loans.Loan loan = AutoMapper.Mapper.Map<Models.Loans.Loan>(loanEditorViewModel.LoanViewModel);
loan.LoanProduct = loanProduct;
loan.Borrower = borrower;
LoanService.AddNewLoan(context, loan);
}
If you are sure that the services are all provided the same DbContext instance, then there may be something odd happening in your Entities.Add() method. Honestly your solution looks to have far too much abstraction around something as simple as a CRUD create and association operation. This looks like a case of premature code optimization for DRY without starting with the simplest solution. The code can more simply just scope a DbContext, fetch the applicable entities, create the new instance, associate, add to the DbSet, and SaveChanges. There's no benefit to abstracting out calls for rudimentary operations such as fetching a reference by ID.
public ActionResult Add(Models.ViewModels.Loans.LoanEditorViewModel loanEditorViewModel)
{
if (!ModelState.IsValid)
return View(loanEditorViewModel);
var loanViewModel = loanEditorViewModel.LoanViewModel;
using (var context = new AppContext())
{
var loanProduct = context.LoanProducts.Single(x => x.LoanProductId ==
loanViewModel.LoanProductId);
var borrower = context.Borrowers.Single(x => x.BorrowerId == loanViewModel.BorrowerId);
var loan = AutoMapper.Mapper.Map<Loan>(loanEditorViewModel.LoanViewModel);
loan.LoanProduct = loanProduct;
loan.Borrower = borrower;
context.SaveChanges();
}
return RedirectToAction("Index");
}
Sprinkle with some exception handling and it's done and dusted. No layered service abstractions. From there you can aim to make the action test-able by using an IoC container like Autofac to manage the Context and/or introducing a repository/service layer /w UoW pattern. The above would serve as a minimum viable solution for the action. Any abstraction etc. should be applied afterwards. Sketch out with pencil before cracking out the oils. :)
Using Mehdime's DbContextScope it would look like:
public ActionResult Add(Models.ViewModels.Loans.LoanEditorViewModel loanEditorViewModel)
{
if (!ModelState.IsValid)
return View(loanEditorViewModel);
var loanViewModel = loanEditorViewModel.LoanViewModel;
using (var contextScope = ContextScopeFactory.Create())
{
var loanProduct = LoanRepository.GetLoanProductById( loanViewModel.LoanProductId).Single();
var borrower = LoanRepository.GetBorrowerById(loanViewModel.BorrowerId);
var loan = LoanRepository.CreateLoan(loanViewModel, loanProduct, borrower).Single();
contextScope.SaveChanges();
}
return RedirectToAction("Index");
}
In my case I leverage a repository pattern that uses the DbContextScopeLocator to resolve it's ContextScope to get a DbContext. The Repo manages fetching data and ensuring that the creation of entities are given all required data necessary to create a complete and valid entity. I opt for a repository-per-controller rather than something like a generic pattern or repository/service per entity because IMO this better manages the Single Responsibility Principle given the code only has one reason to change (It serves the controller, not shared between many controllers with potentially different concerns). Unit tests can mock out the repository to serve expected data state. Repo get methods return IQueryable so that the consumer logic can determine how it wants to consume the data.
Finally with the help of the link shared by #GertArnold Duplicate DataType is being created on every Product Creation
Since all my models inherit a BaseModel class, I modified my Insert method like this:
public virtual void Insert(TEntity entity, params BaseModel[] unchangedModels)
{
if (entity == null)
throw new ArgumentNullException(nameof(entity));
try
{
entity.DateCreated = entity.DateUpdated = DateTime.Now;
entity.CreatedBy = entity.UpdatedBy = GetCurrentUser();
Entities.Add(entity);
if (unchangedModels != null)
{
foreach (var model in unchangedModels)
{
_context.Entry(model).State = EntityState.Unchanged;
}
}
_context.SaveChanges();
}
catch (DbUpdateException exception)
{
throw new Exception(GetFullErrorTextAndRollbackEntityChanges(exception), exception);
}
}
And called it like this:
_LoanService.Insert(loan, loan.LoanProduct, loan.Borrower);
By far the simplest way to tackle this is to add the two primitive foreign key properties to the Loan class, i.e. LoanProductId and BorrowerId. For example like this (I obviously have to guess the types of LoanProduct and Borrower):
public int LoanProductId { get; set; }
[ForeignKey("LoanProductId")]
public Product LoanProduct { get; set; }
public int BorrowerId { get; set; }
[ForeignKey("BorrowerId")]
public User Borrower { get; set; }
Without the primitive FK properties you have so-called independent associations that can only be set by assigning objects of which the state must be managed carefully. Adding the FK properties turns it into foreign key associations that are must easier to set. AutoMapper will simply set these properties when the names match and you're done.
Check Models.Loans.Loan?Is it a joined model of Loans table , LoanProduct and Borrower table.
You have to add
Loans lentity = new Loans()
lentity.property=value;
Entities.Add(lentity );
var lentity = new Loans { FirstName = "William", LastName = "Shakespeare" };
context.Add<Loans >(lentity );
context.SaveChanges();
I've got an OData WebAPI method as follows:
// GET: odata/Employees
[EnableQuery]
public IQueryable<DTOs.Employee> GetEmployees()
{
return this.AttemptOperation(context =>
{
IQueryable<DTOs.Employee> employees
= context.Employees.Project().To<DTOs.Employee>();
return employees;
});
}
It returns data to to the service if I don't specify a filter.
But as soon as I add $filter=EmployeeID eq '1' to the URL I get an exception.
The exception is from an AWS DynamoDB context library I use for performing LINQ queries against DynamoDB. However, what it is indicating is that the context doesn't have a table for DTOs.Employee.
This of course is obvious, the context has the entities, not the DTOs.
How can I get the IQueryable where clause specified from the client to translate back to the proper entity type?
For example, the client needs to query against DTOs.Employee.EmployeeID and it needs to translate into a where clause against Entities.Employee.EmployeeID.
Easy:
public IQueryable<DTOs.Employee> GetEmployees()
{
return this.AttemptOperation(context =>
{
// I commented here your old code:
// IQueryable<DTOs.Employee> employees = context.Employees.Project().To<DTOs.Employee>();
// This is our new code:
var employees = context.Employees.Project()
return employees.select(m=> new EmployeeDto {
property1 = m.property1,
property2 = m.property2
}
});
}
Important: You cannot use an EmployeeDto constructor. It is forbidden to do so under Linq.
I am working on an existing application the uses the Generic Repo pattern and EF6 database first.
I am calling a stored proc that returns a complex type that is not an existing entity in my entity models and therefore I am not sure what type to give.
This is how my sp is being called from my service layer
_unitOfWork.Repository<Model>()
.SqlQuery("sp_Get #FromDateTime, #ToDateTime, #CountyId",
new SqlParameter("FromDateTime", SqlDbType.DateTime) { Value = Request.FromDateTime },
new SqlParameter("ToDateTime", SqlDbType.DateTime) { Value = Request.TripToDateTime },
new SqlParameter("CountyId", SqlDbType.Int) { Value = Convert.ToInt32(Request.County) }
).ToList();
Do I create an Entity in my data layer to map to or what is the best approach for stored procedures returning complex types.
If so is there custom mapping needed or is it just a case of creating the Entity class
thank you
If you have an entity with those fields you can call SqlQuery method as you show above, if not, then I suggest creating a new class to map the result:
public class Result
{
public int CountyId { get; set; }
public DateTime FromDateTime { get; set; }
public DateTime ToDateTime { get; set; }
}
I don't know how is implemented the UnitOfWork pattern in your case, but I assume that you have access to your Context. In your UnitOfWork class you could create a generic method like this:
public class UnitOfWork
{
private YourContext Context { get; set; }
public DbRawSqlQuery<T> SQLQuery<T>(string sql, params object[] parameters)
{
return Context.Database.SqlQuery<T>(sql, parameters);
}
}
This way, you can execute your store procedures as I show below:
var result= _unitOfWork.SqlQuery<Result>("sp_Get #FromDateTime, #ToDateTime, #CountyId",
new SqlParameter("FromDateTime", SqlDbType.DateTime) { Value = Request.FromDateTime },
new SqlParameter("ToDateTime", SqlDbType.DateTime) { Value = Request.TripToDateTime },
new SqlParameter("CountyId", SqlDbType.Int) { Value = Convert.ToInt32(Request.County) }
).ToList();
The purpose of the Repository Pattern is to abstract away the storage & retrieval of data to protect your client code e.g. business layer (service layer in your case) from needing to know anything about how data is persisted. SQL statements, for example, would only exist inside your Repository classes, and not ripple throughout your code.
If you expose SQL, Stored Procedure names and parameters to your client code your are not getting much benefit from the Repository Pattern, and if fact you can't really call it a Repository at all. You lose the benefit of being able to mock the repository and test your business layer independently of your data access layer. This means integration tests (requiring a full database instance) are required to verify business logic.
Consider re-factoring so that you have a CountryRepository class which has a GetCountry(int CountryId, DateTime fromDate, DateTime toDate) method that returns a Country entity, or similar. I think you'll agree the readability of your code will be much improved compared to the code in your question.
public class CountryRepository
{
public Country GetCountry(int CountryId, DateTime fromDate, DateTime toDate)
{
// EF or ADO.NET code here
}
}
Client code would then be e.g.
var c = unitOfWork.CountryRepository.GetCountry(1, DateTime.Now.AddYears(-1), DateTime.Now);
See also this SO question
public virtual IEnumerable<T> GetWithRawSql(string query, params object[] parameters)
{
return DbSet.SqlQuery(query, parameters).ToList();
}
Interface
IEnumerable<T> GetWithRawSql(string query, params object[] parameters);
IQueryable<Cm_Customer> customerQuery = _uow.SqlQuery<Cm_Customer>(#" DECLARE #UserId INT = {0}
EXEC Cm_GetCustomersByUserId #UserId", filter.UserId).AsQueryable();
IQueryable<Cm_Customer> custs = customerQuery.IncludeMultiple(k => k.Cm_CustomerLocations,
k => k.Cm_CustomerSalesmans,
k => k.Cm_CustomerMachineparks,
k => k.Cm_CustomerAuthenticators,
k => k.Cm_CustomerInterviews,
k => k.Cm_CustomerRequest,
k => k.Cm_MachineparkRental).AsQueryable();
I have a problem in architecting my application.
I have the following structure with only important aspects shown.
namespace Domain
{
public class Invoice
{
//properties
}
public class InvoiceRepository
{
public Linq2SqlContext context = new Linq2SqlContext();
public IQueryable<Invoice> GetInvoices()
{
var query = from inv in _dbctx.Invoices orderby inv.invInvoiceDate descending select GetInvoice(inv) ;
return query;
}
}
public class InvoiceService()
{
public InvoiceRepository _repository = new InvoiceRepositroy();
public IQueryable<Invoice> GetInvoices()
{
return _repository.GetInvoices();
}
}
}
namespace MyApp
{
public class UI
{
public InvoiceService _service = new InvoiceService();
public void FilterInvoices()
{
var query =
(
from i in _service.GetInvoices()
from s in _service.GetStatuses()
where i.ProjectID == _projectid &&
s.ID == i.Status
select new
{
InvoiceID = i.ID,
DocumentTotal = i.TotalDue.ToString(),
Created = i.Created,
WeekEnding = i.WeekEnding,
Status = s.staStatus
}
).Skip(_pageIndex * _pageSize).Take(_pageSize);
}
}
{
So I want to return IQueryable from my service so I can
filter from client code. But the problem I'm coming up with
is the FilterInvoices method errors with "No supported
translation to sql" because of the GetInvoice method
which is iused to return an Invoice entity (this is
a layer on top op the LInq2 sql layer) and not an Linq2sql Invoice entity.
So how do I return a IQueryable from my service with this structure??
Also how do I sort and return a IQureyable in repository GetInvoices.
Hope this makes sense.
Malcolm
linq2sql thinks GetInvoice (within GetInvoices) is a stored procedure. One way around it
var query = from inv in _dbctx.Invoices orderby inv.invInvoiceDate descending select inv ;
though that would pass back the objects generated by your datacontext. If you wanted to populated custom objects you could iterated over the collection creating your custom Invoice objects and populating them.
foreach(var inv in query) { somelist.Add(new MyCustomInvoince() { id = inv.id ... }
EDIT: The above will return a list. Use the following to return IQueryable
return from item in query
select GetInvoice(item);
The difference is at this stage your are using Linq2Objects, and that provider will know how to call GetInvoice
You cannot query with LTS (Linq to SQL) something built 'on top' of the LTS layer.
The reason is that the LTS entities layer is a mapping of the content of you database, and the query you perform is 'just' translated into SQL.
I personnaly use another approach to keep an independance between my layers...
I create interfaces that match my LTS entites, and I use the Cast<>() method to have my repository return the interface instead of the concrete implementation.
It works perfectly.
You need to extend the base entity (no pb as it is a partial class) :
partial class Employee : IEmployee
And you need this property in your repository :
public IQueryable<IEmployee> Query
{
get
{
return this._context.Employees.Cast<IEmployee>();
}
}
Based on this, you could code a generic repository, but it's another story (more complicated)
This is just off the top of my head, but you could try:
from i in _service.GetInvoices().AsEnumerable()