I have a solution with around 30 projects in it, most of them uses Microsoft Unity as container.
For this test I'm using remote Azure SQL database in different region and in different network, so i expect delayed response but it will not affect this test.
Let's calculate data access time using Unity and direct data access using DbContext and here's the average calculation in milliseconds:
Unity Container
8749
5757
7225
7072
7256
8791
7016
7465
8449
10741
7852.1 (average)
DbContext
3599
2239
2902
2378
1898
1682
1692
1522
2773
2054
2273.9 (average)
So, accessing data using unity container took 7852.1 (average) milliseconds and at the same time accessing data using DbContext took 2273.9 (average) milliseconds. This is big performance bottleneck, don't you think ?
Let me share few code snippets, this will show how i'm using Unity in project.
Unity configuration in projects looks like below:
public class UnityConfig
{
private static Lazy<IUnityContainer> container = new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
RegisterTypes(container);
return container;
});
public static IUnityContainer GetConfiguredContainer()
{
return container.Value;
}
public static void RegisterTypes(IUnityContainer container)
{
//// Repositories
container.RegisterType<ICartRepository, CartRepository>();
// .... total 50 repositories registrations ....
//// Services
container.RegisterType<ICartService, CartService>();
// .... total 72 services registrations ....
}
}
public static class UnityWebActivator
{
public static void Start()
{
var container = UnityConfig.GetConfiguredContainer();
FilterProviders.Providers.Remove(FilterProviders.Providers.OfType<FilterAttributeFilterProvider>().First());
FilterProviders.Providers.Add(new UnityFilterAttributeFilterProvider(container));
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
// TODO: Uncomment if you want to use PerRequestLifetimeManager
// Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));
}
public static void Shutdown()
{
var container = UnityConfig.GetConfiguredContainer();
container.Dispose();
}
}
Here's a sample repository and a sample service being used with Unity:
public interface ICartRepository
{
Cart Get(string id);
IEnumerable<Cart> GetAll();
// more codes
}
public class CartRepository : ICartRepository
{
[Dependency]
public ApplicationData db { get; set; }
public Cart Get(string id)
{
return db.Carts.AsNoTracking().Where(i => i.Id == id && i.IsDeleted == 0).FirstOrDefault();
}
public IEnumerable<Cart> GetAll()
{
return db.Carts.AsNoTracking().Where(i => i.IsDeleted == 0);
}
// more codes
}
public interface ICartService
{
Cart Get(string id, string orgid);
IEnumerable<Cart> GetAll(string orgid);
// more codes
}
public class CartService : ICartService
{
private ICartRepository cartRepository;
public CartService(ICartRepository _cartRepository)
{
cartRepository = _cartRepository;
}
public Cart Get(string id, string orgid)
{
return cartRepository.GetAll().Where(i => i.OrganizationId == orgid && i.Id == id).FirstOrDefault();
}
public IEnumerable<Cart> GetAll(string orgid)
{
return cartRepository.GetAll().Where(i => i.OrganizationId == orgid);
}
// more codes
}
In the projects I use them like:
public class HomeController : Controller
{
private ICartService cartService;
public HomeController(ICartService _cartService)
{
cartService = _cartService;
}
public ActionResult Index()
{
// through unity
var item = cartService.Get("id", "org_id");
// direct DbContext
ApplicationData data = new ApplicationData();
var item1 = data.Carts.AsNoTracking().Where(i => i.Id == "id" && i.OrganizationId == "org_id").FirstOrDefault();
// more code
return View();
}
}
This is all we using in the application. Do you see anything that can be changed to increase performance?
The reason is two ways you are comparing are completely different, and not because of Unity. First way does this:
var item = cartService.Get("id", "org_id");
Which is implemented as
return cartRepository.GetAll().Where(i => i.OrganizationId == orgid && i.Id == id).FirstOrDefault()
Where GetAll is:
public IEnumerable<Cart> GetAll()
{
return db.Carts.AsNoTracking().Where(i => i.IsDeleted == 0);
}
Because GetAll return type is IEnumerable<Cart> - cartRepository.GetAll().Where(...) will not filter out carts in database. Instead, whole Cart table is pulled into memory with SQL query like select * from Cart where IsDeleted = 0. Then Where is executed and finds target Cart (by organization and id) in memory. Of course this is very inefficient (because it transfers the whole table from your remote database to your machine) and takes much more time that another appoach , which does:
data.Carts.AsNoTracking().Where(i => i.Id == "id" && i.OrganizationId == "org_id").FirstOrDefault();
This one produces SQL you expect, like select top 1 * from Cart where IsDeleted = 0 and Id = #id and OrganizationId = #org_id, and all filtering happens in database, which then transfers just one row over the network.
To fix - change your GetAll (and other similar methods) to return IQueryable:
public IQueryable<Cart> GetAll()
{
return db.Carts.AsNoTracking().Where(i => i.IsDeleted == 0);
}
Related
I'm using Abp version 3.6.2, ASP.Net Core 2.0 and free startup template for multi-page web application. I have following data model:
public class Person : Entity<Guid> {
public Person() {
Phones = new HashSet<PersonPhone>();
}
public virtual ICollection<PersonPhone> Phones { get; set; }
public string Name { get; set; }
}
public class PersonPhone : Entity<Guid> {
public PersonPhone() { }
public Guid PersonId { get; set; }
public virtual Person Person { get; set; }
public string Number { get; set; }
}
// DB Context and Fluent API
public class MyDbContext : AbpZeroDbContext<Tenant, Role, User, MyDbContext> {
public virtual DbSet<Person> Persons { get; set; }
public virtual DbSet<PersonPhone> PersonPhones { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder) {
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<PersonPhone>(entity => {
entity.HasOne(d => d.Person)
.WithMany(p => p.Phones)
.HasForeignKey(d => d.PersonId)
.OnDelete(DeleteBehavior.Cascade)
.HasConstraintName("FK_PersonPhone_Person");
});
}
}
The entities Person and PersonPhone are given here as an example, since there are many "grouped" relationships in the data model that are considered in a single context. In the example above, the relationships between the tables allow to correlate several phones with one person and the associated entities are present in the DTO.
The problem is that when creating the Person entity, I can send the phones with the DTO and they will be created with Person as expected. But when I update Person, I get an error:
Abp.AspNetCore.Mvc.ExceptionHandling.AbpExceptionFilter - The instance of
entity type 'PersonPhone' cannot be tracked because another instance
with the same key value for {'Id'} is already being tracked. When attaching
existing entities, ensure that only one entity instance with a given key
value is attached. Consider using
'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting
key values.
In addition to this, the question arises as to how to remove non-existing PersonPhones when updating the Person object? Previously, with the direct use of EntityFramework Core, I did this:
var phones = await _context.PersonPhones
.Where(p =>
p.PersonId == person.Id &&
person.Phones
.Where(i => i.Id == p.Id)
.Count() == 0)
.ToListAsync();
_context.PersonPhones.RemoveRange(phones);
_context.Person.Update(person);
await _context.SaveChangesAsync();
Question
Is it possible to implement a similar behavior with repository pattern? If "Yes", then is it possible use UoW for this?
P.S.: Application Service
public class PersonAppService : AsyncCrudAppService<Person, PersonDto, Guid, GetAllPersonsDto, CreatePersonDto, UpdatePersonDto, EntityDto<Guid>>, IPersonAppService {
private readonly IRepository<Person, Guid> _personRepository;
public PersonAppService(IRepository<Person, Guid> repository) : base(repository) {
_personRepository = repository;
}
public override async Task<PersonDto> Update(UpdatePersonDto input) {
CheckUpdatePermission();
var person = await _personRepository
.GetAllIncluding(
c => c.Addresses,
c => c.Emails,
c => c.Phones
)
.Where(c => c.Id == input.Id)
.FirstOrDefaultAsync();
ObjectMapper.Map(input, person);
return await Get(input);
}
}
Dynamic API calls:
// All O.K.
abp.services.app.person.create({
"phones": [
{ "number": 1234567890 },
{ "number": 9876543210 }
],
"name": "John Doe"
})
.done(function(response){
console.log(response);
});
// HTTP 500 and exception in log file
abp.services.app.person.update({
"phones": [
{
"id": "87654321-dcba-dcba-dcba-000987654321",
"number": 1234567890
}
],
"id":"12345678-abcd-abcd-abcd-123456789000",
"name": "John Doe"
})
.done(function(response){
console.log(response);
});
Update
At the moment, to add new entities and update existing ones, I added the following AutoMapper profile:
public class PersonMapProfile : Profile {
public PersonMapProfile () {
CreateMap<UpdatePersonDto, Person>();
CreateMap<UpdatePersonDto, Person>()
.ForMember(x => x.Phones, opt => opt.Ignore())
.AfterMap((dto, person) => AddOrUpdatePhones(dto, person));
}
private void AddOrUpdatePhones(UpdatePersonDto dto, Person person) {
foreach (UpdatePersonPhoneDto phoneDto in dto.Phones) {
if (phoneDto.Id == default(Guid)) {
person.Phones.Add(Mapper.Map<PersonPhone>(phoneDto));
}
else {
Mapper.Map(phoneDto, person.Phones.SingleOrDefault(p => p.Id == phoneDto.Id));
}
}
}
}
But there is a problem with removed objects, that is, with objects that are in the database, but not in the DTO. To delete them, i'm in a loop compare objects and manually delete them from the database in the application service:
public override async Task<PersonDto> Update(UpdatePersonDto input) {
CheckUpdatePermission();
var person = await _personRepository
.GetAllIncluding(
c => c.Phones
)
.FirstOrDefaultAsync(c => c.Id == input.Id);
ObjectMapper.Map(input, person);
foreach (var phone in person.Phones.ToList()) {
if (input.Phones.All(x => x.Id != phone.Id)) {
await _personAddressRepository.DeleteAsync(phone.Id);
}
}
await CurrentUnitOfWork.SaveChangesAsync();
return await Get(input);
}
Here there is another problem: the object, which returned from Get, contains all entities (deleted, added, updated) are simultaneously. I also tried to use synchronous variants of methods and opened a separate transaction with UnitOfWorkManager, like so:
public override async Task<PersonDto> Update(UpdatePersonDto input) {
CheckUpdatePermission();
using (var uow = UnitOfWorkManager.Begin()) {
var person = await _companyRepository
.GetAllIncluding(
c => c.Phones
)
.FirstOrDefaultAsync(c => c.Id == input.Id);
ObjectMapper.Map(input, person);
foreach (var phone in person.Phones.ToList()) {
if (input.Phones.All(x => x.Id != phone.Id)) {
await _personAddressRepository.DeleteAsync(phone.Id);
}
}
uow.Complete();
}
return await Get(input);
}
but this did not help. When Get is called again on the client side, the correct object is returned. I assume that the problem is either in the cache or in the transaction. What am I doing wrong?
At the moment I solved this problem.
In the beginning it is necessary to disable collections mapping, because AutoMapper rewrites them in consequence of which EntityFramework defines these collections as new entities and tries to add them to the database. To disable collections mapping, it needs to create a class that inherits from AutoMapper.Profile:
using System;
using System.Linq;
using Abp.Domain.Entities;
using AutoMapper;
namespace ProjectName.Persons.Dto {
public class PersonMapProfile : Profile {
public PersonMapProfile() {
CreateMap<UpdatePersonDto, Person>();
CreateMap<UpdatePersonDto, Person>()
.ForMember(x => x.Phones, opt => opt.Ignore())
.AfterMap((personDto, person) =>
AddUpdateOrDelete(personDto, person));
}
private void AddUpdateOrDelete(UpdatePersonDto dto, Person person) {
person.Phones
.Where(phone =>
!dto.Phones
.Any(phoneDto => phoneDto.Id == phone.Id)
)
.ToList()
.ForEach(deleted =>
person.Phones.Remove(deleted)
);
foreach (var phoneDto in dto.Phones) {
if (phoneDto.Id == default(Guid)) {
person.Phones
.Add(Mapper.Map<PersonPhone>(phoneDto));
}
else {
Mapper.Map(phoneDto,
person.Phones.
SingleOrDefault(c => c.Id == phoneDto.Id));
}
}
}
}
}
In the example above, we ignore the collection mapping and use the callback function to add, update or delete phones. Now the error about the impossibility of tracking the entity no longer arises. But if you run this code now, you can see that the returned object has both added entities and removed too. This is due to the fact that by default Abp uses UnitOfWork for application service methods. Therefore, you must disable this default behavior and use an explicit transaction.
using Abp.Application.Services.Dto;
using Abp.Application.Services;
using Abp.Domain.Repositories;
using Abp.Domain.Uow;
using Microsoft.EntityFrameworkCore;
using ProjectName.Companies.Dto;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System;
namespace ProjectName.Persons {
public class PersonAppService : AsyncCrudAppService<Person, PersonDto, Guid, GetAllPersonsDto, CreatePersonDto, UpdatePersonDto, EntityDto<Guid>>, IPersonAppService {
private readonly IRepository<Person, Guid> _personRepository;
private readonly IRepository<PersonPhone, Guid> _personPhoneRepository;
public PersonAppService(
IRepository<Person, Guid> repository,
IRepository<PersonPhone, Guid> personPhoneRepository) : base(repository) {
_personRepository = repository;
_personPhoneRepository = personPhoneRepository;
}
[UnitOfWork(IsDisabled = true)]
public override async Task<PersonDto> Update(UpdatePersonDto input) {
CheckUpdatePermission();
using (var uow = UnitOfWorkManager.Begin()) {
var person = await _personRepository
.GetAllIncluding(
c => c.Phones
)
.FirstOrDefaultAsync(c => c.Id == input.Id);
ObjectMapper.Map(input, person);
uow.Complete();
}
return await Get(input);
}
}
}
It is possible that such code is not optimal or violates any principles. In this case, I will be nice to know how to do it.
I know it has been a few years since this has been posted, but here is the solution we use.
public async Task<bool> UpdateDBOAsync(DboDto DBO)
{
var databaseObject = await _DBODomainService.GetDBOById(DBO.Id);
databaseObject.Name = DBO.Name;
databaseObject.Hours = DBO.Hours;
if (DBO.AddressId != null) {
databaseObject.AddressId = DBO.AddressId;
}
await _DBODomainService.UpdateDBOAsync(databaseObject);
return true;
}
Essentially, we grab the object from the database. Change the values on the object from the database. Then we save that object back to the database.
I am building Asp.net Web API demo project, i don't want to use any data provider, neither entity framework nor Ado.net.
I build the Repository,
public class ProductRepo : IProductRepo
{
private List<Product> products = new List<Product>();
private int _nextId = 1;
public ExternalProductDataRepo()
{
products.Add(new Product {Id=1, Name = "Toyata", Category = "Car", Price = 1.3M });
products.Add(new Product {Id=2, Name = "Ford", Category = "Car", Price = 3.75M });
products.Add(new Product {Id=3, Name = "Hammer", Category = "Hardware", Price = 16.99M });
}
public IEnumerable<Product> GetAll()
{
return products;
}
public Product Get(int id)
{
return products.Find(p => p.Id == id);
}
public Product Add(Product item)
{
if (item == null)
{
throw new ArgumentNullException("item");
}
item.Id = _nextId++;
products.Add(item);
return item;
}
public void Remove(int id)
{
products.RemoveAll(p => p.Id == id);
}
public bool Update(Product item)
{
if (item == null)
{
throw new ArgumentNullException("item");
}
int index = products.FindIndex(p => p.Id == item.Id);
if (index == -1)
{
return false;
}
products.RemoveAt(index);
products.Add(item);
return true;
}
}
But, in every app hit, a new instance of ProductRepo class is created, so even though i save the data, it never persisted.
Also, i have the option of using, System.Web.Cache, to hold the products list in Cache, but it is for a different purpose and for a limited period of time, also it makes isolated unit testing difficult. So, what is the suggestion to build the successive demo project with products CRUD operation?
You want your repository to be a singleton. There are multiple ways to achieve this, but if this demo code is going to become operational in some way, I would suggest integrating a dependency injection provider into your code. For example, you could use unity.
You set up your repository as a singleton in the container.
You controllers will get passed the IProductRepo interface. This means that if you decide to change the implementation to an actual data provider, your code will not need to change - you will simply change the registration in your DI container.
Here are some resources to help you get started if you want to implement this path:
Install Unity.WebAPI nuget package. See http://www.devtrends.co.uk/blog/introducing-the-unity.webapi-nuget-package
In your App_Start folder, add a static UnityConfig class:
public static class UnityConfig
{
public static void RegisterComponents()
{
var container = new UnityContainer();
container.RegisterType<<IProductRepo>,<ProductRepo>> (new ContainerControlledLifetimeManager());
container.Resolve<IProductRepo>();
GlobalConfiguration.Configuration.DependencyResolver = new UnityDependencyResolver(container);
}
}
In your Global.asax file, add the following line: UnityConfig.RegisterComponents();
Now in the constructor of your controller, just pass a member of type IProductRepo. This will be initialized for you by Unity, and will be the same instance every app hit since it is created once by Unity.
I have a ASP.NET 5 (running on 4.6.2, not Core) application. I wanted to use the ProjectTo<>() method of AutoMapper to project the results from the database to my viewmodels.
I've tried alot of tests, but it seems that the map solely cannot be found when using the ProjectTo<>(). Using mapper.Map<>() on different locations with the same model and viewmodel perfectly works.
I guess there is something wrong with how AutoMapper works with my DI (Autofac), but I can't figure out what.
Anyway, the code:
Startup.Cs
public IServiceProvider ConfigureServices(IServiceCollection services)
{
(...)
// Autofac DI
AutofacContainer = AutofacLoader.Configure(services).Build();
return AutofacContainer.Resolve<IServiceProvider>();
}
AutofacLoader.cs
public static ContainerBuilder Configure(IServiceCollection services)
{
var builder = new ContainerBuilder();
(...)
// AutoMapper
builder.RegisterModule<AutoMapperModule>();
if (services != null)
{
builder.Populate(services);
}
return builder;
}
AutoMapperModule.cs
public class AutoMapperModule : Module
{
protected override void Load(ContainerBuilder builder)
{
var mapping = new MapperConfiguration(cfg =>
{
cfg.AddProfile(new Core.Mappings.AutoMapperProfileConfiguration());
cfg.AddProfile(new Dieet.Core.Mappings.AutoMapperProfileConfiguration());
});
builder.RegisterInstance(mapping.CreateMapper()).As<IMapper>().AutoActivate();
}
}
The test that fails with 'Missing map from Patient to PatientViewModel. Create using Mapper.CreateMap'.
[Fact]
public async void InfohosServiceReturnsPatientViewModels()
{
var db = _container.Resolve<IInfohosDb>();
var search = new PaginatedSearchBase();
search.OrderBy = "Naam";
var mapper = _container.Resolve<IMapper>();
var result = await search.PagedResultAsAsync<Patient,PatientViewModel >(null,db.Patienten,mapper);
}
PaginatedSearchBase
public class PaginatedSearchBase
{
public string OrderBy { get; set; }
public bool OrderDescending { get; set; }
public int Page { get; set; } = 1;
public int PageSize { get; set; } = 10;
}
And finally the extension that calls the ProjectTo
public static class PagedResultExtensions
{
public static async Task<PagedResult<T>> PagedResultAsync<T>(this PaginatedSearchBase vm, ICollection<Expression<Func<T, bool>>> whereCollection, IEnumerable<T> context) where T : class
{
int totalCount;
var query = PrepareQuery(vm, whereCollection, context, out totalCount);
return new PagedResult<T>
{
Results = await query.ToListAsync(),
Page = vm.Page,
PageSize = vm.PageSize,
Total = totalCount
};
}
public static async Task<PagedResult<TAs>> PagedResultAsAsync<T, TAs>(this PaginatedSearchBase vm, ICollection<Expression<Func<T, bool>>> whereCollection, IEnumerable<T> context, IMapper mapper) where T : class
{
int totalCount;
var query = PrepareQuery(vm, whereCollection, context, out totalCount);
return new PagedResult<TAs>
{
----------> Results = await query.ProjectTo<TAs>(mapper).ToListAsync(),
Page = vm.Page,
PageSize = vm.PageSize,
Total = totalCount
};
}
private static IQueryable<T> PrepareQuery<T>(PaginatedSearchBase vm, ICollection<Expression<Func<T, bool>>> whereCollection, IEnumerable<T> context,
out int totalCount) where T : class
{
var query = context.AsQueryable();
if (whereCollection != null)
{
foreach (var w in whereCollection)
{
if (w != null)
{
query = query.Where(w);
}
}
}
// Order by
query = query.OrderBy($"{vm.OrderBy} {(vm.OrderDescending ? "DESC" : "ASC")}");
// Total rows
totalCount = query.Count();
// Paging
query = query.Skip((vm.Page - 1)*vm.PageSize).Take(vm.PageSize);
return query;
}
}
For information, I'm using versions:
"Autofac": "4.0.0-rc1-177"
"Autofac.Extensions.DependencyInjection": "4.0.0-rc1-177"
"AutoMapper": "4.2.1"
Edit:
A new test that I did to check if the mappings really work:
var mapper = _container.Resolve<IMapper>();
var p = new Patient();
p.Naam = "Test";
var vm = mapper.Map<PatientViewModel>(p);
vm.Naam.ShouldBeEquivalentTo("Test");
This test passes
Edit 2:
When I use the Map<> in a Select() instead, it works too, so it's really the ProjectTo<>() that fails:
var results = await query.ToListAsync();
return new PagedResult<TAs>
{
Results = results.Select(mapper.Map<TAs>).ToList(),
Page = vm.Page,
PageSize = vm.PageSize,
Total = totalCount
};
This works, but it requires the mapper to be included and not be injected and it doesn't use the ProjectTo that Automapper has for database access...
I ran into the same issue but managed to get it working. This is happening due to the recent move, by Automapper, away from having the entire API use static methods. Now that everything is instance-based, the static extension methods no longer know about the mapping configuration and they now have to be passed into the method. I ended up registering an instance of the MapperConfiguration as IConfigurationProvider (I'm using Unity)
container.RegisterInstance(typeof (IConfigurationProvider), config);
This is injected into my query handler:
[Dependency]
public IConfigurationProvider MapperConfigurationProvider { get; set; }
Finally, the MapperConfigurationProvider is passed to the call to ProjectTo:
.ProjectTo<Payment>(MapperConfigurationProvider);
Hope this helps.
You don't have to specifically add ConfigurationProvider to DI. If you already added the IMapper to DI, than you can read ConfigurationProvider from the Mapper itself. Example: I had the same problem and created a base class containing IMapper that got injected:
public abstract class ServiceBase
{
public IMapper Mapper { get; set; }
}
This class was inherited in all my Services that used AutoMapper. Now every time any of my services needed to Map something, they did so:
return context.SomeEntity
.Where(e => e.Id == filter.Id)
.ProjectTo<EntityDto>(Mapper.ConfigurationProvider).ToList();
With Mapper being injected.
As long as you put the completely configured Mapper in DI, you're ok.
container.Register(Component.For<IMapper>().UsingFactoryMethod(x =>
{
return new AutoMapperConfig().ConfigureMapper();
})
);
I am attempting to add an interface to make generic code for 2 database repository models, however in each database model returns a different type and I have been unable to find a solution. This seems like it should be possible but I am out of ideas can anybody point me in the right direction or tell me if it is not possible.
Code - Interface:
interface IPermissions
{
//List<User> getUser(string userID);
List<Role> getUserPermissions(string userName);
List<Role> getAllPermissions();
void enable();
void disable();
void addPermission(string permissionName);
void removePermission(string permissionName);
}
Model 1:
public List<AUser> getUser(string userName)
{
IEnumerable<AUser> users = from x in a.AUsers
where x.UserID == userName
select x;
List<AUser> usersList = users.ToList();
return usersList;
}
public List<AGroup> getUserPermissions(string userName)
{
IEnumerable<AGroup> usersPermisions = from abfug in a.UserGroups
join abfg in a.Group on abfug.GroupID equals abfg.ID
join au in a.AUsers on abfug.UserID equals au.ID
where au.UserID == userName
select abfg;
List<Group> usersList = usersPermisions.ToList();
return usersList;
}
Model 2:
public List<UCUser> getUCUser(string userName)
{
IEnumerable<UCsUser> users = from y in UC.UCUsers
where y.UserID == userName
select y;
List<UCyUser> usersList = users.ToList();
return usersList;
}
public List<UCGroup> getUCUserPermissions(string userName)
{
IEnumerable<UCGroup> userPermissions = from bfug in UC.UCUserGroups
join bfg in UC.UCGroups on bfug.GroupID equals bfg.ID
join u in UC.UCUsers on bfug.UserID equals u.ID
where u.UserID == userName
select bfg;
List<UCGroup> usersList = userPermissions.ToList();
return usersList;
}
If there are any typos in the code please bear with me.
Any help would be greatly appreciated.
When you use a generic pattern, you make the interface generic:
public interface IRepository<TModel>
{
IEnumerable<TModel> Retrieve();
void Create(TModel model);
void Delete(TModel model);
void Update(TModel model);
}
public class RoleRepository : IRepository<Role>
{
IEnumerable<TModel> Retrieve()
{
using (var context = new MyContext())
{
return context.Roles.ToList(); // iterating through them hits the database
}
}
public void Create(TModel model)
{
using (var context = new MyContext())
{
context.Set<TModel>().Attach(model);
context.SaveChanges();
}
}
public void Delete(TModel model) {}
public void Update(TModel model) {}
}
public class MyController : Controller
{
private readonly IRepository<Role> roleRepository;
public MyController(IRepository<Role> roleRepository)
{
this.roleRepository = roleRepository;
}
}
Once you have this, you will generally hide this behind a service. Then you have proper serparation of concerns. The repository layer hides the context, so that can change easily. The service layer hides the repository so that can change easily, also you can add those operation specific functions to the service.
public class MyService : IMyService
{
private readonly IRepository<Role> roleRepository;
public MyService(IRepository<Role> roleRepository)
{
this.roleRepository = roleRepository;
}
public IEnumerable<Role> LetsDoSomeCoolThings()
{
// some complicated role specific function to return a complex set of results
}
}
Now obviously you would then substitute the Repository in the controller for this service
I am trying to code with best practices and I have a doubt here. I am testing this on WebForms.
I have a UserService Layer where I have a method to pass a user to the RepositoryLayer:
public AddUserResponse AddUserResponse(AddUserRequest addUserRequest)
{
AddUserResponse response = new AddUserResponse();
User objUser = new User();
objUser.Names = addUserRequest.Names;
objUser.LastName = addUserRequest.LastName;
objUser.Email = addUserRequest.Email;
objUser.Alias = addUserRequest.Alias;
objUser.Profile.IdProfile = addUserRequest.Profile.IdProfile;
objUser.Password = addUserRequest.Password;
objUser.Active = addUserRequest.Active;
short OperationState=_userRepository.Add(objUser);
if (OperationState==0)
{
response.State=true;
response.Message="User inserted";
}
else if (OperationState==2)
{
response.State=false;
response.Message="Alias or Email already exist. Cannot insert User";
}
else
{
response.State=false;
response.Message="Error in User insertion";
}
return response;
}
Then I have a UserRepository Layer where I have a function that Adds a user comming from my service layer:
public short Add(User objUser)
{ ... return OperationState }
As showed this function relays on a stored procedure call to insert the user record. If the user email or alias doesn't exist then it inserts and returns 0, if it does returns 2, and if the operation fails returns 1.
I perform the checking and insertion in one call to save database round trips.
Am I performing the checking in a correct way on my service and repository classes?, or if am not, How should I abstract the logic to let the system determine when is a duplicated user?. Should I use the model or service to put the validation logic and raise a custom exception when that happens?
Thanks a lot for your insight.
UPDATE
For general interest I am posting now how I am implementing this on my App, once I get the Jason's IoC solution gonna make an update on this as well.
Model Class:
using ABC.DEF.Infrastructure.Domain;
namespace ABC.DEF.Model
{
public class AliasOrEmailAreUnique
{
private readonly IRepository<User, int> repository;
public AliasOrEmailAreUnique(IRepository<User,int> repository)
{
this.repository = repository;
}
//If the user is added has Id 0 so search among all the existing users for one that could have the alias or email registered already
//else if the user is being edit then search among all the user except the user with such Id(itself)
public bool IsBroken(User model)
{
if (model.IdUser == 0)
{
return (
repository.List().Where(x => x.Alias == model.Alias).Any()
|| repository.List().Where(x => x.Email == model.Email).Any()
);
}
else
{
return (
repository.List().Where(x => x.Alias == model.Alias && x.IdUser != model.IdUser).Any()
|| repository.List().Where(x => x.Email == model.Email && x.IdUser != model.IdUser).Any()
);
}
}
public ErrorMessage ErrorMessage
{
get { return new ErrorMessage { Property = "AliasEmail", Message = "Alias or Email exists already" }; }
}
}
}
Service Class:
using ABC.DEF.Repository;
using ABC.DEF.Model;
using ABC.DEF.Service.Messaging.User;
namespace ABC.DEF.Service
{
public class UsuarioService
{
public AddUserResponse AddUserResponse(AddUserRequest addUserRequest)
{
AddUserResponse response = new AddUserResponse();
User objUser = new User();
objUser.Names = addUserRequest.Names;
objUser.LastName = addUserRequest.LastName;
objUser.Email = addUserRequest.Email;
objUser.Alias = addUserRequest.Alias;
objUser.Profile.IdProfile = addUserRequest.Profile.IdProfile;
objUser.Password = addUserRequest.Password;
objUser.Active = addUserRequest.Active;
//Determine if the Alias or Email are unique
Model.AliasOrEmailAreUnique aliasOrEmailAreUnique = new Model.AliasOrEmailAreUnique(_userRepository);
if (!aliasOrEmailAreUnique.IsBroken(objUser))
{
_usuarioRepository.Add(objUser);
response.State = true;
response.Message = "User added succesfully";
}
else
{
response.State = false;
response.Message = aliasOrEmailAreUnique.ErrorMessage.Message;
}
return response;
}
}
}
I like to validate the input at the beginning of a unit of work. For a web application the request is the unit of work. before the controller action is fired I validate the user input. the action itself is the "happy path". if it makes it this far I know my operation will succeed. at the end the request (the response) I commit any changes back to the database.
I also like to keep my operation explicit so a call to add an entity would be different than call to edit an entity vs. deleting an entity.
in your scenario you have a service layer rather than controller actions, but the process is still the same. validate the model before calling the service layer. then pass the model to the service layer to preform what operations you want.
...UPDATE 1...
in response to your comment below.....
I have been calling my repositories only in the service layer
Becareful not to fall into the trap of thinking there is a linear patterns for making calls. through the application. instead think of it as an onion or sphere with multiple layers.
The model is just a POCO/DTO. there would be other components responsible for validating the model. typically I have a business rules engine that looks something like this... written off the top of my head.
interface IRule<T>
{
bool IsBroken(T model);
ErrorMessage Message {get;}
}
interface IRulesEngine
{
IEnumerable<ErrorMessage> Validate<T>(T model);
}
class ErrorMessage
{
public string Property {get;set;}
public string Message {get;set;}
}
class RulesEngine : IRulesEngine
{
private readonly IContainer container;
public RulesEngine(IContainer container)
{
this.container = container;
}
public IEnumerable<ErrorMessage> Validate<T>(T model)
{
return container
.GetInstances<IRule<T>>()
.Where(rule => rule.IsBroken(model))
.Select(rule => rule.Message);
}
}
this implementation assumes an IoC container, but can be implemented without one. The a rule may look like this
class NameIsUnique<MyClass> : IRule<MyClass>
{
private readonly IRepository<TheEntity> repository;
public NameIsUnique<MyClass>(IRepository<TheEntity> repository)
{
this.repository = repository;
}
public bool IsBroken(MyClass model)
{
return repository.Where(x => x.Name == model.Name).Any();
}
public ErrorMessage
{
get { return new ErrorMessage { Property = "Name", Message = "Name is not unique" }; }
}
}
finally, how this can be used.
var errors = engine.Validate(model);
LoadErrorsInToView(errors);
if(errors.Any()) return;
//call service to process the happy path...
...UPDATE 2...
first we refactor our interfaces
//this is just a marker interface. don't directly imeplement this.
interface IRule
{
}
interface IRule<T> : IRule
{
bool IsBroken(T model);
ErrorMessage Message {get;}
}
class RulesEngine : IRulesEngine
{
public reasdonly ICollection<IRule> Rules = new List<IRule>();
public IEnumerable<ErrorMessage> Validate<T>(T model)
{
return Rules
.Where(x => typeof(IRule<T>).IsAssignableFrom(x.GetType()))
.Cast<IRule<T>>()
.Where(rule => rule.IsBroken(model))
.Select(rule => rule.Message);
}
}