I have the following generic repository:
public class GenericRepository<TEntity> where TEntity : class {
private EFDbContext context;
private DbSet<TEntity> dbSet;
public GenericRepository(EFDbContext context) {}
public IEnumerable<TEntity> GetAll() {}
public IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = "") {}
public TEntity GetByID(int id) {}
public void Insert(TEntity entity) {}
public void Delete(int id) {}
public void Delete(TEntity entity) {}
public void Update(TEntity entity) {}
}
Now I understand how I can use this for my entities, but I don't understand what to do if an entity needs more than just these methods? Say I have an entity called 'Tournament' and I want to get all the groups in that tournament, where would the 'GetTournamentGroups' method go?
Should I, instead of using a generic repository, write a basic repository interface which all the entities' own interfaces inherit from and then just add the additional methods needed for each entity?
You could also simply use
var TournamentGroups = new List<Group>();
using( var GenericRepo = new GenericRepository<Group>())
{
TournamentGroups = GenericRepo.Get(group => group.TournamentId == tournamentId);
}
somewhere with your normal repository. It seems a little overkill to make a class which does this.
Method GetTournamentGroups() seems to belong to the layer above Repository layer:
class TournamentModel
{
private int tournamentId; // initialized in constructor
private GenericRepository<Group> repository; // initialized in constructor
IEnumerable<Group> GetTournamentGroups()
{
return repository.Get( group => group.TournamentId == tournamentId);
}
}
I'd recommend using IQueryable insteam of IEnumerable in your GenericRepository, though. It would be more flexible if you need to apply filters or sort expressions before you make an actual database query.
Related
First question here, I hope I'm doing it right.
I'm using Entity Framework Core 5.0 (Code First) with an onion architecture (data/repo/service/mvc) and so I have a service for each table (almost).
It's work well but now I need to manage (get, insert, update, delete) about 150 tables which all have the same structure (Id, name, order).
I have added each of them as Entity class and their DbSet too in my DbContext, but I don't want to make 150 services, I would like to have a generic one .
How can I bind it to my generic repository ?
public class Repository<T> : IRepository<T> where T : BaseEntity
{
private readonly ApplicationContext context;
private DbSet<T> entities;
private readonly RepositorySequence repoSequence;
private string typeName { get; set; }
public Repository(ApplicationContext context)
{
this.context = context;
entities = context.Set<T>();
this.repoSequence = new RepositorySequence(context);
this.typeName = typeof(T).Name;
}
public T Get(long plng_Id)
{
return entities.SingleOrDefault(s => s.Id == plng_Id);
}
[...]
}
In an ideal world, would like to have something like this :
public async Task Insert(dynamic pdyn_Entity)
{
Type DynamicType = Type.GetType(pdyn_Entity);
Repository<DynamicType> vobj_Repo = new Repository<DynamicType>(mobj_AppContext);
long Id = await vobj_Repo.InsertAsync(pdyn_Entity);
}
But I can try to get type from DbSet string Name too, I just managed to retrieve some data :
public IEnumerable<object> GetAll(string pstr_DbSetName)
{
return ((IEnumerable<BaseEntity>)typeof(ApplicationContext).GetProperty(pstr_DbSetName).GetValue(mobj_AppContext, null));
}
I've tried the following method (2.0 compatible apparently) to get the good DbSet, not working neither (no Query) : https://stackoverflow.com/a/48042166/10359024
What am I missing?
Thanks a lot for your help
Not sure why you need to get type?
You can use something like this.
Repository.cs
public class Repository<T> : IRepository<T> where T : BaseEntity
{
private readonly ApplicationContext context;
private DbSet<T> entities;
public Repository(ApplicationContext context)
{
this.context = context;
entities = context.Set<T>();
}
public List<T> Get()
=> entities.ToList();
public T Get(long plng_Id)
=> entities.Find(plng_Id);
public long Save(T obj)
{
if (obj.ID > 0)
entities.Update(obj);
else
entities.Add(obj);
return obj.ID;
}
public void Delete(T obj)
=> entities.Remove(obj);
}
Then you can use either one of these 2 options you want
Multiple repositories following your tables
UserRepository.cs
public class UserRepository : Repository<User> : IUserRepository
{
private readonly ApplicationContext context;
public UserRepository(ApplicationContext context)
{
this.context = context;
}
}
BaseService.cs
public class BaseService : IBaseService
{
private readonly ApplicationContext context;
private IUserRepository user;
private IRoleRepository role;
public IUserRepository User { get => user ??= new UserRepository(context); }
public IRoleRepository Role { get => user ??= new RoleRepository(context); }
public BaseService(ApplicationContext context)
{
this.context = context;
}
}
If you are lazy to create multiple repositories, can use this way also. Your service just simple call Repository with entity name.
BaseService.cs
public class BaseService : IBaseService
{
private readonly ApplicationContext context;
private IRepository<User> user;
private IRepository<Role> role;
public IRepository<User> User { get => user ??= new Repository<User>(context); }
public IRepository<Role> Role { get => role ??= new Repository<Role>(context); }
public BaseService(ApplicationContext context)
{
this.context = context;
}
}
Finally, you can call service like this. You can use multiple services instead of BaseService if you want.
HomeController.cs
public class HomeController : Controller
{
private readonly IBaseService service;
public HomeController(IBaseService service)
{
this.service = service;
}
public IActionResult Index()
{
var user = service.User.Get();
return View(user);
}
public IActionResult Add(User user)
{
var id = service.User.Save(user);
return View();
}
}
I suggest to use first option (multiple repositories) because you may need to customise functions in own repository in future. And create service class following your controller name. For example, you have HomeController, UserController, etc. Create HomeService, UserService and link them with BaseService so that you can create customised functions in their own service class.
I assume you have a base entity like this:
public class BaseEntity
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Order { get; set; }
}
Then you can do CRUD operations in your generic repository like this:
public int Create(T item)
{
if (item == null) return 0;
entities.Add(item);////SaveChanges
return item.Id;
}
public void Update(T updatedItem)
{
context.SetModified(updatedItem);//SaveChanges
}
public IQueryable<T> All()
{
return entities();
}
And in each of the methods you have access to your 3 common fields in BaseEntity
Thank you all for your responses.
I need to have the type because I am using a blazor component which automatically binds to these tables. This component has the name of the desired entity class (in string) as a parameter. Thanks to #Asherguru's response I was able to find a way to do this:
1 - I made a 'SedgmentEntity' Class :
public abstract class SegmentEntity : ISegmentEntity
{
public abstract long Id { get; set; }
public abstract string Name { get; set; }
public abstract short? Order { get; set; }
}
2 - A SegmentRepository which is typed via Reflection:
public class SegmentRepository : ISegmentRepository
{
private readonly ApplicationContext context;
private readonly RepositorySequence repoSequence;
public SegmentRepository(ApplicationContext context)
{
this.context = context;
this.repoSequence = new RepositorySequence(context);
}
public async Task<long> Insert(string pstr_EntityType, SegmentEntity pobj_Entity)
{
Type? vobj_EntityType = Assembly.GetAssembly(typeof(SegmentEntity)).GetType("namespace.Data." + pstr_EntityType);
if (vobj_EntityType != null)
{
// create an instance of that type
object vobj_Instance = Activator.CreateInstance(vobj_EntityType);
long? nextId = await repoSequence.GetNextId(GetTableName(vobj_EntityType));
if (nextId == null)
{
throw new TaskCanceledException("Sequence introuvable pour " + vobj_EntityType.FullName);
}
PropertyInfo vobj_PropId = vobj_EntityType.GetProperty("Id");
vobj_PropId.SetValue(vobj_Instance, nextId.Value, null);
PropertyInfo vobj_PropName = vobj_EntityType.GetProperty("Name");
vobj_PropName.SetValue(vobj_Instance, pobj_Entity.Name, null);
PropertyInfo vobj_PropOrder = vobj_EntityType.GetProperty("Order");
vobj_PropOrder.SetValue(vobj_Instance, pobj_Entity.Order, null);
return ((SegmentEntity)context.Add(vobj_Instance).Entity).Id;
}
}
public IEnumerable<object> GetAll(string pstr_EntityType)
{
Type? vobj_EntityType = Assembly.GetAssembly(typeof(SegmentEntity)).GetType("namespace.Data." + pstr_EntityType);
if (vobj_EntityType != null)
{
PropertyInfo vobj_DbSetProperty = typeof(ApplicationContext).GetProperties().FirstOrDefault(prop =>
prop.PropertyType.FullName.Contains(vobj_EntityType.FullName));
return (IEnumerable<object>)vobj_DbSetProperty.GetValue(context, null);
}
return null;
}
}
I still have to handle the Get and the Delete functions but it should be fine.
Then I will be able to create a single service which will be called by my component.
Thanks again !
I'm using Spring boot with mongodb. I've extended PagingAndSortingRepository repository and added the following function
#Query("{'title':{ $nin: [?0]}}")
List<Item> findItem(String[] exclude);
I want to be able to pass it an array of regular expressions such as /dog/,/cat/,/horse/ to exclude any item that may have one of these in it's title.
The above function does not work because the exclude is converted to a string. How can I pass an array of regular expressions to be able to do the above?
You can work it out by using a Querydsl predicate in one of your controller method.
Add something like this to your controller:
#RequestMapping(value="/search/findByNameRegexNotIn", method = RequestMethod.GET)
#ResponseBody
public List<Item> findByNameRegexNotIn(#RequestParam(name = "name") List<String> names) {
// build a query predicate
BooleanBuilder predicate = new BooleanBuilder(); // comes from the Querydsl library
for (String name : names) {
predicate.and(QItem.item.name.contains(name).not()); // the QItem class is generated by Querydsl
}
List<Item> items = (List<Item>)repository.findAll(predicate);
return items;
}
You can of course add a Pageable parameter and return a Page<Item> instead of a List.
Edit: another solution if you use Querydsl for this sole purpose is to override the default bindings of your query parameter.
public interface ItemRepository extends CrudRepository<Item, String>,
QueryDslPredicateExecutor<Item>, QuerydslBinderCustomizer<QItem> {
#Override
default public void customize(QuerydslBindings bindings, QItem item) {
bindings.bind(item.name).all(
(path, values) -> path.matches(StringUtils.collectionToDelimitedString(values, "|")).not());
// disable query on all parameters but the item name
bindings.including(item.name);
bindings.excludeUnlistedProperties(true);
}
}
The controller method:
#RequestMapping(value="/search/query", method = RequestMethod.GET)
#ResponseBody
public List<Item> queryItems(
#QuerydslPredicate(root = Item.class) Predicate predicate) {
List<Item> items = (List<Item>)repository.findAll(predicate);
return items;
}
Edit: if you don't wan't to override the default QuerydslBinderCustomizer#customize, you can also implement your own binder and specify it in the controller method.
public interface ItemRepository extends CrudRepository<Item, String>,
QueryDslPredicateExecutor<Item> {
...
}
The controller method:
#RequestMapping(value="/search/query", method = RequestMethod.GET)
#ResponseBody
public List<Item> queryItems(
#QuerydslPredicate(root = Item.class, bindings = ItemBinder.class) Predicate predicate) {
List<Item> items = (List<Item>)repository.findAll(predicate);
return items;
}
The binder class:
class ItemBinder implements QuerydslBinderCustomizer<QItem> {
#Override
public void customize(QuerydslBindings bindings, QItem item) {
bindings.bind(item.name).all(
(path, values) -> path.matches(StringUtils.collectionToDelimitedString(values, "|")).not()
);
bindings.including(item.name);
bindings.excludeUnlistedProperties(true);
}
}
Edit: for the sake of exhaustivity and those who don't want to hear about Querysl. Using the solution proposed in Spring Data Mongodb Reference.
Define a custom repository interface:
interface ItemRepositoryCustom {
public Page<Item> findByNameRegexIn(Collection<String> names, Pageable page);
}
Define an custom repository implementation (Impl postfix required!):
public class ItemRepositoryImpl implements ItemRepositoryCustom {
#Autowired
private MongoOperations operations;
#Override
public Page<Item> findByNameRegexNotIn(Collection<String> names, Pageable pageable) {
String pattern = StringUtils.collectionToDelimitedString(names, "|");
// this time we use org.springframework.data.mongodb.core.query.Query instead of Querydsl predicates
Query query = Query.query(where("name").regex(pattern).not()).with(pageable);
List<Item> items = operations.find(query, Item.class);
Page<Item> page = new PageImpl<>(items, pageable, items.size());
return page;
}
}
Now simply extend ItemRepositoryCustom:
public interface ItemRepository extends MongoRepository<Item, String>, ItemRepositoryCustom {
...
}
And you're done!
You can pass a java.util.regex.Pattern[] to the method. This will be converted to regex array under the hood:
#Query("{'title':{ $nin: ?0}}")
List<Item> findItem(Pattern[] exclude);
This code works fine, However, if I run execute a storedprocedure in my unit of work class (or any update, delete, add operation), I'm still getting original the data. Actually, I have already a solution(posted below in controller) but Im sure this is not the most elegant way, I hope someone can help me refactor the code. please help. Thanks
My unit of work
public class UnitOfWork : IUnitOfWork, IDisposable
{
private readonly ObjectContext _context;
private BookRepository _books;
public UnitOfWork(ObjectContext context)
{
if (context == null)
{
throw new ArgumentNullException("Context was not supplied");
}
_context = context;
}
public IRepository<Book> Books
{
get
{
if (_books== null)
{
_books= new BookRepository (_context);
}
return _books;
}
}
public void UpdateAuthor(int id)
{
_context.ExecuteStoreCommand("sp_UpdateAuthor #param1",
new SqlParameter("param1", id));
}
public void Commit()
{
_context.SaveChanges();
}
Book Repository
public class BookRepository : Repository<Book>
{
public BookRepository (ObjectContext context)
: base(context)
{
}
public override Machine GetById(object id)
{
return _objectSet.SingleOrDefault(s => s.Id== (int)id);
}
}
Generic Repository
public abstract class Repository<T> : IRepository<T>
where T : class, IAuditEntity
{
protected IObjectSet<T> _objectSet;
public Repository(ObjectContext context)
{
_objectSet = context.CreateObjectSet<T>();
}
public abstract T GetById(object id);
public IEnumerable<T> GetAll()
{
return _objectSet;
}
public IEnumerable<T> Query(Expression<Func<T, bool>> filter)
{
return _objectSet.Where(filter);
}
public void Add(T entity)
{
_objectSet.AddObject(entity);
}
public void Remove(T entity)
{
_objectSet.DeleteObject(entity);
}
}
Controller Code
public class HomeController : Controller
{
private IUnitOfWork _unitOfWork;
#region Core Action Methods
public HomeController()
{
this._unitOfWork = new UnitOfWork(((IObjectContextAdapter)new BookContext()).ObjectContext);
}
private IEnumerable<BookViewModel> GetBookdsViewModels(int id)
{
//THE CODE WHERE ITS NOT RETURNING THE UPDATED VLAUES
//var query = _unitOfWork.Books.GetAll().Where(d => d.Id== id);
//I WANT TO CHANGE THIS CODE
ObjectContext objectContext = ((IObjectContextAdapter)new BookContext()).ObjectContext;
ObjectSet<Book> set = objectContext.CreateObjectSet<Book>();
set.MergeOption = MergeOption.OverwriteChanges;
var query = from a in set
where a.Id== id && !a.IsDeleted
select a;
return query
.Select(
c => new BookViewModel
{
Id = c.Id ,
Name = c.Name
});
}
I believe the problem is because you're executing things directly against your database, and then trying to refer back to the local copy that's stored in your repository and they're different.
public void UpdateAuthor(int id)
{
_context.ExecuteStoreCommand("sp_UpdateAuthor #param1",
new SqlParameter("param1", id));
}
When you run this query, you're making a change in your db instead of your local - why dont you do something like this:
public void UpdateAuthor(int id)
{
var book = Books.GetById(id);
/* make changes to your book object */
_unit.Save();
}
If you're wanting to make changes using store procs, you're going to have to dispose of your context, and recreate the repo's so that you're working with data from the DB instead of the local copy.
Expanding on Mark Oreta's answer, you need to ensure your Model is updated after manually executing a stored proc on your database. If you must manually call that stored proc then try this afterward:
_context.Entry<Book>(instanceOfAuthor).Reload();
Which might be:
_context.Entry<Book>(_context.Books.GetById(id)).Reload();
I need to have a CRUd operations on my class (CompetenceSpecific).
Competence has three derived classes - CompetenceFunction, CompetenceArea and CompetenceSpecifc
The error I recieved:
There are no EntitySets defined for the specified entity type 'CompetencyManagement.Domain.Entities.CompetenceFunction'. If 'CompetencyManagement.Domain.Entities.CompetenceFunction' is a derived type, use the base type instead. Parameter name: TEntity
How should I correct this? Please suggest a solution that would solve my problem. Thanks
Please check the code below, I removed some parts of the code for simplicity.
--MODEL
public class Competence
{
public int CompetenceID { get; set; }
public int CourseID { get; set; }
...
}
public class CompetenceFunction : Competence
{
}
--REPOSITORY and interfaces
public interface IRepository<T> where T : class
{
T GetById(object id);
IEnumerable<T> GetAll();
IEnumerable<T> Query(Expression<Func<T, bool>> filter);
void Add(T entity);
void Remove(T entity);
}
public abstract class Repository<T> : IRepository<T>
where T : class
{
protected IObjectSet<T> _objectSet;
public Repository(ObjectContext context)
{
_objectSet = context.CreateObjectSet<T>();
}
...
}
public class CompetenceFunctionRepository : Repository<CompetenceFunction>
{
public CompetenceFunctionRepository(ObjectContext context)
: base(context)
{
}
public override CompetenceFunction GetById(object id)
{
return _objectSet.SingleOrDefault(s => s.CompetenceID == (int)id);
}
}
--UNIT oF WORK
public interface IUnitOfWork
{
IRepository<CompetenceFunction> CompetenceFunctions { get; }
IRepository<CompetenceArea> CompetenceAreas { get; }
IRepository<CompetenceSpecific> CompetenceSpecifics { get; }
void Commit();
}
public class UnitOfWork : IUnitOfWork, IDisposable
{
private CompetenceFunctionRepository _competencefunction;
private CompetenceAreaRepository _competencearea;
private CompetenceSpecificRepository _competencespecifc;
public UnitOfWork(ObjectContext context)
{
if (context == null)
{
throw new ArgumentNullException("Context was not supplied");
}
_context = context;
}
#region IUnitOfWork Members
public IRepository<CompetenceFunction> CompetenceFunctions
{
get
{
if (_competencefunction == null)
{
_competencefunction = new CompetenceFunctionRepository(_context);
}
return _competencefunction;
}
}
public IRepository<CompetenceArea> CompetenceAreas
{
get
{
if (_competencearea == null)
{
_competencearea = new CompetenceAreaRepository(_context);
}
return _competencearea;
}
}
public IRepository<CompetenceSpecific> CompetenceSpecifics
{
get
{
if (_competencespecifc == null)
{
_competencespecifc = new CompetenceSpecificRepository(_context);
}
return _competencespecifc;
}
}
--Im getting an error in this part of Repository
public Repository(ObjectContext context)
{
_objectSet = context.CreateObjectSet<T>();
}
There are no EntitySets defined for the specified entity type 'CompetencyManagement.Domain.Entities.CompetenceFunction'. If 'CompetencyManagement.Domain.Entities.CompetenceFunction' is a derived type, use the base type instead. Parameter name: TEntity
Here's how I implement in the controller
private IUnitOfWork _unitOfWork;
var a = _unitOfWork.CompetenceFunctions.GetAll();
return View(a);
You have to get derived type by the OfType function, e.g.
context.CreateObjectSet<Competence>().OfType<CompetenceFunction>()
In your case that would mean that there is only a CompetenceRepository that serves all derivatives of Competence.
Edit
(After your comment)
First, UoW is meant for temporarily storing changes that should be dealt with in one batch (like changes to be committed to the database). GetAll and similar functions are repository stuff.
But do you need repositories? I like this post. When beginning to know EF, I would focus on the ins and outs of EF without getting distracted too much by surrounding architecture. E.g. start with services that at the inside communicate directly with the context and expose methods like GetCompetenceFunctions, GetCompetenceAreas (using OfType), and SaveCompetenceFunction, ....
You can address these service methods directly from action methods in the MVC controllers.
I'm working on a generic repository using Entity Framework/MVC3/Ninject.MVC3. The interface looks like this.
public interface IRepository<TEntity> where TEntity : class
{
IQueryable<TEntity> Query { get; }
void Add(TEntity entity);
void Edit(TEntity entity);
void Delete(TEntity entity);
}
My Concrete implementation looks like this.
public class EFRepository<T> : IRepository<T> where T : class
{
private EFDbContext context = new EFDbContext();
public IQueryable<T> Query
{
get { return context.Set<T>().AsQueryable(); }
}
public void Add(T entity)
{
context.Set<T>().Add(entity);
context.SaveChanges();
}
public void Edit(T entity)
{
context.Entry<T>(entity).State = System.Data.EntityState.Modified;
context.SaveChanges();
}
public void Delete(T entity)
{
context.Set<T>().Remove(entity);
context.SaveChanges();
}
}
Ninject has the binding
kernel.Bind(typeof(IRepository<>)).To(typeof(EFRepository<>));
What I need to do is get the last insert ID in the concrete implementation. I have a transaction table that will get an insert based on the last table and insert ID. I could call the transaction from the controller, but I'd rather just get it all done in the Data Access Layer, so I can easily write the transaction after the last insert/update.
First, is the above example the proper way to implement the generic repository. Second is there a way to get the data I want through this method?
When you call Add on an entity, and the table has an identity column (auto increment) then you can just check the entity after the SaveChanges() and it will contain the generated ID.