I'm trying to test the that the GetSystem(int id) method in SystemService.cs returns the correct value, but can't seem to figure out how to get everything to play well together. It seems that no matter what I do, GetSystem() always returns null. This is using Entity Framework 6. If I change the body of GetSystem to read _context.Systems.SingleOrDefault(s => s.Id = id), then everything works properly, but I'd really like to use Find().
What is the proper way to test this? I'm using xUnit and Moq in this example. SystemServiceTests.cs shows the code that I'm currently using that isn't working.
SystemService.cs
namespace MyProject.Services
{
public class SystemService
{
private readonly MyContext _context;
public SystemService(MyContext context)
{
_context = context;
}
public Models.System GetSystem(int id)
{
return _context.Systems.Find(id);
}
}
}
SystemServiceTests.cs
namespace MyProject.Tests.Unit
{
public class SystemServiceTests
{
[Fact]
public void GetSystemReturnsFromContext()
{
var data = new List<Models.System> {
new Models.System { Id = 1, Name = "test 1" },
new Models.System { Id = 2, Name = "test 2" }
}.AsQueryable();
var mockContext = new Mock<MyContext>();
var mockSet = new Mock<MockableDbSetWithIQueryable<Models.System>>();
mockContext.Setup(c => c.Systems).Returns(mockSet.Object);
mockSet.Setup(m => m.Provider).Returns(data.Provider);
mockSet.Setup(m => m.Expression).Returns(data.Expression);
mockSet.Setup(m => m.ElementType).Returns(data.ElementType);
mockSet.Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());
var service = new SystemService(mockContext.Object);
var system = service.GetSystem(1);
Assert.NotNull(system); // This is always null
}
}
}
MyContext.cs
namespace MyProject.Models
{
public class MyContext : DbContext
{
public MyContext()
: base("DefaultConnection")
{
}
public virtual DbSet<Models.System> Systems { get; set; }
}
}
System.cs
namespace MyProject.Models
{
public class System
{
public int Id { get; set; }
public string Name { get; set; }
}
}
MockableDbSetWithIQueryable.cs
namespace MyProject.Tests.Helpers
{
public abstract class MockableDbSetWithIQueryable<T> : DbSet<T>, IQueryable
where T : class
{
public abstract IEnumerator<T> GetEnumerator();
public abstract Expression Expression { get; }
public abstract Type ElementType { get; }
public abstract IQueryProvider Provider { get; }
}
}
PS. Some of the code for this, specifically MockableDbSetWithIQueryable was found at http://msdn.microsoft.com/en-US/data/dn314429
I was able to find the recommended way to test everything using Entity Framework 6. The resource for this recommendation is available at http://msdn.microsoft.com/en-US/data/dn314431.
In a nutshell, test classes need to be created for each bit that needs to be tested. What I ended up doing is the following:
TestDbSet.cs
public class TestDbSet<TEntity> : DbSet<TEntity>, IQueryable, IEnumerable<TEntity>
where TEntity : class
{
ObservableCollection<TEntity> _data;
IQueryable _query;
public TestDbSet()
{
_data = new ObservableCollection<TEntity>();
_query = _data.AsQueryable();
}
public override TEntity Add(TEntity item)
{
_data.Add(item);
return item;
}
public override TEntity Remove(TEntity item)
{
_data.Remove(item);
return item;
}
public override TEntity Attach(TEntity item)
{
_data.Add(item);
return item;
}
public override TEntity Create()
{
return Activator.CreateInstance<TEntity>();
}
public override TDerivedEntity Create<TDerivedEntity>()
{
return Activator.CreateInstance<TDerivedEntity>();
}
public override ObservableCollection<TEntity> Local
{
get
{
return _data;
}
}
Type IQueryable.ElementType
{
get { return _query.ElementType; }
}
Expression IQueryable.Expression
{
get { return _query.Expression; }
}
IQueryProvider IQueryable.Provider
{
get { return _query.Provider; }
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _data.GetEnumerator();
}
IEnumerator<TEntity> IEnumerable<TEntity>.GetEnumerator()
{
return _data.GetEnumerator();
}
}
TestSystemDbSet.cs
class TestSystemDbSet : TestDbSet<Models.System>
{
public override Models.System Find(params object[] keyValues)
{
var id = (int)keyValues.Single();
return this.SingleOrDefault(s => s.Id == id);
}
}
TestContext.cs
public class TestContext: IContext
{
public TestContext()
{
this.Systems = new TestSystemDbSet();
}
public DbSet<Models.System> Systems { get; set; }
public int SaveChangesCount { get; private set; }
public int SaveChanges()
{
this.SaveChangesCount++;
return 1;
}
}
SystemServiceTests.cs
public class SystemServiceTests
{
[Fact]
public void GetSystemReturnsFromContext()
{
var context = new TestContext();
context.Systems.Add(new Models.System { Id = 1, Name = "System 1" });
context.Systems.Add(new Models.System { Id = 2, Name = "System 2" });
context.Systems.Add(new Models.System { Id = 3, Name = "System 3" });
var service = new SystemService(context);
var system = service.GetSystem(2);
Assert.NotNull(system);
Assert.Equal(2, system.Id);
Assert.Equal("System 2", system.Name);
}
}
SystemService.cs
public class SystemService : ISystemService
{
private readonly IContext _context;
public SystemService(IContext context)
{
_context = context;
}
public Models.System AddSystem(Models.System system)
{
var s = _context.Systems.Add(system);
_context.SaveChanges();
return s;
}
public Models.System GetSystem(int id)
{
return _context.Systems.Find(id);
}
}
ISystemService.cs
public interface ISystemService
{
Models.System AddSystem(Models.System system);
Models.System GetSystem(int id);
}
.Find() is returning null because that is the default value for System. The collection does not contain an item with id id.
.Find() is a method of List.
I suggest you use LINQ's FirstOrDefault()
The reason being, you can use lazy loading by returning an IQueryable
Related
I'm having an issue where I try to make one endpoint for all classes that derive the same class.
One of my Core Entities is called Cell and has many deriving types such as ImageCell, VideoCell and so on.
The project is implemented using Ardalis.Specification and Ardalis.Specification.EntityFrameworkCore.
For reference here is the base class Cell and two deriving classes.
public abstract class Cell : IAggregateRoot
namespace Core.Entities.Aggregates
{
public abstract class Cell : IAggregateRoot
{
public int CellId { get; set; }
public string CellType { get; set; }
public int RowIndex { get; set; }
public int ColIndex { get; set; }
public int RowSpan { get; set; }
public int ColSpan { get; set; }
public int PageId { get; set; }
public Page Page { get; set; }
}
}
namespace Core.Entities.Cells
{
public class ImageCell : Cell
{
public string Url { get; set; }
}
}
namespace Core.Entities.Cells
{
public class TextCell : Cell
{
public string Text { get; set; }
}
}
All classes have a corresponding DTO.
namespace API.DTOs
{
public class CellDTO : DTO
{
public int CellId { get; set; }
public string CellType { get; set; }
public int RowIndex { get; set; }
public int ColIndex { get; set; }
public int RowSpan { get; set; }
public int ColSpan { get; set; }
public int PageId { get; set; }
}
}
namespace API.DTOs.Cells
{
public class ImageCellDTO : CellDTO
{
public string ImageUrl { get; set; }
}
}
namespace API.DTOs.Cells
{
public class TextCellDTO : CellDTO
{
public string Text { get; set; }
}
}
The MappingProfile is set up according to the documentation:
namespace API
{
public class MappingProfile : Profile
{
public MappingProfile()
{
// Entity -> DTO
...
// Cells
// https://docs.automapper.org/en/stable/Mapping-inheritance.html
CreateMap<Cell, CellDTO>()
.IncludeAllDerived();
CreateMap<ImageCell, ImageCellDTO>();
CreateMap<AudioTextCell, AudioTextCellDTO>();
CreateMap<AudioCell, AudioCellDTO>();
CreateMap<GameCell, GameCellDTO>();
CreateMap<TextCell, TextCellDTO>();
CreateMap<VideoCell, VideoCellDTO>();
...
// DTO -> Enitity
...
// Cells
CreateMap<CellDTO, Cell>()
.IncludeAllDerived();
CreateMap<AudioTextCellDTO, AudioTextCell>();
CreateMap<AudioCellDTO, AudioCell>();
CreateMap<GameCellDTO, GameCell>();
CreateMap<TextCellDTO, TextCell>();
CreateMap<VideoCellDTO, VideoCell>();
CreateMap<ImageCellDTO, ImageCell>();
...
}
}
}
The Repository is set up like this:
using Ardalis.Specification;
namespace Core.Interfaces
{
public interface IRepository<T> : IRepositoryBase<T> where T : class, IAggregateRoot
{
}
}
using Ardalis.Specification;
namespace Core.Interfaces
{
public interface IReadRepository<T> : IReadRepositoryBase<T> where T : class, IAggregateRoot
{
}
}
namespace Infrastructure.Data
{
public class EfRepository<T> : RepositoryBase<T>, IReadRepository<T>, IRepository<T> where T : class, IAggregateRoot
{
public EfRepository(BookDesinerContext dbContext) : base(dbContext)
{
}
}
}
Service like this:
namespace Core.Interfaces
{
public interface IService<T> where T : class, IAggregateRoot
{
Task<bool> ExistsByIdAsync(int id);
Task<T> GetByIdAsync(int id);
Task<T> GetByIdAsyncWithSpec(Specification<T> spec);
Task<IEnumerable<T>> ListAsync();
Task<IEnumerable<T>> ListAsyncWithSpec(Specification<T> spec);
Task DeleteByIdAsync(int id);
Task DeleteRangeAsync(IEnumerable<T> range);
Task<T> AddAsync(T t);
Task UpdateAsyc(T t);
}
}
Now I created a default implementation:
using Ardalis.Specification;
using Core.Interfaces;
namespace Core.Services
{
public class GenericService<T> : IService<T> where T : class, IAggregateRoot
{
private readonly IRepository<T> _repository;
private readonly IAppLogger<GenericService<T>> _logger;
public GenericService(IRepository<T> repository, IAppLogger<GenericService<T>> logger)
{
_repository = repository;
_logger = logger;
}
public async Task<bool> ExistsByIdAsync(int id)
{
return await _repository.GetByIdAsync(id) != null;
}
public async Task<T> GetByIdAsync(int id)
{
var t = await _repository.GetByIdAsync(id);
if (t == null)
{
_logger.Error($"Element with id: {id} can not be found!");
throw new ArgumentException($"Element with id: {id} can not be found!");
}
return t;
}
public async Task<T> GetByIdAsyncWithSpec(Specification<T> spec)
{
if (!(spec is ISingleResultSpecification))
{
throw new ArgumentException("Specification does not implement marker interface.");
}
ISingleResultSpecification<T> specification = (ISingleResultSpecification<T>)spec;
var t = await _repository.GetBySpecAsync(specification);
if (t == null)
{
_logger.Error($"Element can not be found!");
throw new ArgumentException($"Element can not be found!");
}
return t;
}
public async Task<IEnumerable<T>> ListAsync()
{
return await _repository.ListAsync();
}
public async Task<IEnumerable<T>> ListAsyncWithSpec(Specification<T> spec)
{
return await _repository.ListAsync(spec);
}
public async Task DeleteByIdAsync(int id)
{
var t = await _repository.GetByIdAsync(id);
if (t == null)
{
_logger.Error($"Element with id: {id} can not be found!");
throw new ArgumentException($"Element with id: {id} can not be found!");
}
await _repository.DeleteAsync(t);
}
public async Task DeleteRangeAsync(IEnumerable<T> range)
{
await _repository.DeleteRangeAsync(range);
}
public async Task<T> AddAsync(T t)
{
return await _repository.AddAsync(t);
}
public async Task UpdateAsyc(T t)
{
await _repository.UpdateAsync(t);
}
}
}
I registered a Service for every single Subtype:
builder.Services.AddScoped<IService<Cell>, GenericService<Cell>>();
builder.Services.AddScoped<IService<ImageCell>, GenericService<ImageCell>>();
builder.Services.AddScoped<IService<TextCell>, GenericService<TextCell>>();
builder.Services.AddScoped<IService<AudioCell>, GenericService<AudioCell>>();
builder.Services.AddScoped<IService<AudioTextCell>, GenericService<AudioTextCell>>();
builder.Services.AddScoped<IService<VideoCell>, GenericService<VideoCell>>();
builder.Services.AddScoped<IService<GameCell>, GenericService<GameCell>>();
And for the final part the controller:
namespace API.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class CellsController : BaseController<Cell, CellDTO>
{
private readonly IService<ImageCell> _imageCellService;
private readonly IService<TextCell> _textCellService;
private readonly IService<AudioCell> _audioCellService;
private readonly IService<AudioTextCell> _audioTextCellService;
private readonly IService<VideoCell> _videoCellService;
private readonly IService<GameCell> _gameCellService;
public CellsController(
IService<Cell> service,
IService<ImageCell> imageCellService,
IService<TextCell> textCellService,
IService<AudioCell> audioCellService,
IService<AudioTextCell> audioTextCellService,
IService<VideoCell> videoCellService,
IService<GameCell> gameCellService,
IMapper mapper) : base(service, mapper)
{
_imageCellService = imageCellService;
_textCellService = textCellService;
_audioCellService = audioCellService;
_audioTextCellService = audioTextCellService;
_videoCellService = videoCellService;
_gameCellService = gameCellService;
}
[HttpGet]
public override async Task<IActionResult> Get()
{
var result = new List<Object>();
// Add ImageCells
ICollection<ImageCell> imageCells = (ICollection<ImageCell>)await _imageCellService.ListAsync();
result.AddRange(_mapper.Map<ICollection<ImageCell>, ICollection<CellDTO>>(imageCells));
// Add TextCells
ICollection<TextCell> textCells = (ICollection<TextCell>)await _textCellService.ListAsync();
result.AddRange(_mapper.Map<ICollection<TextCell>, ICollection<CellDTO>>(textCells));
...
return Ok(result);
}
[HttpGet("Page/{pageId}")]
public async Task<IActionResult> GetByPageId(int pageId)
{
var result = new List<Object>();
// Add ImageCells
ICollection<ImageCell> imageCells = (ICollection<ImageCell>)await _imageCellService.ListAsync();
result.AddRange(_mapper.Map<ICollection<ImageCell>, ICollection<ImageCellDTO>>(imageCells.Where(c => c.PageId == pageId).ToList()));
// Add TextCells
ICollection<TextCell> textCells = (ICollection<TextCell>)await _textCellService.ListAsync();
result.AddRange(_mapper.Map<ICollection<TextCell>, ICollection<TextCellDTO>>(textCells.Where(c => c.PageId == pageId).ToList()));
...
return Ok(result);
}
[HttpGet("{id}")]
public override async Task<IActionResult> Get(int id)
{
if (await _imageCellService.ExistsByIdAsync(id))
{
var result = await _imageCellService.GetByIdAsync(id);
return Ok(_mapper.Map<ImageCell, ImageCellDTO>(result));
}
if (await _textCellService.ExistsByIdAsync(id))
{
var result = await _textCellService.GetByIdAsync(id);
return Ok(_mapper.Map<TextCell, TextCellDTO>(result));
}
...
return NotFound();
}
...
}
}
This is a highly inefficient implementation to my understanding.
Problems:
I can call /Cells to get all Cells the way it was intended with the List<Object>. List<CellDTO> always led to a downcast, which was unintended.
The same problem occures in a DTO that is not shown, that has a List<CellDTO> as a property. But I would need the concrete subtypes in this list.
My goals:
Remove redundant code in the controller
Only register one CellSerivce
Correct mapping Entity <=> DTO
Things I have considered, but I could not find information to back my thesis:
Writing a CellSpecification that includes all subtypes
Creating a DTO that covers all fields from the subtypes
Try the following:
var cells = (ICollection<Cell>)await _cellService.ListAsync();
result.AddRange(_mapper.Map<ICollection<Cell>, ICollection<CellDTO>>(cells));
Where _cellService is IService<Cell>
I'm implementing a generic repository + Unit of work pattern along with a WebApi project. I'm having problems with getting one entity and including the collection of another enttity that refers to it.
I have the following entities mapped through code first:
public class Ingredient : BaseEntity
{
public string Name { get; set; }
public string Amount { get; set; }
public Guid RecipeId { get; set; }
public virtual Recipe Recipe { get; set; }
}
public class Recipe : BaseEntity
{
public string Name { get; set; }
public string Description { get; set; }
public string ImagePath { get; set; }
public virtual ICollection<Ingredient> Ingredients { get; set; }
}
This is my unit of work:
public class UnitOfWork<TContext> : IRepositoryFactory, IUnitOfWork<TContext>, IUnitOfWork where TContext : DbContext
{
private readonly TContext context;
private Dictionary<Type, object> repositories;
public UnitOfWork(TContext context)
{
this.context = context ?? throw new ArgumentNullException(nameof(context));
}
public TContext Context => context;
public IRepository<TEntity> GetRepository<TEntity>() where TEntity : BaseEntity
{
if (repositories == null)
{
repositories = new Dictionary<Type, object>();
}
var type = typeof(TEntity);
if (!repositories.ContainsKey(type))
{
repositories.Add(type, new Repository<TEntity>(context));
}
return (IRepository<TEntity>)repositories[type];
}
public int Commit()
{
return context.SaveChanges();
}
public void Dispose()
{
context?.Dispose();
}
}
And my generic repository:
public class Repository<T> : IRepository<T> where T : BaseEntity
{
protected readonly DbContext dbContext;
protected readonly DbSet<T> dbSet;
public Repository(DbContext context)
{
dbContext = context;
dbSet = dbContext.Set<T>();
}
public T GetEntity(Guid id)
{
return dbSet.Find(id);
}
public T GetEntity(Guid id, params Expression<Func<T, object>>[] includeProperties)
{
IEnumerable<string> properties = GetProperties(includeProperties);
IQueryable<T> queryable = dbSet;
foreach (var property in includeProperties)
{
queryable = dbSet.Include(property);
}
return queryable.FirstOrDefault(x => x.Id == id);
}
[...]
private static IEnumerable<string> GetProperties(Expression<Func<T, object>>[] includeProperties)
{
List<string> includelist = new List<string>();
foreach (var item in includeProperties)
{
MemberExpression body = item.Body as MemberExpression;
if (body == null)
throw new ArgumentException("The body must be a member expression");
includelist.Add(body.Member.Name);
}
return includelist.AsEnumerable();
}
}
The controller is injecting the RecipeService. In the controller I have this method:
[HttpGet("{id}", Name = "Get")]
public IActionResult Get(Guid id)
{
var recipe = recipeService.GetRecipe(id);
if (recipe == null)
{
return NotFound();
}
return Ok(recipe);
}
The recipe service injects the IUnitOfWork and has the following method:
public Recipe GetRecipe(Guid id)
{
return repository.GetEntity(id, r => r.Ingredients);
}
Also I have the services registered as follows:
services.AddScoped<IRepositoryFactory, UnitOfWork<TContext>>();
services.AddScoped<IUnitOfWork, UnitOfWork<TContext>>();
services.AddScoped<IUnitOfWork<TContext>, UnitOfWork<TContext>>();
services.AddScoped<IRecipeService, RecipeService>();
My problem is when I'm getting a specified recipe(along with its ingredients) i got a "The connection has been restarted" error (in firefox). While debugging I can see that I have the recipe with its ingredients. But when returning Ok(recipe) and mapping the result to the entities, the IUnitOfWork is disposed after getting the first ingredient.
Anyone can help me out? Thanks
The problem was I was having a circular reference i wasn't getting any exception.
I fixed by adding the following in the ConfigureServices method of the Startup class:
services.AddMvc().AddJsonOptions(options =>
{
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});
Environment:
I am working in Webapi. There is 2 entity classes which are follows;
public class Class1
{
public Class1()
{
this.items = new HashSet<Class2>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Class2> items { get; set; }
}
public class Class2
{
public int Id { get; set; }
public string Name { get; set; }
public int Class1Id { get; set; }
public virtual Class1 class1 { get; set; }
}
Business Layer:
The buniess layer have the following codes;
public class Class1Logic : IClass1Logic
{
private readonly IClass1Repository _repo;
public Class1Logic(IClass1Repository repository)
{
_repo = repository;
}
public async Task<bool> AddClass1ItemAsync(Class1 item)
{
_repo.Add(item);
bool status = await _repo.SaveAsync();
return status;
}
public async Task<Class1> GetClass1ItemAsync(int id)
{
return await _repo.GetAsync(id);
}
}
public class Class2Logic : IClass1Logic
{
private readonly IClass2Repository _repo;
public Class2Logic(IClass2Repository repository)
{
_repo = repository;
}
public async Task<bool> AddClass2ItemAsync(Class2 item)
{
_repo.Add(item);
bool status = await _repo.SaveAsync();
return status;
}
public async Task<Class2> GetClass2ItemAsync(int id)
{
return await _repo.GetAsync(id);
}
}
ViewModels:
public class Class1Model
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Class2Model
{
public int Id { get; internal set; }
public string Name { get; set; }
public int Class1Id { get; set; }
public string Class1Name { get; internal set; }
}
Controllers:
There are 2 contrtollers like Class1Controller and Class2Controller. Both have all CRUD operations.
[RoutePrefix("api/class1items")]
public class Class1Controller : ApiController
{
private readonly IClass1Logic _class1Logic;
private ModelFactory TheFactory;
public Class1Controller(IClass1Logic class1Logic)
{
_class1Logic = class1Logic;
TheFactory = new ModelFactory();
}
[Route("")]
public async Task<IHttpActionResult> Post(Class1Model class1Model)
{
var item = TheFactory.Parse(class1Model);
bool result = await _class1Logic.AddClassItemAsync(item);
if (!result)
{
return BadRequest("Error");
}
string uri = Url.Link("GetLabById", new { id = item.Id });
return Created(uri, TheFactory.Create(item));
}
[Route("{id:int}", Name = "GetClass1ItemById")]
public async Task<IHttpActionResult> GetClass1Item(int id)
{
Class1 item = await _class1Logic.GetClassItemAsync(id);
if (item == null)
{
return NotFound();
}
return Ok(TheFactory.Create(item));
}
}
[RoutePrefix("api/class2items")]
public class Class2Controller : ApiController
{
private readonly IClass2Logic _class2Logic;
private ModelFactory TheFactory;
public Class2Controller(IClass2Logic class2Logic)
{
_class2Logic = class2Logic;
TheFactory = new ModelFactory();
}
[Route("")]
public async Task<IHttpActionResult> Post(Class2Model class2Model)
{
var item = TheFactory.Parse(class2Model);
***//Here item should include Class1 object even if user give ClassId in class2Model***
bool result = await _class2Logic.AddClassItemAsync(item);
if (!result)
{
return BadRequest("Error");
}
string uri = Url.Link("GetClass2ItemById", new { id = item.Id });
return Created(uri, TheFactory.Create(item));
}
}
There is not dependecies in Class1. So all operations are fine. In Class2Controller post method, I got the model object as following to create Class2.
{
"id": 0,
"name": "string",
"class1Id": 1
}
Understanding:
I need to return this viewmodel to user after the create the record. The record created successfully but when mapping to viewmodel i got null exception as Class1 object not in the Class2 object.
In order to get the Class2 object including class1 object, I need to give the class1Object in the request object.
For this i need to find the Class1 object with Class1Id in the request object.
ViewMapper Code:
public class ModelFactory
{
public Class1Model Create(Class1 item)
{
return new Class1Model
{
Id = item.Id,
Name = item.Name
};
}
public Class2Model Create(Class2 item)
{
return new Class2Model
{
Id = item.Id,
Name = item.Name,
Class1Id = item.class1.Id,
Class1Name = item.class1.Name
};
}
public Class1 Parse(Class1Model modelItem)
{
return new Class1
{
Id = modelItem.Id,
Name = modelItem.Name
};
}
public Class2 Parse(Class2Model modelItem)
{
return new Class2
{
Id = modelItem.Id,
Name = modelItem.Name,
Class1Id = modelItem.Class1Id,
***/*Issue Place*/
//class1 = Need to set property by getting object using modelItem.Class1Id***
};
}
}
Issue:
Now i need to call get method of Class1Controller by passing Class1Id.
How to call and is this correct? or my design is bad?
This is initial case. If my Class3 have both Class1 and Class2 again i need to call methods of Class1 and Class2.
Please help to find the correct solution in this case
Note: I added comments the issue area to understand
Well, just to fix this issue you need to manually call _class1Logic.GetClass1ItemAsync after saving. However this doesn't look good.
More elegant ways to fix it:
1) If you always need Class2.Class1 field to be filled use Include when you fetch data (in repository): dbContext.Set<Class2>().Include(c => c.class1).
2) Also you can turn on LazyLoading for EF - I assume it should work in your case.
3) Inject class1Repo to class2Logic and fix up class1 reference after saving - in case if you don't want to enable lazy loading or item was detached from context after save method
Thoughts about design:
I suggest you to look at Automapper or simular libraries instead of ModelFactory where you going to have all mapping logic
Edit: About generic repository: you can modify you GetAsync method
public async Task<T> GetAsync<T>(int id, params Expression<Func<T, object>>[] includes)
where T: class, IEntity
{
var query = context.Set<T>().AsQueryable();
if (includes.Length > 0)
{
query = includes.Aggregate(query,
(current, include) => current.Include(include));
}
return await query.FirstOrDefaultAsync(x => x.Id == id);
}
IEntity interface:
interface IEntity
{
int Id { get; }
}
With this implementation you can use
await _repo.GetAsync<Class2>(id, x => x.class1);
I have create a Generic Repository (Using EF 6.1.1), which I am using in several projects and it works very well.
I have implemented a 'soft' delete feature, where we mark data as deleted, but do not actually remove it from the database.
I can then filter all my queries as all entities inherit from a base entity which has the IsDeleted property. This is all works nicely, but it obviously does not filter out any of the 'soft deleted' child entities.
I am unsure how to go about doing this in a generic way, as I dont want to have to over code a solution into every respoitory, that really defeats the reason for having a generic repo.
this is an example of my current Generic Repo
public sealed class MyRepository<T> : IRepository<T> where T : BaseEntity
{
public String CurrentUser { get; set; }
private readonly MyObjectContext context;
private readonly Configuration configuration = ConfigurationManager.GetConfiguration();
private IDbSet<T> entities;
private IDbSet<T> Entities
{
get { return entities ?? (entities = context.Set<T>()); }
}
public MyRepository(MyObjectContext context, String userName = null)
{
this.context = context;
var providerManager = new DataProviderManager(configuration);
var dataProvider = (IDataProvider)providerManager.LoadDataProvider();
dataProvider.InitDatabase();
CurrentUser = userName;
}
public void Dispose()
{
//do nothing at the moment
}
public T GetById(Guid id)
{
return Entities.FirstOrDefault(x => x.Id == id && !x.IsDeleted);
}
public IQueryable<T> GetAll()
{
return Entities.Where(x => !x.IsDeleted);
}
public IQueryable<T> Query(Expression<Func<T, bool>> filter)
{
return Entities.Where(filter).Where(x => !x.IsDeleted);
}
public void Delete(T entity)
{
if (configuration.HardDelete)
{
HardDelete(entity);
}
else
{
SoftDelete(entity);
}
}
private void HardDelete(T entity)
{
try
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
Entities.Attach(entity);
Entities.Remove(entity);
}
catch (DbEntityValidationException ex)
{
var msg = string.Empty;
foreach (var validationErrors in ex.EntityValidationErrors)
foreach (var validationError in validationErrors.ValidationErrors)
msg += Environment.NewLine + string.Format("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
var fail = new Exception(msg, ex);
throw fail;
}
}
private void SoftDelete(T entity)
{
entity.IsDeleted = true;
Update(entity);
}
}
Any help on this would be great.
Thanks
Someone has built a global filter, you can try it and install it from nuget EntityFramework.Filters.
https://github.com/jbogard/EntityFramework.Filters
Here is an example how to use it.
public abstract class BaseEntity
{
public int Id { get; set; }
public bool IsDeleted { get; set; }
}
public class Foo : BaseEntity
{
public string Name { get; set; }
public ICollection<Bar> Bars { get; set; }
}
public class Bar : BaseEntity
{
public string Name { get; set; }
public int FooId { get; set; }
public Foo Foo { get; set; }
}
public class AppContext : DbContext
{
public DbSet<Foo> Foos { get; set; }
public DbSet<Bar> Bars { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Registers and configures it first.
DbInterception.Add(new FilterInterceptor());
var softDeleteFilter = FilterConvention.Create<BaseEntity>("SoftDelete",
e => e.IsDeleted == false); // don't change it into e => !e.IsDeleted
modelBuilder.Conventions.Add(softDeleteFilter);
}
}
Then you can enable it in your repository constructor or somewhere after db context instance is created because the filters are disabled by default.
using (var db = new AppContext())
{
db.EnableFilter("SoftDelete");
var foos = db.Foos.Include(f => f.Bars).ToArray(); // works on Include
}
using (var db = new AppContext())
{
db.EnableFilter("SoftDelete");
var foos = db.Foos.ToArray();
foreach (var foo in foos)
{
var bars = foo.Bars; // works on lazy loading
}
}
using (var db = new AppContext())
{
db.EnableFilter("SoftDelete");
var foos = db.Foos.ToArray();
foreach (var foo in foos)
{
db.Entry(foo).Collection(f => f.Bars).Load(); // works on manual loading
}
}
This filter is not needed anymore.
public IQueryable<T> Query(Expression<Func<T, bool>> filter)
{
return Entities.Where(filter);//.Where(x => !x.IsDeleted);
}
As long as you have enabled it.
public MyRepository(MyObjectContext context, String userName = null)
{
this.context = context;
if (!configuration.HardDelete)
{
this.context.EnableFilter("SoftDelete");
}
}
I was having the same problem but my method to solve was little different i used a genric base interface IGenricInterface with IsDeleted as property
public int DeletebyId(string Id)
{
var Ent = (IGenricInterface)_sitecontext.Set<TEntity>().Find(Id);
Ent.IsDeleted = 1;
}
and for hardDelete
_sitecontext.Set<TEntity>().Remove(Ent);
This is on ID but offcourse you can do it on EnTity as well
I'm have a Base Repository and all Entities repositories inherits from that.
In my testes i create a Fake DbContext and Fake DbSet to test my repositories, but when implementing some methods in my FakeDbContext I'm not able to implement the IDbContext.Entry method:
public class FakeDbContext : IDbContext
{
private IDbSet<Usuario> _usuario;
private IDbSet<Atividade> _atividade;
private IDbSet<Autor> _autor;
private IDbSet<CategoriaModulo> _categoriaModulo;
private IDbSet<CategoriaMateria> _categoriaMateria;
private IDbSet<Site> _site;
private IDbSet<Modulo> _modulo;
private IDbSet<Perfil> _perfil;
private IDbSet<CategoriaGaleriaImagem> _categoriaGaleriaImagem;
public IDbSet<Usuario> Usuario { get { return _usuario ?? (_usuario = new FakeDbSet<Usuario>()); } set { } }
public IDbSet<Atividade> Atividade { get { return _atividade ?? (_atividade = new FakeDbSet<Atividade>()); } set { } }
public IDbSet<Autor> Autor { get { return _autor ?? (_autor = new FakeDbSet<Autor>()); } set { } }
public IDbSet<CategoriaModulo> CategoriaModulo { get { return _categoriaModulo ?? (_categoriaModulo = new FakeDbSet<CategoriaModulo>()); } set { } }
public IDbSet<CategoriaMateria> CategoriaMateria { get { return _categoriaMateria ?? (_categoriaMateria = new FakeDbSet<CategoriaMateria>()); } set { } }
public IDbSet<Site> Site { get { return _site ?? (_site = new FakeDbSet<Site>()); } set { } }
public IDbSet<Modulo> Modulo { get { return _modulo ?? (_modulo = new FakeDbSet<Modulo>()); } set { } }
public IDbSet<Perfil> Perfil { get { return _perfil ?? (_perfil = new FakeDbSet<Perfil>()); } set { } }
public IDbSet<CategoriaGaleriaImagem> CategoriaGaleriaImagem { get { return _categoriaGaleriaImagem ?? (_categoriaGaleriaImagem = new FakeDbSet<CategoriaGaleriaImagem>()); } set { } }
public void SaveChanges()
{
//do nothing
}
public IDbSet<TEntity> Set<TEntity>() where TEntity : class
{
foreach (PropertyInfo property in typeof(FakeDbContext).GetProperties())
{
if (property.PropertyType == typeof(IDbSet<TEntity>))
return property.GetValue(this, null) as IDbSet<TEntity>;
}
throw new Exception("Type collection not found");
}
public System.Data.Entity.Infrastructure.DbEntityEntry Entry<TEntity>(TEntity entity) where TEntity : class
{
}
}
The last method I'm not able to implementing, can you guys help me?
I'm using this Entry method to update a Entity in my base repository:
public abstract class BaseRepository<TEntity> : IBaseRepository<TEntity> where TEntity : class
{
#region Fields
protected TEntity EntityType;
protected IDbSet<TEntity> DbSet;
#endregion
#region Properties
public IDbContext DbContext
{
get
{
return DbContextFactory.Instance.GetOrCreateContext();
}
}
#endregion
#region Constructors
protected BaseRepository()
{
this.EntityType = DependencyResolverFactory.Instance.Get<TEntity>();
this.DbSet = DbContext.Set<TEntity>();
}
#endregion
#region Methods
public virtual void Add(TEntity entity)
{
this.DbSet.Add(entity);
}
public virtual void Remove(TEntity entity)
{
this.DbSet.Remove(entity);
}
public virtual void RemoveById(object id)
{
TEntity entity = this.GetById(id);
this.DbSet.Remove(entity);
}
public virtual void Edit(TEntity entity)
{
this.DbContext.Entry(entity).State = EntityState.Modified;
}
public virtual TEntity GetById(object id)
{
return (TEntity)this.DbSet.Find(id);
}
public virtual IList<TEntity> GetAll()
{
return ((IEnumerable<TEntity>)this.DbSet).ToList();
}
#endregion
}
Read this and all linked questions before you continue. Unit testing anything returning EF related classes or working with linq-to-entities is dangerous.
Give up with unit testing your repositories and instead unit test your application logic by faking repositories themselves. If you want to test your repositories create integration tests talking to the real database.