I want to modify existing generic repository to add optional select functionality like in Entity Framework Core.
Desired result:
private readonly IUnitOfWork _unit;
// ...
// without using select functionality
IEnumerable<Entity> entities = await _unit.Account.AllAsync();
Entity? x = await _unit.Account.SingleAsync(x => x == id);
// using select functionality
IEnumerable<DTO> y = await _unit.Account.AllAsync(select: x => new DTO
{
Name = x.Name
});
DTO? y = await _unit.Account.SingleAsync(x => x == id, select: x => new DTO
{
Name = x.Name
});
I tried to implement solution from this question Select specific columns in a generic repository function but parameter was required and I want it to be optional.
For simplicity I only leave methods to which I want to add this functionality in generic repository:
in IBaseRepository.cs
public interface IBaseRepository<T> where T : BaseEntity
{
Task<IEnumerable<T>> AllAsync(
Expression<Func<T, bool>>? filter = null,
Func<IQueryable<T>, IOrderedQueryable<T>>? order = null,
Func<IQueryable<T>, IIncludableQueryable<T, object>>? include = null,
int skip = 0,
int take = int.MaxValue,
Track track = Track.NoTracking);
Task<T?> SingleAsync(
Expression<Func<T, bool>> filter, Func<IQueryable<T>,
IIncludableQueryable<T, object>>? include = null,
Track track = Track.Tracking);
}
in BaseRepository.cs
public class BaseRepository<T> : IBaseRepository<T> where T : BaseEntity
{
private readonly DataContext _context;
internal DbSet<T> _set;
public BaseRepository(DataContext context)
{
_context = context;
_set = context.Set<T>();
}
public async Task<IEnumerable<T>> AllAsync(
Expression<Func<T, bool>>? filter = null,
Func<IQueryable<T>, IOrderedQueryable<T>>? order = null,
Func<IQueryable<T>, IIncludableQueryable<T, object>>? include = null,
int skip = 0, int take = int.MaxValue, Track track = Track.NoTracking)
{
IQueryable<T> query = _set;
switch (track)
{
case Track.NoTracking:
query = query.AsNoTracking();
break;
case Track.NoTrackingWithIdentityResolution:
query = query.AsNoTrackingWithIdentityResolution();
break;
default:
query = query.AsTracking();
break;
}
query = skip == 0 ? query.Take(take) : query.Skip(skip).Take(take);
query = filter is null ? query : query.Where(filter);
query = order is null ? query : order(query);
query = include is null ? query : include(query);
return await query.ToListAsync();
}
public async Task<T?> SingleAsync(
Expression<Func<T, bool>> filter,
Func<IQueryable<T>, IIncludableQueryable<T, object>>? include = null,
Track track = Track.Tracking)
{
IQueryable<T> query = _set;
switch (track)
{
case Track.NoTracking:
query = query.AsNoTracking();
break;
case Track.NoTrackingWithIdentityResolution:
query = query.AsNoTrackingWithIdentityResolution();
break;
default:
query = query.AsTracking();
break;
}
query = filter is null ? query : query.Where(filter);
query = include is null ? query : include(query);
return await query.SingleOrDefaultAsync();
}
}
in BaseEntity.cs (every class-dbtable would inherit from BaseEntity)
public abstract class BaseEntity
{
public Guid Id { get; set; } = Guid.NewGuid();
public DateTime CreatedAt { get; set; } = DateTime.Now;
public DateTime? UpdatedAt { get; set; }
}
Ask yourself honestly: "Why do I believe I need a Generic Repository?"
A: "Because I want to add a layer of abstraction over EF so I can replace it if needed."
You are a victim of a self-fulfilling prophecy; Handcuffing yourself and your application's capability to leverage EF to it's fullest. The solution will either be so limited or complex and problematic that EF will be considered too slow or incapable of doing what is needed and needs to be replaced with something someone convinces you is better/faster.
A: "Because I don't want to pollute my business logic with domain knowledge or EF-specific knowledge."
This is a lie. Passing expressions into your repository pollutes the domain with domain and EF-specific knowledge & limitations. If the expressions are passed to EF's Linq methods, they must conform to the domain and they must be palatable to EF. You remove the reference to EF, but still impose the EF's limitations on your consumer. You cannot have references to methods in the expressions, or use unmapped properties. EF and your chosen provider need to be able to translate these expressions down to SQL, otherwise you are left writing even more code to introduce your own expression parser and adapter.
A: "Because I want to be able to introduce a point of abstraction so I can more easily write unit tests."
This is a good reason, but it can be solved in a considerably more simple approach:
.
IQueryable<T> All();
IQueryable<T> Single(int id);
That's it. Your consumer can support filtering, sorting, pagination, exists check, count, projection, and even determine for itself whether the overhead of an async operation is necessary. It's dead simple to mock out, adding just a pinch of complexity to support passing back data for async consumption. The repository method can impose base-level filtering such as IsActive for soft-delete systems, or checking authorization for the current user and factoring that into the results returned.
Where you want to introduce a solution to satisfy "DNRY" (Do not repeat yourself) or enforce a level of conformity in your application, or between identical (not merely similar) concerns such as a Web API service and a web application,
that can be done by handing off to a common service that fetches and packages data in a consistent way using the repository to return DTOs. Otherwise treat the repository like you would an MVC Controller, scope it to be responsible for one consumer, and only have that one reason to change.
The typical argument against returning IQueryable is that it is a weak or leaky abstraction and hands the consumer a loaded shotgun. This is 100% true that it is a weak abstraction, this makes it very easy to mock out. If you don't need to mock it out for unit tests, You just "ain't gonna need it." As far as giving developers a loaded weapon, IMHO it is far, far better for a project to give implementation developers the tools they need to be able to write efficient expressions against the domain and the training/knowledge how to do it properly, and correct mistakes if and when they are found; than it is to try and abstract the technology away to "dumb it down" or in essence, end up introducing just as much complexity and ability to make a mess, just with your name across the interface rather than EF.
Related
There are a large number of examples on how to use the Generic Repository Pattern over Entitry Framework but none ever go into great detail. More often than not the example they use for accessing data from a webapi is a one to one mapping to the model.
For Eg. Get a single model object _carRepository<Car>.Get(id); or get all cars _carRepository<Car>.GetAll();
The way I have it implemented is in seperate layers like below:
Controller -> Service Layer -> Repository Layer -> DataAccess
I have my service layer using AutoMapper where I map from the model(entity) to a DTO and that gets returned to the controller who returns that.
The issue that I cant get my head around is how do I return an object from the repository layer which isnt of type < T>.
Eg. I want to return some data for a grid lets say a combination of multipe entities like a customers last number of purchases. Am I supposed to pass the Customer, Product, Orders repository into the service and make multiple calls to the database and aggregate? Or I was thinking in the cutomers repository just make a method which returns like a view dto of sorts already aggreating the data where it does a linq query and projects to this dto. The issue I have is that I'm sure my repository shouldnt know about dto's?
Would appreciate anyone who has thoughts on the correct way of doing this?
In short, I don't ever recommend using the Generic Repository (anti)pattern. The trouble is that it ends up being far too restrictive and inefficient. Methods like GetAll() serve only to end up materializing entire tables into memory. They don't accommodate things like projection (essentially what you are looking for) as well as eager loading related data, filtering, sorting, or things like supporting pagination.
The way I recommend looking at repositories is as a one-to-one supplier of data for a Controller in the MVC pattern. If you have a CarController you have a CarRepository to serve all data for that controller. If you break it down to CarController and AddCarController then similarly you can build repositories to serve each of these. These repositories are not Generic in that they don't just serve Car entities, but any and all entities from the DbContext that this controller (or service) needs rather than trying to marry repositories to a specific entity. This gives them only one concern for their existance, so a CarController only needs to worry about primarily one repository, and that repository only has to worry about serving that controller. (Rather than every controller that might want information about a Car)
Regarding using a Service between the controller and repository, I would only suggest this if there is a distinct requirement to provide that separation. For example, if you want to support both an MVC controller and a public facing WebAPI and you want the inputs and outputs provided to these to be 100% consistent. The data transported from Service to Consumer (Controllers, etc.) would be DTOs. This adds the complexity of needing to send details like sorting, pagination, and filtering etc. from the consumer to the service.
Removing the service can make interacting with the repository a lot easier, and you can leverage projection to populate the DTOs/view models actually sent to the view via the controller actions/endpoints. The way I facilitate this is leveraging IQueryable<TEntity> in the repository. This lets the repository handle low level filtering/rules if necessary and lets the consumer (controller) handle determining how it wants to consume the data.
For example if we have a CarController with a CarRepository and CarRepository has a method:
public IQueryable<Car> GetCars(bool includeInactive = false)
{
var query = _context.Cars.AsQueryable();
if (!includeInactive)
query = query.Where(x => x.IsActive);
return query;
}
When it comes time for the controller to request cars from the repository, it has full control over how to consume it:
var cars = await CarRepository.GetCars()
.Where(x => x.Make == make && x.Model == model)
.OrderBy(x => x.ModelYear)
.Select(x => new CarSummary
{
Make = x.Make,
Model = x.Model,
ModelYear = x.ModelYear,
Color = x.Color,
Features = x.Features.Select(...)
}.Skip(pageSize * (pageNumber-1))
.Take(pageSize)
.ToListAsync();
The controller has full control over how the data should come back including whether to run an async query or synchronous one all without adding any complexity to the repository. So here we can project (Select, or leverage Automapper and ProjectTo) to get whatever data we need from the entities and their related data. This leads to building far faster and memory/network efficient queries because the projections only worry about the data the end consumer actually needs. When we want to load an entity and its relations to perform an update, we can fetch the entity and eager load the related data we need to inspect/validate/update. Eager loading data is faster than lazy loading, but both use a fair bit of memory so we don't want to be serializing and transmitting entire object graphs. However, when doing an update we are typically only dealing with a single top-level object at a time.
The question then becomes "Why use a repository then?" The repository provides a nice abstraction for unit testing, and it can help standardize core rules you want in the system such as Active/Inactive (soft delete) and things like authorization in systems using multi-tenancy or otherwise having distinct access controls for the current user as to what data they should ever be able to see. If you don't have either of these requirements, then there isn't really much point to using a repository at all. The DbContext and it's DbSets essentially serve that role.
If you do have plans for multiple consumers, then the repository remains the same but the above consuming code happens in the Service, returning the DTO/ViewModel. This means that your controller will need to package and transmit standardized requests to the service or parameters covering whether it expects any sorting, pagination, etc. Again, I wouldn't recommend taking on that overhead and complexity unless there is a very real requirement justifying it. It just serves to make the code harder to work with.
This may not be complete answer but few things while working with Generic Repository.
Assumption I made that your generic repository return type of T and T is the entity.
Following is not exact interface but based on your question.
public class IRepository<T>
{
T Get(int Id);
IEnumrable<T> GetAll();
}
So most of the scenario above will suffice the thing.
Now let's assume that you have requirement that will only return specific attribute of T or partial of T.
Generic repository implementation never say that you don't need any extra or extended repository.
So you can do like this.
public interface ICarRepository : IRepository<Car>
{
IList<int> GetIds();
IList<string> GetAllModels();
bool IsActiveInProduction(int carId);
}
Implement above just like you have implemented IRepository for EF core. Inject this in your services and Use ICarRepository instead of IRepository
for your development.
Note: This is personal preference but for large project GenericRepository become issue sometime or may not completely fit but Repository is useful pattern and it overall depends on how you use it.
Update 1
In Car Repository if you want to return some other DTO but make sure it related to Car Entity otherwise it is not reponsibility of Repo layer.
public interface ICarRepository : IRepository<Car>
{
IList<int> GetIds();
IList<string> GetAllModels();
bool IsActiveInProduction(int carId);
MyDto GetMyDto(int carId); // MyDto is just for explanation.
}
Below is the BaseRespository.cs code that is use that I use as Generic class for entity framework. I inherit this class as parent with my other RepositoryClass.
public class BaseRepository
{
#region Ctor(s)
protected RuhBotEntities RuhBotEntities;
public BaseRepository(RuhBotEntities entities)
{
this.RuhBotEntities = entities;
}
#endregion
#region Protected & Private Members
protected async Task<T> Get<T>(Guid id) where T : class
{
return await this.RuhBotEntities.Set<T>().FindAsync(id);
}
protected IQueryable<T> GetAll<T>(out int totalCount ,bool isDeleted, string searchField = null, string searchTerm = null,
int? pageIndex = null, int? pageSize = null, string sortBy = null, SortOrder? sortOrder = null) where T : class
{
IQueryable<T> entities = this.RuhBotEntities.Set<T>();
var sortByProperty = typeof(T).GetProperty(sortBy);
if (!string.IsNullOrWhiteSpace(sortBy) && sortOrder.HasValue && sortByProperty != null)
{
switch (sortByProperty.PropertyType.FullName)
{
case "System.Int32":
entities = this.RuhBotEntities.Set<T>().Sort<T, int>(sortBy, sortOrder);
break;
case "System.String":
entities = this.RuhBotEntities.Set<T>().Sort<T, string>(sortBy, sortOrder);
break;
}
}
PropertyInfo[] entityProperties = typeof(T).GetProperties();
var propertyList = entityProperties.Where(p => p.PropertyType.FullName == "System.String").Select(p => p.Name).ToList();
if (!string.IsNullOrWhiteSpace(searchTerm) && !string.IsNullOrWhiteSpace(searchField))
{
entities = entities.Where(string.Format("{0}.Contains(#0)", searchField), searchTerm);
}
else if (!string.IsNullOrWhiteSpace(searchTerm) && propertyList.Count > 0)
{
var searchFieldList = new List<string>();
propertyList.ForEach(p => searchFieldList.Add(string.Format("{0}.Contains(#0)", p)));
var propertySearchField = String.Join("||", searchFieldList);
entities = entities.Where(propertySearchField, searchTerm);
}
if (isDeleted)
{
entities = entities.Where(string.Format("{0}.Equals(#0)", "IsDeleted"), false);
}
totalCount = entities.Count();
if (pageIndex.HasValue && pageSize.HasValue)
{
entities = entities.OrderBy(sortBy).Skip(pageIndex.Value * pageSize.Value).Take(pageSize.Value);
}
return entities;
}
protected void Add<T>(T entity) where T : class
{
this.RuhBotEntities.Set<T>().Add(entity);
}
public async Task<T> AddIfNotExists<T>(T entity, Expression<Func<T, bool>> predicate = null) where T : class, new()
{
var dbSet = this.RuhBotEntities.Set<T>();
var exists = predicate != null ? dbSet.Any(predicate) : dbSet.Any();
return exists ? await dbSet.FirstOrDefaultAsync(predicate) : dbSet.Add(entity);
}
protected void Update<T>(T entity, params string[] fieldsExcludedForUpdation) where T : class
{
var dbEntityEntry = this.AttachEntity(entity);
var createdOn = dbEntityEntry.Entity.GetType().GetProperty("CreatedOn");
var createdBy = dbEntityEntry.Entity.GetType().GetProperty("CreatedBy");
if (createdOn != null)
{
dbEntityEntry.Property("CreatedOn").IsModified = false;
}
if (createdBy != null)
{
dbEntityEntry.Property("CreatedBy").IsModified = false;
}
if (fieldsExcludedForUpdation != null && fieldsExcludedForUpdation.Length > 0)
{
foreach (var field in fieldsExcludedForUpdation)
{
dbEntityEntry.Property(field).IsModified = false;
}
}
dbEntityEntry.State = EntityState.Modified;
}
protected void Delete<T>(Guid id) where T : class
{
var entity = this.RuhBotEntities.Set<T>().Find(id);
this.RuhBotEntities.Set<T>().Remove(entity);
}
protected async Task SaveChanges()
{
await this.RuhBotEntities.SaveChangesAsync();
}
private DbEntityEntry AttachEntity(object entity)
{
var entry = this.RuhBotEntities.Entry(entity);
if (entry.State == EntityState.Detached)
{
var set = this.RuhBotEntities.Set(entity.GetType());
object attachedEntity = set.Find(entity.GetType().GetProperty("Id").GetValue(entity));
if (attachedEntity != null)
{
var attachedEntry = this.RuhBotEntities.Entry(attachedEntity);
attachedEntry.CurrentValues.SetValues(entity);
return attachedEntry;
}
else
{
entry.State = EntityState.Modified;
}
}
else
{
this.RuhBotEntities.Set(entity.GetType()).Attach(entity);
this.RuhBotEntities.Entry(entity).State = EntityState.Modified;
}
return entry;
}
#endregion
}
I am using Repository pattern to develop my project using Nhibernate. How to do complex join in Nhibernate. I am using below repository method for fetching data
public virtual TEntity FindBy(Expression<Func<TEntity, bool>> query)
{
try
{
return NHUnitOfWork.Session.Query<TEntity>().Where(query).FirstOrDefault();
}
catch (Exception ex)
{
throw;
}
}
Here I can fetch data without any joins. How to fetch data with joins?
for eg: Menu details are storing in Menu table and Menu Rights are storing to Menu Rights table. How to create a repository for that?
With QueryOver, accepting input Junction instead of Expression to your FindBy method will do.
Following code may be helpful. Remove columnList and top parameters if not needed to you. Please note that my ISession usage is different. You need to replace nhSession with NHUnitOfWork.Session. Another difference is that you are using Query and I am using QueryOver here.
public virtual IList<T> FindBy<T>(ProjectionList columnList, Junction where, int top) where T : BaseEntity
{
IList<T> instance = GetQueryOver<T>(columnList, where).Take(top).List();
return instance;
}
public virtual IQueryOver<T> GetQueryOver<T>(ProjectionList columnList, Junction where) where T : BaseEntity
{
IQueryOver<T> query = null;
if((columnList != null) && (where != null))
{
query = nhSession.QueryOver<T>()
.Select(columnList)
.TransformUsing(Transformers.AliasToBean<T>())
.Where(where);
}
else if((columnList != null) && (where == null))
{
query = nhSession.QueryOver<T>()
.Select(columnList)
.TransformUsing(Transformers.AliasToBean<T>());
}
else if((columnList == null) && (where != null))
{
query = nhSession.QueryOver<T>()
.Where(where);
}
else
{
query = nhSession.QueryOver<T>();
}
return query;
}
And then, you can call this at some other location like:
Junction where = Restrictions.Conjunction();
where.Add(Restrictions.Eq(Projections.Property<MyEntity>(x => x.Field), findValue));
where.Add(Restrictions.................);
where.Add(Restrictions.................);
entityList = Repository.FindBy<MyEntity>(null, where, 100);
If you want to eager load related entities, use Fetch and FetchMany.
public virtual Menu FindBy(Expression<Func<Menu, bool>> query)
{
return NHUnitOfWork.Session.Query<Menu>().Where(query)
.FetchMany(m => m.Rights)
// Required with FetchMany and First, otherwise only one right would be loaded.
.ToList()
.FirstOrDefault();
}
Or if your model has only one right per menu:
public virtual Menu FindBy(Expression<Func<Menu, bool>> query)
{
return NHUnitOfWork.Session.Query<Menu>().Where(query)
.Fetch(m => m.Right)
.FirstOrDefault();
}
But it seems you want to define some "generic repository" encapsulating the NHibernate API.
Maybe then:
public virtual TEntity FindBy<TFetched>(Expression<Func<TEntity, bool>> query,
Expression<Func<TEntity, IEnumerable<TFetched>>> fetchMany)
{
var query = NHUnitOfWork.Session.Query<TEntity>().Where(query);
if (fetchMany != null)
query = query.FetchMany(fetchMany);
return query
// Required with FetchMany and First, otherwise only one right would be loaded.
.ToList()
.FirstOrDefault();
}
Or if your model has only one right per menu:
public virtual TEntity FindBy<TFetched>(Expression<Func<TEntity, bool>> query,
Expression<Func<TEntity, TFetched>> fetch)
{
var query = NHUnitOfWork.Session.Query<TEntity>().Where(query);
if (fetch != null)
query = query.Fetch(fetch);
return query
.FirstOrDefault();
}
But how to do if you need many fetches*? And sub-fetches (ThenFetch/ThenFetchMany)? It looks to me like a cursed path. You may end up coding encapsulation for the whole NHibernate API following that path.
Indeed, exposing publicly an Expression where argument on repositories does not look good to me. Your repository is then no more responsible of defining how the data is queried.
If you want to do that, why not exposing directly the IQueryable? It would prove far less code verbose than trying to encapsulate it in a "repository" while still defining the query outside of this "repository".
*: beware of Cartesian products in case you do more than one FetchMany. To avoid it, either split your query in many (using ToFuture if you wish a single round-trip to DB), or use lazy loading with batching instead of fetching.
I'm trying to build an Entity framework abstract repository with data transfer objects and separate data providers. We need to be able to switch between oracle, sql server and azure sql depending on the install.
The end repository returns DTO's to the consuming code. Get, Update and delete are working fine. The issue I'm having is with the lambdas for where clauses etc. The generic repository is unaware of the actual Entity object from the data provider so I can't create a where lambda.
P.S. Using AutoMapper to convert between Entity and DTO
//Repository base
public class Repository<TEntity> : IDisposable, IRepository<TEntity> where TEntity : class, IEntity
{
public virtual IList<TEntity> GetList(Func<TEntity, bool> where, params Expression<Func<TEntity, object>>[] navigationProperties)
{
List<TEntity> list;
IQueryable<TEntity> dbQuery = Context.Set<TEntity>();
//Apply eager loading
foreach (Expression<Func<TEntity, object>> navigationProperty in navigationProperties)
dbQuery = dbQuery.Include<TEntity, object>(navigationProperty);
list = dbQuery
.AsNoTracking()
.Where(where)
.ToList<TEntity>();
return list;
}
}
//Implementing repository where T is the entity from the EF model
public class UserRepository<T> : IUserRepository, IUnitOfWork
where T : class, IEntity
{
public Repository<T> Base { get; private set; }
public UserRepository(DbContext context)
{
Base = new Repository<T>(context);
}
public List<UserAccountDTO> GetList(Expression<Func<UserAccountDTO, bool>> where)
{
T obj = SupportedRepos.DTOMapper.Map<T>(where.Parameters[0]);
/* HOW CAN I CONVERT THE FUNC<>? */
//Base.GetList();
return null;
}
}
public void TestMethod1()
{
var dbtypeFromConfig = RepoEF.Setup.StorageType.SQLServer;
using (var repo = RepoEF.Setup.SupportedRepos.Create(dbtypeFromConfig))
{
//WORKS FINE, CHANGES AND UPDATES
var source = repo.Get(3);
source.LookupId = 111111;
repo.Add(source);
//CAN'T CREATE WHERE BECAUSE UserRepository<T> ONLY SEES IEntity
repo.GetList(x => x.UserAccountId == 3);
}
}
Is it possible to build a Func<> to pass to the base Repository.
If not any ideas how I change the design to achieve what I need?
Perhaps I could dig into your current implementation of repository pattern and try to fix it, but I believe that, after all, there're many design flaws that will force you to completely refactor your solution.
So I'll give you some hints:
1) A repository is still domain, thus it always work with domain objects
Domain shouldn't work with DTOs. You use DTO pattern when you want to transfer data between logical or physical boundaries. For example, when a domain service need to provide a list of some domain objects to an application service. That list will be exposed as DTOs of the domain object list.
2) Database technology switch is a responsibility of data mapping layer (i.e. Entity Framework).
Your repositories should remain agnostic to the database technology and coupled with the OR/M framework.
3) If an user repository IS a repository, you should use inheritance instead of composition
Thus, UserRepository should both implement IUserRepository and derive from Repository<User>.
4) A concrete repository shouldn't accept a generic type parameter to provide the domain object to handle
At the end of the day, a concrete repository like IUserRepository handles instances of User, thus, why you want a TEntity generic parameter? Since you've this design flaw, TEntity isn't T... and this is the reason behind the problem you want to solve...
I am writing a generic repository to interface with EF using DBContext.
I have a generic Get() method which receives a primary key value and returns the entity:
public class DALRepository<DALEntity> : IDisposable, IGenericRepository<DALEntity> where DALEntity : class
{
private IDbSet<DALEntity> dbSet;
private NWEntities context;
public DALRepository()
{
context = new NWEntities();
context.Configuration.LazyLoadingEnabled = false;
dbSet = context.Set<DALEntity>();
}
Here's a simple get method - just works on the PK - exactly what I want.
public DALEntity Get(string ID)
{
return dbSet.Find(ID);
}
I now want to change this to allow the consumer to pass in a list of includes - so as well as returning just a customer they can request to return the orders as well. Here's where I'm running into trouble. If I do this:
public DALEntity Get(string ID, IEnumerable<string> IncludeEntities = null)
{
IQueryable<DALEntity> query = dbSet;
query = IncludeEntities.Aggregate(query, (current, includePath) => current.Include(includePath));
}
I can't use find with the IQueryable. And I can't Find() directly because I can't pass includes to it. If I use a where lambda instead on the IQueryable, how do I tell it to use the PK of the entity? I guess I could have a generic constraint that insists the generic type must implement some IPkey interface with a well-defined primary column name such as "ID", but then I can't use the entities generated by DBContext as they woudl have to implement this interface. I can change the T4 to do this if I need - and I've already changed it to emit XML comments so not too averse to that - but does anyone have a simpler way? I suppose what I need is an overloaded find() which accepts a list of includes.
So my question is either how to use Find with includes, or how to write the lambda where it knows the PK? I can't receive such a lambda as a parameter as this will ultimately be consumed by a WCF service.
Kind of wierd answering your own question but in case anyone else has this issue here's what I did. I used the dynamic LINQ stuff and used the string overloaded version of .Where(). I used one of the links mentioned to figure out how to grab the primary key (and mine is from a DBContext as well), and the method now looks like this:
public DALEntity Get(string ID, IEnumerable<string> IncludeEntities = null)
{
var set = ((IObjectContextAdapter)context).ObjectContext.CreateObjectSet<DALEntity>();
var entitySet = set.EntitySet;
string[] keyNames = entitySet.ElementType.KeyMembers.Select(k => k.Name).ToArray();
Debug.Assert(keyNames.Length == 1, "DAL does not work with composite primary keys or tables without primary keys");
IQueryable<DALEntity> query = dbSet;
query = IncludeEntities.Aggregate(query, (current, includePath) => current.Include(includePath));
query = query.Where(keyNames[0] + "= #0", ID);
return query.FirstOrDefault();
}
You could get your Key dinamycally the way it is described here : https://stackoverflow.com/a/10796471/971693
That being said, I can't see a way without using some reflection :
public IEnumerable<DALEntity> Get(params string IDs)
{
var objectSet = objectContext.CreateObjectSet<YourEntityType>();
var keyNames = objectSet.EntitySet.ElementType.KeyMembers.First(k => k.Name);
return dbSet.Where(m => ID.Contains((string)m.GetType().GetProperty(keyNames ).GetValue(m, null));
}
First of, params string IDs will let you pass 1 or more ID and will result in an array of string.
The first part of the function is to dynamically get the name of your primary key.
The second part creates a query to return all elements from your set where the primary key value (obtained through reflection) is contained within the array of IDs received in parameter.
Use Linq's Single or First methods, which allow you to search on IQueryable objects.
public DALEntity Get(string ID, IEnumerable<string> IncludeEntities = null)
{
IQueryable<DALEntity> query = dbSet;
query = IncludeEntities.Aggregate(query, (current, includePath) => current.Include(includePath));
query = query.Single(x=>x.Id == ID);
}
Currently I'm building an windows application using sqlite. In the data base there is a table say User, and in my code there is a Repository<User> and a UserManager. I think it's a very common design. In the repository there is a List method:
//Repository<User> class
public List<User> List(where, orderby, topN parameters and etc)
{
//query and return
}
This brings a problem, if I want to do something complex in UserManager.cs:
//UserManager.cs
public List<User> ListUsersWithBankAccounts()
{
var userRep = new UserRepository();
var bankRep = new BankAccountRepository();
var result = //do something complex, say "I want the users live in NY
//and have at least two bank accounts in the system
}
You can see, returning List<User> brings performance issue, becuase the query is executed earlier than expected. Now I need to change it to something like a IQueryable<T>:
//Repository<User> class
public TableQuery<User> List(where, orderby, topN parameters and etc)
{
//query and return
}
TableQuery<T> is part of the sqlite driver, which is almost equals to IQueryable<T> in EF, which provides a query and won't execute it immediately. But now the problem is: in UserManager.cs, it doesn't know what is a TableQuery<T>, I need to add new reference and import namespaces like using SQLite.Query in the business layer project. It really brings bad code feeling. Why should my business layer know the details of the database? why should the business layer know what's SQLite? What's the correct design then?
I'd recommend you to use IEnumerable<T> rather than IQueryable<T>, which allows lazy loading too. IEnumerable does not, however, imply you can query the data in any way. Your DB LINQ Provider will probably have a reduced feature set.
Typically, in a clean architecture, data query logic is encapsulated in repositories. Use of Pipes and Filters can help reuse query logic. Wrapping these with methods in data-layer/repositories will be more readable and maintainable plus re-usable.
For instance, Pipes and filters to query users:
/// Pipes/Filters for user queries.
public static class UserExtensions
{
public static IQueryable<User> Active(this IQueryable<User> query)
{
return query.Where(user => user.Active == true);
}
}
public class UserRepository : IRepository<User>, IUserRepository
{
/// Retrieve all users
public List<User> List()
{
// Logic to query all users in the database.
}
public List<User> ListActive()
{
// Logic to query all active users in the database.
return context.Users.Active().ToList();
}
}
Complex queries requires the understanding of it's purpose and responsibilities to abstract the query logic to it's repositories. For instance, 'Get all accounts belongs to this user" can be written in AccountRepository class as List<Account> ListForUser(int userId) { }.
Edit: Based on the comments, here is the scenarios to write a search query that retrieves Users lives in LA who have at least 2 accounts.
public class UserRepository : IRepository<User>, IUserRepository
{
// other queries.
public List<User> List(ISearchQuery query)
{
// Logic to query all active users in the database.
return context.Users.Active().LivesIn(query.Country).WithAccounts(query.AccountsAtLeast).ToList();
}
}
public static class UserExtensions
{
// other pipes and filters.
public static IQueryable<User> LivesIn(this IQueryable<User> query, string country)
{
return query.Where(user => user.Country.Name == country);
}
public static IQueryable<User> WithAccounts(this IQueryable<User> query, int count)
{
return query.Where(user => user.Accounts.Count() >= count);
}
}
Since TableQuery<T> implements IEnumerable<T>, and not IQueryable<T>, the best solution would be to simply change your repository interface to return IEnumerable<T> instead of TableQuery<T>. This not only breaks the explicit client dependency with your SqlLite library, but it is also a better design to use an abstraction (IEnumerable<T>) instead of an implementation (TableQuery<T>) in your interfaces.
Your example method should look like this:
//Repository<User> class
public IEnumerable<User> List(where, orderby, topN parameters and etc)
{
//query and return
}
Lazy loading can be a PITA and I'd rather try to inject a loading strategy into the repository instead of trying to fiddle with the query object in my application or business layer. Especially when the query object forces me to tightly couple the layers.