Entity Framework Lazy Loading with generic repository - entity-framework

My current project uses a generic repository interface, thus:
public interface IDataSource : IDisposable
{
void Add<T>(T newItem) where T : EntityBase;
T Get<T>(Guid id) where T : EntityBase;
T Get<T>(Expression<Func<T, bool>> predicate) where T : EntityBase;
IQueryable<T> GetAll<T>(Expression<Func<T, bool>> predicate = null) where T : EntityBase;
int Count<T>(Expression<Func<T, bool>> predicate = null) where T : EntityBase;
bool Any<T>(Expression<Func<T, bool>> predicate = null) where T : EntityBase;
void Update<T>(T updated) where T : EntityBase;
void Delete<T>(Guid id) where T : EntityBase;
void Delete<T>(T item) where T : EntityBase;
void Commit();
}
As an example, the Get method looks like this:
public T Get<T>(Expression<Func<T, bool>> predicate) where T : EntityBase
{
return db.Set<T>().Single(predicate);
}
where db is an instance my data context, which extends Entity Framework's DbContext. The whole thing implements IDisposable so that I can use it in a scope block for unit-of-work pattern, waiting to the end before committing changes, or disposing the entire thing if something goes wrong before that.
This interface is used by a logic layer to handle more complex queries, to keep business logic entirely separated from data access. So, a query to that layer might go like this:
public List<Product> ItemsBoughtByCustomer(Guid customerID)
{
using (var db = DataAccess.GetContext())
{
List<Purchase> purchaseHistory = db.GetAll<Purchase>(p => p.CustomerID == customerID);
List<int> IDs = purchaseHistory.Select(p => p.ProductID);
return db.GetAll<Product>(p => IDs.Contains(p.ID));
}
}
(Yes, I realise that can be condensed; it is in the app, but for an example, this is clearer.)
My problem is that sometimes I return a set of objects, and then later I might want to get to some of the things it references. For example, when I get a Product to display, the Display might want to do this:
#foreach (Comment comment in Product.Comments)
{
<div class="comment">
<span>#Html.UserDisplay(comment.Author)</span>
<span>#comment.Body</span>
</div>
}
(ignore the quality of the HTML; again, it's a quick example)
The problem is that this throws errors when Entity Framework's lazy loading leaves these properties null when returning entities from my queries. Now, I'm aware of the Include() method, but if my repository is generic then it's difficult to apply those. I could turn it off entirely, but then EF will start retrieving enormous linked collections of things when I don't need them - the structure of my model and the links that things have out to the audit logs mean a lot of links for EF to follow.
Is there a way that I can lazy-load in a slightly smarter manner? Is there a method like .Single() and .Where() that I can call on the DbSet that will bring child objects as well, so that I can specifically ask for child objects to be included for a certain query?

add an optional parameter for the include path then invoke Include(str) on the DbSet. Example with your Get method:
public T Get<T>(Expression<Func<T, bool>> predicate, string includePath = null) where T : EntityBase
{
var query = db.Set<T>();
if( !string.IsNullorWhitespace( includePath ) )
{
query = query.Include( includePath );
}
return query.Single(predicate);
}

Related

How to search records from a table with composite key in EF6?

I've created my database schema using Entity Framework's Code First, and one of the models has a composite key (which is reflected perfectly in the db).
However, when I try to look for records using a list of composite keys I get an exception that says:
"Unable to create a constant value of type 'Anonymous type'. Only primitive types or enumeration types are supported in this context."
This is the code I've used last that failed (variations of this had also produced the same error):
var ids = models.Select(m => new { m.Id, m.InstanceId })
.ToArray();
var records = _dataService.TableWithCompositeKey
.Where(t => ids.Contains(new { t.Id, t.InstanceId }))
.ToArray();
The exception is thrown when evaluating records. So how can I accomplish such a task ?
EDIT : Rename variable, and add comments on multiple records
I did assume you wanted single records on after the other due to the nature of this problem. Contains cant do what you want in EF.
Context.Set<poco>.Find() supports multiple field keys
see Ef Source public virtual TEntity Find(params object[] keyValues)
var record = _dataService.TableWithCompositeKey
.Find( Id, InstanceId )
.ToList();
if you can NOT get teh records with a regular where clause and remove the unwanted entries not in the key list then :
a) Get individually
foreach...
find(a,b)
b) Build the where clause dynamically
using something like Predicate builder see below, to build the where clause
with
foreach
Thischeck = ( a = X and b = Y ) // eg create Expression for (Tpoco t)=>value.Equals(t.PropertyName)
Whereclause.Or(thisCheck)
observation, If you have small number of entries to collect, then individual gets is of consideration.
If you have many records to get, then size of the Where clause generated is of consideration. That may end up with a full table scan of the DB.
You also need to be careful that the Linq you generate doesnt cause all records to be loaded into memory before the wanted records are found.
No nice solution to this issue.
/// See http://www.albahari.com/expressions for information and examples.
public static class PredicateBuilder
{
public static Expression<Func<T, bool>> True<T> () { return f => true; }
public static Expression<Func<T, bool>> False<T> () { return f => false; }
public static Expression<Func<T, bool>> Or<T> (this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast<Expression> ());
return Expression.Lambda<Func<T, bool>>
(Expression.OrElse (expr1.Body, invokedExpr), expr1.Parameters);
}
public static Expression<Func<T, bool>> And<T> (this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast<Expression> ());
return Expression.Lambda<Func<T, bool>>
(Expression.AndAlso (expr1.Body, invokedExpr), expr1.Parameters);
}
}

Using .Find() & .Include() on the same query

I have the following method automatically generated from the scaffold template with repository:-
public Group Find(int id)
{
return context.Groups.Find(id);
}
But since the Groups object has two navigation properties which I need , so I wanted to include the .Include, so I replace the .find with .where :-
public Group Find(int id)
{
return context.Groups.Where(c=>c.GroupID==id)
.Include(a => a.UserGroups)
.Include(a2 => a2.SecurityRoles)
.SingleOrDefault();
}
But my question is how can I apply the .Include with the .find() instead of using .Where()?
I was just thinking about what find actually does. #lazyberezovsky is right include and find cant be used in conjunction with each other. I think this is quite deliberate and here's why:
The Find method on DbSet uses the primary key value to attempt to find
an entity tracked by the context. If the entity is not found in the
context then a query will be sent to the database to find the entity
there. Null is returned if the entity is not found in the context or
in the database.
Find is different from using a query in two significant ways:
A round-trip to the database will only be made if the entity with the given key is not found in the context.
Find will return entities that are in the Added state. That is, Find will return entities that have been added to the context but have
not yet been saved to the database.
(from http://msdn.microsoft.com/en-us/data/jj573936.aspx)
Because find is an optimised method it can avoid needing a trip to the server. This is great if you have the entity already tracked, as EF can return it faster.
However if its not just this entity which we are after (eg we want to include some extra data) there is no way of knowing if this data has already been loaded from the server. While EF could probably make this optimisation in conjunction with a join it would be prone to errors as it is making assumptions about the database state.
I imagine that include and find not being able to be used together is a very deliberate decision to ensure data integrity and unnecessary complexity. It is far cleaner and simpler
when you are wanting to do a join to always go to the database to perform that join.
You can't. Find method defined on DbSet<T> type and it returns entity. You can't call Include on entity, so the only possible option is calling Find after Include. You need DbSet<T> type for that, but Include("UserGroups") will return DbQuery<T>, and Include(g => g.UserGroups) will also return DbQuery<T>:
public static IQueryable<T> Include<T>(this IQueryable<T> source, string path)
where T: class
{
RuntimeFailureMethods.Requires(source != null, null, "source != null");
DbQuery<T> query = source as DbQuery<T>;
if (query != null)
return query.Include(path); // your case
// ...
}
DbQuery<T> is not a child of DbSet<T> thus method Find is not available. Also keep in mind, that Find first looks for entity in local objects. How would it include some referenced entities, if they don't loaded yet?
You can try to do this:
public static class DbContextExtention
{
public static TEntity FirstOfDefaultIdEquals<TEntity, TKey>(
this IQueryable<TEntity> source, TKey otherKeyValue)
where TEntity : class
{
var parameter = Expression.Parameter(typeof(TEntity), "x");
var property = Expression.Property(parameter, "ID");
var equal = Expression.Equal(property, Expression.Constant(otherKeyValue));
var lambda = Expression.Lambda<Func<TEntity, bool>>(equal, parameter);
return source.FirstOrDefault(lambda);
}
public static TEntity FirstOfDefaultIdEquals<TEntity>(
this ObservableCollection<TEntity> source, TEntity enity)
where TEntity : class
{
var value = (int)enity.GetType().GetProperty("ID").GetValue(enity, null);
var parameter = Expression.Parameter(typeof(TEntity), "x");
var property = Expression.Property(parameter, "ID");
var equal = Expression.Equal(property, Expression.Constant(value));
var lambda = Expression.Lambda<Func<TEntity, bool>>(equal, parameter);
var queryableList = new List<TEntity>(source).AsQueryable();
return queryableList.FirstOrDefault(lambda);
}
}
GetById:
public virtual TEntity GetByIdInclude(TId id, params Expression<Func<TEntity, object>>[] includes)
{
var entry = Include(includes).FirstOfDefaultIdEquals(id);
return entry;
}
Method include EntityFramework Core (look here(EF6 and EF Core)):
protected IQueryable<TEntity> Include(params Expression<Func<TEntity, object>>[] includes)
{
IIncludableQueryable<TEntity, object> query = null;
if (includes.Length > 0)
{
query = DbSet.Include(includes[0]);
}
for (int queryIndex = 1; queryIndex < includes.Length; ++queryIndex)
{
query = query.Include(includes[queryIndex]);
}
return query == null ? DbSet : (IQueryable<TEntity>)query;
}

Entity Framework: Generic compare type without Id?

Is there anyway to do a comparison between objects for equality generically without objects having an ID?
I am trying to do a typical generic update, for which I have seen many examples of online, but they all usually look something like this:
public void Update(TClass entity)
{
TClass oldEntity = _context.Set<TClass>().Find(entity.Id);
foreach (var prop in typeof(TClass).GetProperties())
{
prop.SetValue(oldEntity, prop.GetValue(entity, null), null);
}
}
or something similar.The problem with my system is that not every class has a property named Id, depending on the class the Id can be ClassnameId. So is there anyway for me to check for the existence of and return such an entity via LINQ without supplying any properties generically?
Try
public void Update(TClass entity)
{
var oldEntry = _context.Entry<TClass>(oldEntity);
if (oldEntry.State == EntityState.Detached)
{
_context.Set<TClass>().Attach(oldEntity);
}
oldEntry.CurrentValues.SetValues(entity);
oldEntry.State = EntityState.Modified;
}

Update method for generic Entity framework repository

I have a repository like that:
public class Repository<T> : IRepository<T> where T : class
{
private readonly IRepositoryContext _repositoryContext;
public Repository(IRepositoryContext repositoryContext)
{
_repositoryContext = repositoryContext;
_objectSet = repositoryContext.GetObjectSet<T>();
}
public virtual void Update(T entity)
{
ObjectSet.AddObject(entity);
_repositoryContext.ObjectContext.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
_repositoryContext.SaveChanges();
}
}
Now that actually works for all scalar properties of the entity, but all the other entities that associated with properties of entity typeOf(T), don't care that entity state is modified, and EF simply adds new data.
So, if you do for example Repository<Student>.Update(), and you only changed the name, it will find the right Student and change his name, but it also will change the Campus, although you already have a Campus associated with that student, it will be created again with a different CampusId.
Show me please the correct way to do updates in this situation.
What I did when I wanted to follow generic approach was translated to your code something like:
public class Repository<T> : IRepository<T> where T : class
{
...
public virtual void Update(T entity)
{
if (context.ObjectStateManager.GetObjectStateEntry(entity).State == EntityState.Detached)
{
throw new InvalidOperationException(...);
}
_repositoryContext.SaveChanges();
}
}
All my code then worked like:
var attachedEntity = repository.Find(someId);
// Merge all changes into attached entity here
repository.Update(attachedEntity);
=> Doing this in generic way moves a lot of logic into your upper layer. There is no better way how to save big detached object graphs (especially when many-to-many relations are involved and deleting of relations is involved).

EF foreign key constraints and repository pattern

I have this simple Delete Get and Post methods in a asp.net mvc application
public ActionResult Delete(int ehrId, int id)
{
EHR ehr = ehrRepository.FindById(ehrId);
PhysicalTest test = ehr.PhysicalTests.Where(t => t.ID == id).Single();
return View(test);
}
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int ehrId, int id)
{
EHR ehr = ehrRepository.FindById(ehrId);
PhysicalTest test = ehr.PhysicalTests.Where(t => t.ID == id).Single();
ehr.PhysicalTests.Remove(test);
unitOfWork.Commit();
TempData["Success"] = "You have deleted the Physical Test Succesfully";
return RedirectToAction("Index");
}
the problem is that when I try to delete a child object this way and EF will complain
The operation failed: The relationship could not be changed because
one or more of the foreign-key properties is non-nullable. When a
change is made to a relationship, the related foreign-key property is
set to a null value. If the foreign-key does not support null values,
a new relationship must be defined, the foreign-key property must be
assigned another non-null value, or the unrelated object must be
deleted.
One answer is to use a PhysicalTest(child element) repository instead of a EHRRepository.. but that doesnt seem like a good solution cause I want to enforce the security of always querying through the parent object to avoid a user from editing/deleting a physicalTest that doesnt belong to him.
I would love to just limit my repositories to just aggregate roots.
Heres my current SqlRepository generic implementation.... Im open to suggestions.
public class SqlRepository<T> : IRepository<T>
where T : class, IEntity {
internal SummumnetDB context;
internal DbSet<T> _objectSet;
public SqlRepository(SummumnetDB context)
{
this.context = context;
this._objectSet = context.Set<T>();
}
public IQueryable<T> Find(Expression<Func<T, bool>> predicate) {
return _objectSet.Where(predicate);
}
public void Add(T newEntity) {
_objectSet.Add(newEntity);
}
public void Remove(T entity) {
_objectSet.Remove(entity);
}
public IQueryable<T> FindAll()
{
return _objectSet;
}
public T FindById(int id)
{
return _objectSet.Single(o => o.ID == id);
}
}
Your EHR and PhysicalTest forms aggregate where EHR is aggregate root for PhysicalTest because PhyscialTest cannot exist without EHR (your exception says that FK in PhysicalTest cannot be null). Repository should exist per aggregate root and it should offer specific method to deal with relations.
Yes it will not be generic because generic approach for entities with different configurations and requirements doesn't work.
What is the problem with your code? Calling ehr.PhysicalTests.Remove(test) will not delete test. It will only sets its FK to null. To delete test as well you must really call context.DeleteObject(test). To allow direct deleting you must use identifying relation.