I have two Entity Framework 5 Get() methods that perform (i) a single entity get by ID, and (ii) a single entity get via a filter with any eager loading bolted on. See below for the code:
internal readonly FallenNovaContext Context;
private readonly DbSet<TEntity> _dbSet;
internal GenericRepository(FallenNovaContext context)
{
Context = context;
_dbSet = context.Set<TEntity>();
}
// (i) Get by ID.
public TEntity GetById(int id)
{
return _dbSet.Find(id);
}
// (ii) Get by filter and optional eager loading includes.
public TEntity Get(
Expression<Func<TEntity, bool>> filter = null,
IEnumerable<string> includePaths = null)
{
IQueryable<TEntity> query = _dbSet;
if (filter != null)
{
query = query.Where(filter);
}
if (includePaths != null)
{
query = includePaths.Aggregate(query, (current, includePath) => current.Include(includePath));
}
return query.SingleOrDefault();
}
All of which works fine now what I'm finding as my application grows is I'm writing a lot of non-generic methods that need a mix of both - more specifically I want a generic get by ID and also be able to eager load related entities.
So the method signature would look something like this:
public TEntity GetById(
int id,
IEnumerable<string> includePaths)
{
// ???
}
Which I could call like this:
User user = UnitOfWork.UserRepository.GetById(117, new List<string>() { "UserRole", "UserStatus" });
Or like this:
Car car = UnitOfWork.CarRepository.GetById(51, new List<string>() { "Make", "Model", "Tyres" });
Any help on the suggestions of how I use Entity Framework 5 to code the logic for the TEntity GetById(int id, IEnumerable includePaths) method would be appreciated.
First, write a base class for entities, which defines the primary key field. Something like the following may work:
public abstract class BaseEntity
{
public int Id {get;set;}
}
Then, write a base class for your repositories; define all generic methods in this base repository. Let this repository have a generic parameter of entity type:
public class RepositoryBase<TEntity> where TEntity : BaseEntity
{
public TEntity GetById(
int id,
params Expression<Func<TEntity, object>>[] includeList)
{
TEntity entity = null;
ObjectQuery<TEntity> itemWithIncludes = context.Set<TEntity>() as ObjectQuery<TEntity>;
foreach (Expression<Func<TEntity, object>> path in includeList)
{
itemWithIncludes = ((IQueryable)itemWithIncludes.Include(path)) as ObjectQuery<T>;
}
IQueryable<TEntity> items = itemWithIncludes.AsQueryable<TEntity>();
entity = items.Where(p => p.Id == id).SingleOrDefault();
return entity;
}
}
Update: #Bern asked whether there is any other way to find primary key than declaring a base class. The following questions refer to this problem.
Entity Framework 4: How to find the primary key?
Entity Framework code first. Find primary key
On the otherhand I do not know if there is any other way in EF 5.
Related
I have an EF core that I am trying to "Include" some relations by key, and found an answer I can do it like this:
//Get entity by key first, then load relations for it like this:
await context.Entry(entity).Collection(expressions).LoadAsync();
This collection method takes:
Expression<Func<TEntity, IEnumerable<TProperty>>> expressions
All examples I have seen, is a way to pass a single property to it.
Here is the full code of a wrapper that I want:
public static async Task<TEntity> FindInContextAsync<TEntity, TProperty>(object keyValue, Expression<Func<TEntity, IEnumerable<TProperty>>> expressions)
where TEntity : class where TProperty : class
{
using var scope = _scopeFactory.CreateScope();
var context = scope.ServiceProvider.GetService<AccountsDbContext>();
var dbSet = context.Set<TEntity>();
var Entit = await dbSet.FindAsync(keyValue);
if (Entit == null)
{
throw new NotFoundException(nameof(TEntity), keyValue);
}
await context.Entry(Entit).Collection(expressions).LoadAsync();
return Entit;
}
I would like to call it as cleanly and as little code as possible, something like this:
await FindInAccountsContextAsync<MyEntity, dynamic>(id, x => x.RelationClass1, x.RelationClass2...);
Seems like above would be more of a params case:
params Expression<Func<TEntity, TProperty>[] expressions
But then this would not get accepted by Collection()
In the end, I want to be able to retrieve any object from DbContext by key, with eager loading specified
Any solution is welcomed, as I can't get anything to work!
DbEntityEntry.Collection takes:
Expression<Func<TEntity, ICollection<TElement>>> navigationProperty
This is to select a single property of type ICollection<TElement>, not multiple properties.
Because ICollection is invariant, and TElement is likely going to be different for each navigation property, you aren't going to be able to pass a collection of these to FindInContextAsync.
The simplest workaround would be to remove the delegate and load the navigations after calling FindInContextAsync, but if you must pass delegates you could do this:
public static async Task<TEntity> FindInContextAsync<TEntity>(
object keyValue,
params Func<TEntity, Task>[] afterFind)
where TEntity : class
{
// ...
var entity = await dbSet.FindAsync(keyValue);
// ...
await Task.WhenAll(afterFind.Select(func => func(entity)));
return entity;
}
And call like this:
var entity = await FindInAccountsContextAsync(
id,
(MyEntity x) => context.Entry(x).Collection(y => y.RelationClass1).LoadAsync(),
(MyEntity x) => context.Entry(x).Collection(y => y.RelationClass2).LoadAsync());
The above could be simplified slightly by adding a helper function:
Task LoadCollectionAsync<TEntity, TProperty>(
TEntity entity,
Expression<Func<TEntity, ICollection<TElement>>> navigationProperty)
{
return context.Entry(entity).Collection(navigationProperty).LoadAsync();
}
Which would allow you to do this:
var entity = await FindInAccountsContextAsync(
id,
(MyEntity x) => LoadCollectionAsync(x, y => y.RelationClass1),
(MyEntity x) => LoadCollectionAsync(x, y => y.RelationClass2));
My mistake for thinking I need to use Collection(). I was able to achieve below using Reference() instead, which takes Expression<Func<TEntity, TProperty>> instead of IEnumerable which is what was causing me issues. Thanks to #NetMage, end result:
var entity = await FindInAccountsContextAsync<AccountCreditDebit>(
id, x => x.CreditDebitType, x => x.CreditDebitSource;
This allows me to pass any DbSet in my Account DB Context (in this case AccountCreditDebit DbSet) and return the entity from DbSet with relations specified (CreditDebitType, CreditDebitSource).
Full Code:
public static async Task<TEntity> FindInAccountsContextAsync<TEntity>(object keyValue, params Expression<Func<TEntity, object>>[] expressions)
where TEntity : class where TProperty : class
{
using var scope = _scopeFactory.CreateScope();
var context = scope.ServiceProvider.GetService<AccountsDbContext>();
var dbSet = context.Set<TEntity>();
var entity = await dbSet.FindAsync(keyValue);
if (entity == null)
{
throw new NotFoundException(nameof(TEntity), keyValue);
}
foreach (var expression in expressions)
{
await context.Entry(entity).Reference(expression).LoadAsync();
}
return entity;
}
The limitation I can think of, is I cannot pass a function if ICollection in the mix with single property such as:
var entity = await FindInAccountsContextAsync<AccountCreditDebit>(
id,
x => x.CreditDebitType, /*<= Single property */
x => x.ManyCreditDebitSources; //<= A Collection
Old Comment answered in comments:
The only thing I would like to improve, is to remove this dynamic if possible
await FindInAccountsContextAsync<AccountCreditDebit, dynamic>(...)
I have a database first Entity Framework setup. Usually, I create procedures like this, to return a list of a defined object in my model.
public List<Employees> GetEmployees()
{
var tables = _crmContext.Employees.ToList();
return tables;
}
That works fine but the case I can't figure out, it different. I want to return a list of an object in the entity framework model, based on a parameter passed to the procedure.
I was trying something like this, but it's not working and I also don't know if is the right approach.
public List<T> GetValuesFromTable(string table)
{
var results = _crmContext."table".ToList();
return results;
}
Thanks for the help.
I think the Generic Repository Pattern is what you're looking for. In this way, you can call all the tables you have given type with a single method. Example
Repository.cs
public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
protected readonly DbContext Context;
public Repository(DbContext context)
{
this.Context = context;
}
public IEnumerable<TEntity> GetAll()
{
return Context.Set<TEntity>().ToList();
}
}
IRepository.cs
public interface IRepository<TEntity> where TEntity : class
{
IEnumerable<TEntity>GetAll();
}
I am trying to delete an entity of Employee from the database which contains different tables like Employee, Project, Skills using a generic repository pattern.
namespace Information.Repository
{
public class IRepositoy<TEntity> : IRepository<TEntity> where TEntity : class
{
private readonly ApplicationDbContext _dbContext;
public IRepositoy(ApplicationDbContext dbContext)
{
_dbContext = dbContext;
}
public void Remove(int id)
{
TEntity element = _dbContext.Set<TEntity>().Find(id);
_dbContext.Set<TEntity>().Remove(element);
}
}
}
When the above Remove method is called it makes two database call
One for getting the entity.
Second for deleting it.
I have found the query like the below one which executes with single SQL query
when the entity type(Employee or Project or Skill) is known
public void Remove(int id)
{
Employee employee = new Employee { EmployeeId = id };
_dbContext.Entry(employee).State = EntityState.Deleted;
}
can anyone please suggest me how to delete an entity without fetching it using a generic repository pattern similar to the above example.
Using raw SQL
Entity Framework doesn't allow you to delete an object that you haven't loaded (or attached). This also extends to conditional deletes (e.g. deleting all users named John) as it requires you to load the users before deleting them.
You can get around this by executing raw SQL. It's not ideal as you tend to use EF so you don't have to write SQL, but the lack of a decent delete behavior (without loading) makes this an acceptable solution.
Something along the lines of:
using (var context = new FooContext())
{
var command = "DELETE * FROM dbo.Foos WHERE Id = 1";
context
.Database
.ExecuteSqlCommand(command);
}
Where relevant, don't forget about SQL injection protection. However, it's usually a non-issue for simple deletes as the FK is usually a GUID or int, which doesn't expose you to injection attacks.
Making it generic
The example you posted works as well, but you're probably not using it because it can't easily be made generic-friendly.
What I tend to do in all my EF projects is to have an (abstract) base class for all my entities, something along the lines of:
public class BaseEntity
{
public int Id { get; set; }
public DateTime CreatedOn { get; set; }
public string CreatedBy { get; set; }
public DateTime? UpdatedOn { get; set; }
public string UpdatedBy { get; set; }
}
An interface would also work, I just prefer a base class here.
The audit fields are not part of this answer but they do showcase the benefits of having a base class.
When all your entities inherit from the same base class, you can put a generic type constraint on your repositories which ensures that the generic type has an Id property:
public class IRepositoy<TEntity> : IRepository<TEntity> where TEntity : BaseEntity
At which point you can generically implement your proposed solution:
public void Remove(TEntity obj)
{
dbContext.Entry(obj).State = EntityState.Deleted;
}
You can also specify a parameterless constructor type constraint:
where TEntity : BaseEntity, new()
which enables you to instantiate your generic type as well:
public void Remove(int id)
{
TEntity obj = new TEntity() { Id = id };
dbContext.Entry(obj).State = EntityState.Deleted;
}
Note
There is a generic raw SQL solution as well, but I've omitted it as it is more complex because it requires you to retrieve the table name based on the entity type.
The raw SQL variant is only valuable in cases where you want to execute conditional deletes (e.g. removing all entities whose id is an even number).
However, since most conditional deletes are entity-specific, this means that you generally don't need to make them generic, which makes the raw SQL approach more viable as you only have to implement it in a specific repository and not the generic one.
You still have to fetch it. Entity Framework caches your dbSets so it's usually pretty quick. Use the same context like so:
public virtual void Delete(object id)
{
TEntity entityToDelete = dbSet.Find(id);
Delete(entityToDelete);
}
public virtual void Delete(TEntity entityToDelete)
{
if (context.Entry(entityToDelete).State == EntityState.Detached)
{
dbSet.Attach(entityToDelete);
}
dbSet.Remove(entityToDelete);
}
Where dbSet =
context.Set<TEntity>();
The current limitation of Entity Framework is, in order to update or delete an entity you have to first retrieve it into memory. However there are few alternatives to delete a specific record.
You can try ExecuteSqlCommandto delete a specific record
_dbContext.Database.ExecuteSqlCommand("Delete Employee where EmployeeId = {0}", id );
or try using EntityFramework.Extended Library to delete a specific record
_dbContext.Settings.Where(s=> s.EmployeeId == id).Delete();
I am working in project using generic repository design pattern and i need to get the name of entity(name of table) that sent to the following Add function
public class Repository<TEntity> : IRepository<TEntity> where TEntity:class
{
public TEntity Add(TEntity entity)
{
validate("nameoftable");
TEntity result= _set.Add(entity);
return result;
}
private validate(string entity-name)
{
if (entity-name == "students")
{
////do some work
}
}
i want to get the entity table to use it for some validation before save
Why can't you say like below if TEntity happens to be the entity or table name
validate(typeof(TEntity).FullName);
(OR)
validate(typeof(TEntity).Name);
Previously (when using .net 4.5.2 and EF 6). I have had a generic Get method that accepted a number of includes as follows;
public abstract class DataContext : IdentityDbContext<ApplicationUser, ApplicationRole, int>, IDataContext
{
public DataContext(DbContextOptions options)
: base(options)
{
}
// reduced for brevity
public T Get<T>(int id, params Expression<Func<T, object>>[] includes) where T : class, IEntity
{
return this.Set<T>().Include(includes).FirstOrDefault(x => x.Id == id);
}
I would then call for example;
context.Get<Job>(id,
x => x.Equipment,
x => x.Equipment.Select(y => y.Type));
To include the Job.Equipment and also the Job.Equipment.Type.
However, when I have ported this over to asp.net core 2. I have tried the same generic approach, but if I try to include a sub-entity I get the following error;
The property expression 'x => {from Equipment y in x.Equipment select [y].Type}' is not valid. The expression should represent a property access: 't => t.MyProperty'. For more information on including related data, see http://go.microsoft.com/fwlink/?LinkID=746393.
Can anyone suggest how I can work around this to include sub entities in my Generic Get<T> method with Entity Framework Core 2?
Update
From looking at the documents there is an additional include method
include(string navigationPropertyPath)
I added the following method;
public T Get<T>(int id, string[] includes) where T : class, IEntity
{
var result = this.Set<T>().AsQueryable();
foreach(var include in includes)
{
result = result.Include(include);
}
return result.FirstOrDefault(x => x.Id == id);
}
Which does work, although I am not convinced on the efficiency here?
EF Core Include / ThenInclude pattern cannot be represented by Expression<Func<T, object>>[] like in EF6.
Looking at the source code of one of the EF Core extensions - Microsoft.EntityFrameworkCore.UnitOfWork, which claims to be
A plugin for Microsoft.EntityFrameworkCore to support repository, unit of work patterns, and multiple database with distributed transaction supported.
looks like the intended pattern for includes should be based on Func<IQueryable<T>, IIncludableQueryable<T, object>>:
public T Get<T>(int id, Func<IQueryable<T>, IIncludableQueryable<T, object>> include = null) where T : class, IEntity
{
var result = this.Set<T>().AsQueryable();
if (include != null)
result = include(result);
return result.FirstOrDefault(x => x.Id == id);
}
The drawback is that it adds EF Core dependency on the caller and requires using Microsoft.EntityFrameworkCore;. Which in your case is not essential since you are extending the DbContext.
The usage with your example would be:
context.Get<Job>(id, q => q
.Include(x => x.Equipment)
.ThenInclude(y => y.Type));
You can do something like this:
public abstract class DataContext : IdentityDbContext<ApplicationUser, ApplicationRole, int>, IDataContext
{
public DataContext(DbContextOptions options)
: base(options)
{
}
// reduced for brevity
public T Get<T>(int id, Func<IQueryable<T>, IIncludableQueryable<T, object>> includes = null) where T : class, IEntity
{
IQueryable<T> queryable = this.Set<T>();
if (includes != null)
{
queryable = includes(queryable);
}
return queryable.FirstOrDefault(x => x.Id == id);
}
}
context.Get<Job>(id, includes: source => source.Include(x => x.Equipment).ThenInclude(x => x.Type));