Thank you Halil :)
Have a great day!
Thank you very much.
So does this then mean that the repository interface and implementation of it must reside within the Core assembly?
Currently I have the interface in the Core assembly and the implementation of it in the EntityFramework assembly. I am running into the problem of not being able to reference the custom repository in the core module class because of that.
What is the correct way to implement this?
Ok I understand that is now a requirement due to the way EF works.
Because I am currently hosting the application database on my local machine for development purposes, enabling MSDTC is not a problem for me.
The database that I am reading the data via stored procedures is remote though and for me to enable network access for MSDTC to allow coordination between local and remote, firewall settings needs to be changed, which the network administrator is very reluctant to do.
I will try and convince him that that is what needs to be done for this to work.
Thank you Halil, that worked beautifully! Not sure why it didn't work the 1st time, maybe I did something wrong during my many fiddles to try and get it working.
Now I am still faced with the TransactionScope issue that requires me to have MSDTC enabled. Like I said I don't think it will be an option.
Is there any other way around this?
Hi,
Have you check your permissions setup on the user?
You should have permission to the navigation for administration and dashboard if you log in as admin and from there you can assign the permission to the user account if a new one has been created.
Yes I did think that my approach of instantiating the dbContext the manually is not really correct, but it was desperate times for me :oops:
I have run into a problem now with that approach in that when reading data using the 1st dbContext and reading data using the 2nd dbContext causes an error which requires me to have MSDTC running on my local machine (which hosts the database of the 1st dbContext) and MSDTC running on the remote machine (which hosts the database of the 2nd dbContext).
I don't think it will be an option to allow MSDTC network access on the remote machine.
I am going to try your suggested approach again (I am almost sure I did try that before and it didn't work).
Question I have is, do I have to enable migrations on the 2nd dbContext for this to work? The reason I ask is because I won't be migrating my entities to the database as this database is used only for read purposes.
Hi,
I have tried that but was unsuccessful in my attempt. I then reverted back to the way it was before.
Here is my 2nd dbContext:
using Abp.EntityFramework;
using Caerus.Workshop.Servicing;
using System.Data.Common;
using System.Data.Entity;
namespace Caerus.EntityFramework
{
public class AutolineDbContext : AbpDbContext
{
public virtual IDbSet<ServicesDueAggregate> ServicesDueAggregate { get; set; }
public AutolineDbContext()
: base("Autoline")
{
}
public AutolineDbContext(string nameOrConnectionString)
: base(nameOrConnectionString)
{
}
/* This constructor is used in tests to pass a fake/mock connection.
*/
public AutolineDbContext(DbConnection dbConnection)
: base(dbConnection, true)
{
}
}
}
Here is my repository base for my 2nd dbContext:
using Abp.Domain.Entities;
using Abp.EntityFramework;
using Abp.EntityFramework.Repositories;
namespace Caerus.EntityFramework.Repositories
{
public abstract class AutolineRepositoryBase<TEntity, TPrimaryKey> : EfRepositoryBase<AutolineDbContext, TEntity, TPrimaryKey>
where TEntity : class, IEntity<TPrimaryKey>
{
protected AutolineRepositoryBase(IDbContextProvider<AutolineDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
}
public abstract class AutolineRepositoryBase<TEntity> : AutolineRepositoryBase<TEntity, int>
where TEntity : class, IEntity<int>
{
protected AutolineRepositoryBase(IDbContextProvider<AutolineDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
}
}
Here is my custom repository's interface:
using Abp.Dependency;
using Abp.Domain.Repositories;
using Caerus.Workshop.Servicing;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Caerus.Workshop.Servicing.Repositories
{
public interface IServicesDueAggregateRepository : IRepository<ServicesDueAggregate>, ITransientDependency
{
List<ServicesDueAggregate> GetServicesDueAggregationByType(string Type, DateTime? startDate, DateTime? endDate);
}
}
Here is my implementation of custom repository:
using System;
using System.Collections.Generic;
using Abp.EntityFramework;
using Caerus.Workshop.Servicing;
using System.Linq;
using System.Data.SqlClient;
using Caerus.Workshop.Servicing.Repositories;
namespace Caerus.EntityFramework.Repositories.Workshop.Servicing
{
public class ServicesDueAggregateRepository : AutolineRepositoryBase<ServicesDueAggregate>, IServicesDueAggregateRepository
{
public ServicesDueAggregateRepository(IDbContextProvider<AutolineDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
public List<ServicesDueAggregate> GetServicesDueAggregationByType(string locationCode, DateTime? startDate, DateTime? endDate)
{
var LocationCode = new SqlParameter { ParameterName = "@locationCode", SqlDbType = System.Data.SqlDbType.VarChar, Size = 3, Value = locationCode };
var StartDate = new SqlParameter { ParameterName = "@startDate", SqlDbType = System.Data.SqlDbType.Date, Value = startDate };
var EndDate = new SqlParameter { ParameterName = "@endDate", SqlDbType = System.Data.SqlDbType.Date, Value = endDate };
var result = Context.Database.SqlQuery<ServicesDueAggregate>(
"EXEC dbo.sp_FK_Agreement_ServicesDue_By_Type_Count @LocationCode, @StartDate, @EndDate",
LocationCode,
StartDate,
EndDate
).ToList();
return result;
}
}
}
I ended up implementing it as follows and there is communication with the database now:
using Caerus.Workshop.Servicing;
using Caerus.Workshop.Servicing.Repositories;
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
namespace Caerus.EntityFramework.Repositories.Workshop.Servicing
{
public class ServicesDueAggregateRepository : IServicesDueAggregateRepository
{
private AutolineDbContext _autolineDbContext;
public ServicesDueAggregateRepository()
{
_autolineDbContext = new AutolineDbContext("Autoline");
}
public List<ServicesDueAggregate> GetServicesDueAggregationByType(string locationCode, DateTime? startDate, DateTime? endDate)
{
var LocationCode = new SqlParameter { ParameterName = "@locationCode", SqlDbType = System.Data.SqlDbType.VarChar, Size = 3, Value = locationCode };
var StartDate = new SqlParameter { ParameterName = "@startDate", SqlDbType = System.Data.SqlDbType.Date, Value = startDate };
var EndDate = new SqlParameter { ParameterName = "@endDate", SqlDbType = System.Data.SqlDbType.Date, Value = endDate };
var result = _autolineDbContext.Database.SqlQuery<ServicesDueAggregate>(
"EXEC dbo.sp_FK_Agreement_ServicesDue_By_Type_Count @LocationCode, @StartDate, @EndDate",
LocationCode,
StartDate,
EndDate
).ToList();
return result;
}
}
}
But I would very much like to implement it rather using your brilliant method via dependency injection. I must admit I don't fully understand how the dependency injection works within your framework and try to follow how you have done it in the hope that I will get the same results.
It would be very helpful if you could maybe do some detailed documentation explaining how and why some of these concepts in the framework is being implemented in the way it does. I think it will go a long way in reducing the number of questions one might have.
By the way I just want you thank you for all the help and for providing such an awesome framework and template. You are an inspiration to me after seeing how things really should be done and the techniques that should be implemented.
Ah thank you very much, I am going to try this.
Reason why I have to constructors is because the dashboard calls a stored procedure for aggregated data and the detailed report displaying the data in the grid is related. I thought it would make sense to keep it in the same AppService, obviously I am doing it wrong. Pardon me for doing things the wrong way, I am still learning.
I have a problem still with the multiple dbContext too. The context is not set correctly in the the custom repository and it still referring to the 1st context connectionstring defined in the web.config.
Any chance you could help me on that? Should I asked this on a different thread? I am really desperate at this stage as I am struggling very much and have lost a lot of hair.
Hi,
Here is the ApplicationServiceBase:
using Abp.Application.Services;
using Abp.IdentityFramework;
using Abp.MultiTenancy;
using Abp.Runtime.Session;
using Caerus.Authorization.Locations;
using Caerus.Authorization.Users;
using Caerus.MultiTenancy;
using Microsoft.AspNet.Identity;
using System;
using System.Configuration;
using System.Threading.Tasks;
namespace Caerus
{
/// <summary>
/// All application services in this application is derived from this class.
/// We can add common application service methods here.
/// </summary>
public abstract class CaerusAppServiceBase : ApplicationService
{
public TenantManager TenantManager { get; set; }
public UserManager UserManager { get; set; }
private LocationManager _locationManager { get; set; }
protected CaerusAppServiceBase()
{
LocalizationSourceName = CaerusConsts.LocalizationSourceName;
}
protected virtual Task<User> GetCurrentUserAsync()
{
var user = UserManager.FindByIdAsync(AbpSession.GetUserId());
if (user == null)
{
throw new ApplicationException("There is no current user!");
}
return user;
}
protected virtual User GetCurrentUser()
{
var user = UserManager.FindById(AbpSession.GetUserId());
if (user == null)
{
throw new ApplicationException("There is no current user!");
}
return user;
}
protected virtual Task<Tenant> GetCurrentTenantAsync()
{
return TenantManager.GetByIdAsync(AbpSession.GetTenantId());
}
protected virtual Tenant GetCurrentTenant()
{
return TenantManager.GetById(AbpSession.GetTenantId());
}
protected virtual CommaDelimitedStringCollection GetCurrentUserAssignedLocations()
{
var assignedLocations = new CommaDelimitedStringCollection();
var user = GetCurrentUser();
if (user == null)
{
throw new ApplicationException("There is no current user!");
}
if (user.Locations.Count == 0)
{
throw new ApplicationException("There is no location assigned to the user!");
}
foreach (var userLocation in user.Locations)
{
var location = _locationManager.GetLocationById(userLocation.LocationId);
if (location == null)
{
throw new ApplicationException("There is no location with id: " + userLocation.LocationId);
}
assignedLocations.Add(location.Code);
}
return assignedLocations;
}
protected virtual void CheckErrors(IdentityResult identityResult)
{
identityResult.CheckErrors(LocalizationManager);
}
}
}
and here is the ApplicationService:
using Abp.Application.Services.Dto;
using Abp.Authorization;
using Abp.AutoMapper;
using Caerus.Authorization;
using Caerus.Tenants.Workshop.Servicing.Dto;
using Caerus.Workshop.Servicing.Repositories;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Threading.Tasks;
namespace Caerus.Tenants.Workshop.Servicing
{
[AbpAuthorize(AppPermissions.Pages_Tenant_Workshop_Servicing_Reports_ServicesDue)]
public class WorkshopServicingServicesDueAppService : CaerusAppServiceBase, IWorkshopServicingServicesDueAppService
{
private readonly IServicesDueAggregateRepository _servicesDueAggregateRepository;
private readonly IServicesDueDetailRepository _servicesDueDetailRepository;
private CommaDelimitedStringCollection _assignedLocations { get; set; }
public WorkshopServicingServicesDueAppService(IServicesDueAggregateRepository servicesDueAggregateRepository)
{
_servicesDueAggregateRepository = servicesDueAggregateRepository;
var user = GetCurrentUser();
_assignedLocations = GetCurrentUserAssignedLocations();
}
public WorkshopServicingServicesDueAppService(IServicesDueDetailRepository servicesDueDetailRepository)
{
_servicesDueDetailRepository = servicesDueDetailRepository;
var user = GetCurrentUser();
_assignedLocations = GetCurrentUserAssignedLocations();
}
public async Task<ListResultOutput<ServicesDueAggregateListDto>> GetServicesDueAggregationByType(ServicesDueAggregateInput input)
{
var servicesDueAggregate = _servicesDueAggregateRepository.GetServicesDueAggregationByType(_assignedLocations.ToString(), input.StartDate, input.EndDate);
return new ListResultOutput<ServicesDueAggregateListDto>(servicesDueAggregate.MapTo<List<ServicesDueAggregateListDto>>());
}
public Task<ListResultDto<ServicesDueDetailListDto>> GetServicesDueDetailByType(ServicesDueDetailInput input)
{
throw new NotImplementedException();
}
}
}
Any help will be greatly appreciated, I know it works as I had a look at ProfileAppService, but I can't figure out what I am doing wrong.
Is this because the base class that I inherit from is in the custom module assembly and the entity class is in the core module assembly?
Or do I need to add a reference to Entity Framework in my custom module assembly?