Multiple Includes() in EF Core - c#

I have an extension method that lets you generically include data in EF:
public static IQueryable<T> IncludeMultiple<T>(this IQueryable<T> query, params Expression<Func<T, object>>[] includes)
where T : class
{
if (includes != null)
{
query = includes.Aggregate(query, (current, include) => current.Include(include));
}
return query;
}
This allows me to have methods in my repository like this:
public Patient GetById(int id, params Expression<Func<Patient, object>>[] includes)
{
return context.Patients
.IncludeMultiple(includes)
.FirstOrDefault(x => x.PatientId == id);
}
I believe the extension method worked before EF Core, but now including "children" is done like this:
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ThenInclude(post => post.Author);
Is there a way to alter my generic extension method to support EF Core's new ThenInclude() practice?

As said in comments by other, you can take EF6 code to parse your expressions and apply the relevant Include/ThenInclude calls. It does not look that hard after all, but as this was not my idea, I would rather not put an answer with the code for it.
You may instead change your pattern for exposing some interface allowing you to specify your includes from the caller without letting it accessing the underlying queryable.
This would result in something like:
using YourProject.ExtensionNamespace;
// ...
patientRepository.GetById(0, ip => ip
.Include(p => p.Addresses)
.ThenInclude(a=> a.Country));
The using on namespace must match the namespace name containing the extension methods defined in the last code block.
GetById would be now:
public static Patient GetById(int id,
Func<IIncludable<Patient>, IIncludable> includes)
{
return context.Patients
.IncludeMultiple(includes)
.FirstOrDefault(x => x.EndDayID == id);
}
The extension method IncludeMultiple:
public static IQueryable<T> IncludeMultiple<T>(this IQueryable<T> query,
Func<IIncludable<T>, IIncludable> includes)
where T : class
{
if (includes == null)
return query;
var includable = (Includable<T>)includes(new Includable<T>(query));
return includable.Input;
}
Includable classes & interfaces, which are simple "placeholders" on which additional extensions methods will do the work of mimicking EF Include and ThenInclude methods:
public interface IIncludable { }
public interface IIncludable<out TEntity> : IIncludable { }
public interface IIncludable<out TEntity, out TProperty> : IIncludable<TEntity> { }
internal class Includable<TEntity> : IIncludable<TEntity> where TEntity : class
{
internal IQueryable<TEntity> Input { get; }
internal Includable(IQueryable<TEntity> queryable)
{
// C# 7 syntax, just rewrite it "old style" if you do not have Visual Studio 2017
Input = queryable ?? throw new ArgumentNullException(nameof(queryable));
}
}
internal class Includable<TEntity, TProperty> :
Includable<TEntity>, IIncludable<TEntity, TProperty>
where TEntity : class
{
internal IIncludableQueryable<TEntity, TProperty> IncludableInput { get; }
internal Includable(IIncludableQueryable<TEntity, TProperty> queryable) :
base(queryable)
{
IncludableInput = queryable;
}
}
IIncludable extension methods:
using Microsoft.EntityFrameworkCore;
// others using ommitted
namespace YourProject.ExtensionNamespace
{
public static class IncludableExtensions
{
public static IIncludable<TEntity, TProperty> Include<TEntity, TProperty>(
this IIncludable<TEntity> includes,
Expression<Func<TEntity, TProperty>> propertySelector)
where TEntity : class
{
var result = ((Includable<TEntity>)includes).Input
.Include(propertySelector);
return new Includable<TEntity, TProperty>(result);
}
public static IIncludable<TEntity, TOtherProperty>
ThenInclude<TEntity, TOtherProperty, TProperty>(
this IIncludable<TEntity, TProperty> includes,
Expression<Func<TProperty, TOtherProperty>> propertySelector)
where TEntity : class
{
var result = ((Includable<TEntity, TProperty>)includes)
.IncludableInput.ThenInclude(propertySelector);
return new Includable<TEntity, TOtherProperty>(result);
}
public static IIncludable<TEntity, TOtherProperty>
ThenInclude<TEntity, TOtherProperty, TProperty>(
this IIncludable<TEntity, IEnumerable<TProperty>> includes,
Expression<Func<TProperty, TOtherProperty>> propertySelector)
where TEntity : class
{
var result = ((Includable<TEntity, IEnumerable<TProperty>>)includes)
.IncludableInput.ThenInclude(propertySelector);
return new Includable<TEntity, TOtherProperty>(result);
}
}
}
IIncludable<TEntity, TProperty> is almost like IIncludableQueryable<TEntity, TProperty> from EF, but it does not extend IQueryable and does not allow reshaping the query.
Of course if the caller is in the same assembly, it can still cast the IIncludable to Includable and start fiddling with the queryable. But well, if someone wants to get it wrong, there is no way we would prevent him doing so (reflection allows anything). What does matter is the exposed contract.
Now if you do not care about exposing IQueryable to the caller (which I doubt), obviously just change your params argument for a Func<Queryable<T>, Queryable<T>> addIncludes argument, and avoid coding all those things above.
And the best for the end: I have not tested this, I do not use Entity Framework currently!

For posterity, another less eloquent, but simpler solution that makes use of the Include() overload that uses navigationPropertyPath:
public static class BlogIncludes
{
public const string Posts = "Posts";
public const string Author = "Posts.Author";
}
internal static class DataAccessExtensions
{
internal static IQueryable<T> IncludeMultiple<T>(this IQueryable<T> query,
params string[] includes) where T : class
{
if (includes != null)
{
query = includes.Aggregate(query, (current, include) => current.Include(include));
}
return query;
}
}
public Blog GetById(int ID, params string[] includes)
{
var blog = context.Blogs
.Where(x => x.BlogId == id)
.IncludeMultiple(includes)
.FirstOrDefault();
return blog;
}
And the repository call is:
var blog = blogRepository.GetById(id, BlogIncludes.Posts, BlogIncludes.Author);

You can do something like this:
public Patient GetById(int id, Func<IQueryable<Patient>, IIncludableQueryable<Patient, object>> includes = null)
{
IQueryable<Patient> queryable = context.Patients;
if (includes != null)
{
queryable = includes(queryable);
}
return queryable.FirstOrDefault(x => x.PatientId == id);
}
var patient = GetById(1, includes: source => source.Include(x => x.Relationship1).ThenInclude(x => x.Relationship2));

I made this method to do the dynamic includes. This way the "Select" command can be used in lambda to include just as it was in the past.
The call works like this:
repository.IncludeQuery(query, a => a.First.Second.Select(b => b.Third), a => a.Fourth);
private IQueryable<TCall> IncludeQuery<TCall>(
params Expression<Func<TCall, object>>[] includeProperties) where TCall : class
{
IQueryable<TCall> query;
query = context.Set<TCall>();
foreach (var property in includeProperties)
{
if (!(property.Body is MethodCallExpression))
query = query.Include(property);
else
{
var expression = property.Body as MethodCallExpression;
var include = GenerateInclude(expression);
query = query.Include(include);
}
}
return query;
}
private string GenerateInclude(MethodCallExpression expression)
{
var result = default(string);
foreach (var argument in expression.Arguments)
{
if (argument is MethodCallExpression)
result += GenerateInclude(argument as MethodCallExpression) + ".";
else if (argument is MemberExpression)
result += ((MemberExpression)argument).Member.Name + ".";
else if (argument is LambdaExpression)
result += ((MemberExpression)(argument as LambdaExpression).Body).Member.Name + ".";
}
return result.TrimEnd('.');
}

Ofcourse there is,
you could traverse the Expression tree of original params, and any nested includes, add them as
.Include(entity => entity.NavigationProperty)
.ThenInclude(navigationProperty.NestedNavigationProperty)
But its not trivial, but definitely very doable, please share if you do, as it can defintiely be reused!

public Task<List<TEntity>> GetAll()
{
var query = _Db.Set<TEntity>().AsQueryable();
foreach (var property in _Db.Model.FindEntityType(typeof(TEntity)).GetNavigations())
query = query.Include(property.Name);
return query.ToListAsync();
}

I adhere the simpler solution that makes use of the Include() overload that uses string navigationPropertyPath. The simplest that I can write is this extension method below.
using Microsoft.EntityFrameworkCore;
using System.Linq;
namespace MGame.Data.Helpers
{
public static class IncludeBuilder
{
public static IQueryable<TSource> Include<TSource>(this IQueryable<TSource> queryable, params string[] navigations) where TSource : class
{
if (navigations == null || navigations.Length == 0) return queryable;
return navigations.Aggregate(queryable, EntityFrameworkQueryableExtensions.Include); // EntityFrameworkQueryableExtensions.Include method requires the constraint where TSource : class
}
}
}

public TEntity GetByIdLoadFull(string id, List<string> navigatonProoperties)
{
if (id.isNullOrEmpty())
{
return null;
}
IQueryable<TEntity> query = dbSet;
if (navigationProperties != null)
{
foreach (var navigationProperty in navigationProperties)
{
query = query.Include(navigationProperty.Name);
}
}
return query.SingleOrDefault(x => x.Id == id);
}
Here is a much simpler solution, idea is to cast the dbset to iqueryable and then recursively include properties

I handle it with like that;
I have Article entity. It includes ArticleCategory entity. And also ArticleCategory entity includes Category entity.
So: Article -> ArticleCategory -> Category
In My Generic Repository;
public virtual IQueryable<T> GetIncluded(params Func<IQueryable<T>, IIncludableQueryable<T, object>>[] include)
{
IQueryable<T> query = Entities; // <- this equals = protected virtual DbSet<T> Entities => _entities ?? (_entities = _context.Set<T>());
if (include is not null)
{
foreach (var i in include)
{
query = i(query);
}
}
return query;
}
And I can use it like that;
var query = _articleReadRepository.GetIncluded(
i => i.Include(s => s.ArticleCategories).ThenInclude(s => s.Category),
i => i.Include(s => s.User)
);

Related

Conditionally call Linq extension method

I have an abstract class that implement all of my queries. My Get function is:
public virtual List<TEntity> Get(Expression<Func<TEntity, bool>> criteria)
{
return _dbSet.Where(criteria).OrderByDescending(OrderBy).ThenBy(ThenOrderBy).ToList();
}
And the ThenOrderBy property is:
public virtual Func<TEntity, object> ThenOrderBy { get; set; }
The OrderBy property is required, but my new property, ThenOrderBy is not required and not implemented in all classes that inherits this abstract class.
I am getting this error:
Value can not be null
Is there any way to mantain this clean code without putting blocks of "if's" statments?
Solution that I Used:
public virtual List<TEntity> Consultar(Expression<Func<TEntity, bool>> criteria)
{
var query = _dbSet.Where(criteria);
query = OrderDescending ?
query.OrderByDescending(OrderBy).AndOptionallyBy(ThenOrderBy)
:
query.OrderBy(OrderBy).AndOptionallyBy(ThenOrderBy);
query = (paggedSearch && Skip > 0) ? query.Skip(Skip) : query;
query = (paggedSearch && Take > 0) ? query.Take(Take) : query;
return query.ToList();
}
And Created a new Extension Class
public static class ExtensionMethods
{
public static IOrderedQueryable<TSource> AndOptionallyBy<TSource, TKey>(this IOrderedQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector)
{
return (keySelector == null) ? source : source.ThenBy(keySelector);
}
}
No, you need to block it with an if. However, you can build up your EF queries without hitting the database:
public virtual List<TEntity> Get(Expression<Func<TEntity, bool>> criteria)
{
var query = _dbSet.Where(criteria);
if(OrderBy != null)
{
query = query.OrderByDescending(OrderBy);
if(ThenOrderBy != null)
{
query = query.ThenBy(ThenOrderBy);
}
}
return query.ToList();
}
If short then:
public virtual List<TEntity> Get(Expression<Func<TEntity, bool>> criteria)
{
var result = _dbSet.Where(criteria).OrderByDescending(OrderBy);
if(ThenOrderBy != null)
{
result = result.ThenBy(ThenOrderBy);
}
return result.ToList();
}
You could create your own AndOptionallyBy method that applies the second condition if supplied, or leaves the IOrderedQueryable as is otherwise, eg:
public static IOrderedQueryable<TSource> AndOptionallyBy<TSource, TKey>(
this IOrderedQueryable<TSource> source,
Expression<Func<TSource, TKey>> keySelector)
{
if (keySelector==null)
{
return source;
}
else
{
return source.ThenBy(keySelector);
}
}
I wouldn't use this though. While this allows you to use function chaining, it will surprise maintainers of your code.
On the one hand you have :
var result = _dbSet.Where(criteria)
.OrderByDescending(OrderBy)
.AndOptionallyBy(ThenOrderBy)
.ToList();
On the other
var query = _dbSet.Where(criteria)
.OrderByDescending(OrderBy);
if (ThenOrderBy!=null)
{
query = query.ThenBy(ThenOrderBy);
}
var result = query.ToList();
Which do you think is clearer to someone else?
UPDATE
It's easy to create a paging query using conditionals:
var query = _dbSet.Where(criteria);
var orderedQuery=OrderDescending
?query.OrderByDescending(OrderBy)
:query.OrderBy(OrderBy);
if (buscaPaginada)
{
if (Skip > 0)
{
query = query.Skip(Skip);
}
if (Take >0)
{
query = query.Skip(Skip);
}
}
return query.ToList();
Sometimes the cleanest solution is using a bunch of if() statements. In this case you can check whether the ThenOrderBy is null:
public virtual List<TEntity> Get(Expression<Func<TEntity, bool>> criteria)
{
IQueryable<TEntity> recordsToReturn = _dbSet.Where(criteria);
if (OrderBy != null)
{
var orderedRecordsToReturn = recordsToReturn.OrderByDescending(OrderBy);
recordsToReturn = orderedRecordsToReturn;
// You can only call ThenBy() on an IOrderedQueryable
if (ThenOrderBy != null)
{
recordsToReturn = orderedRecordsToReturn.ThenBy(ThenOrderBy);
}
}
return recordsToReturn.ToList();
}
Sure, there are ways to abuse syntax to condense this code, but being explicit about what is happening makes code easier to read, hence cleaner.
If you want the ThenBy to have a default behavior then just default it to something which will have no effecton the ordering instead of null. For example entity => 1
I tested it like the below, which allows me to not set OrderBy or ThenOrderBy and it still all works as expected:
public class TestClass<TEntity>
{
private IEnumerable<TEntity> data;
public TestClass(IEnumerable<TEntity> data){
OrderBy = (t) => 1;
ThenOrderBy = (t) => 1;
this.data = data;
}
public IEnumerable<TEntity> Get(Func<TEntity, bool> criteria){
return data.Where(criteria).OrderBy(OrderBy).ThenBy(ThenOrderBy);
}
public Func<TEntity, object> OrderBy { get; set; }
public Func<TEntity, object> ThenOrderBy { get; set; }
}
Live example (you can uncomment the ThenOrderBy line to see it working with/without) : http://rextester.com/YFOB38755
By the way, your existing code could be made much more readable/simple:
public virtual List<TEntity> Get(Expression<Func<TEntity, bool>> criteria)
{
IEnumerable<TEntity> query = _dbSet.Where(criteria);
query = OrderDescending ? query.OrderByDescending(OrderBy) : query.OrderBy(OrderBy)
if (paggedSearch)
{
if(Skip > 0)
query = query.Skip(Skip);
if(Take > 0)
query = query.Take(Take);
}
return query.ToList();
}
There was no need to keep repeating yourself (DRY!)

passing dynamic expression to order by in code first EF repository

we have written a Generic function to get the records from EF code first in a repository pattern. Rest seems to be ok but when passing an Integer to the dynamic order by , it says Cannot cast System.Int32 to System.Object
the expression is as follows:
Expression<Func<HeadOffice, object>> orderByFunc = o => o.Id;
if (options.sort == "Id")
{
// this is an Integer
orderByFunc = o => o.Id;
}
if (options.sort =="Name")
{
// string
orderByFunc = o => o.Name;
}
if (options.sort == "Code")
{
orderByFunc = o => o.Code;
}
the generic method is as follows:
public virtual IEnumerable<TEntity> GetSorted<TSortedBy>(
Expression<Func<TEntity, object>> order,
int skip, int take,
params Expression<Func<TEntity, object>>[] includes)
{
IQueryable<TEntity> query = dbSet;
foreach (var include in includes)
{
query = dbSet.Include(include);
}
IEnumerable<TEntity> data = query.OrderBy(order).Skip(skip).Take(take).ToList();
return data;
}
if we convert Expression<Func<TEntity, object>> to Expression<Func<TEntity, int>> then it seems to work fine with integer but consequently not with strings
any help appreciated.
Maybe if you change the type of that parameter for this Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy, it could make your life easier:
public virtual IEnumerable<TEntity> GetSorted(Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy,...)
{
IQueryable<TEntity> query = dbSet;
//...
if (orderBy != null)
{
query = orderBy(query);
}
//...
}
This way you can pass an Func like this:
Func<IQueryable<HeadOffice>, IOrderedQueryable<HeadOffice>> orderBy=null;
if (options.sort == "Id")
{
orderBy= query=>query.OrderBy(o => o.Id);
}
//...
Update
Another thing that I notice now is you are not using the TSortedBy generic parameter, so, you could also do this:
public virtual IEnumerable<TEntity> GetSorted<TSortedBy>(Expression<Func<TEntity, TSortedBy>> order,
int skip, int take,
params Expression<Func<TEntity, object>>[] includes)
{
}
But anyway I think is better use the first option and remove that generic parameter.
Create a Sorter class. We also need a property-type-neutral base class:
public class SorterBase<TEntity>
{
public abstract IEnumerable<TEntity> GetSorted( // Note, no order argument here
int skip, int take,
params Expression<Func<TEntity, object>>[] includes);
}
public class Sorter<TEntity, TSortProp> : SorterBase<TEntity>
{
private Expression<Func<TEntity, TSortProp>> _order;
public Sorter(Expression<Func<TEntity, TSortProp>> order)
{
_order = order;
}
public override IEnumerable<TEntity> GetSorted(...)
{
// Use _order here ...
}
}
Now change the sort decision to:
SorterBase<HeadOffice> sorter;
if (options.sort == "Id") {
sorter = new Sorter<HeadOffice, int>(o => o.Id);
} else if (options.sort == "Name") {
sorter = new Sorter<HeadOffice, string>(o => o.Name);
}
...
var result = sorter.GetSorted(skip, take, includes);
One solution is to have two overloaded methods, one takes
Expression<Func<TEntity, int>>
and one takes
Expression<Func<TEntity, string>>
To minimize code duplication, extract common code (for example the query initialization statement and the for loop) to a shared method, and just let the two methods call this shared method and then invoke OrderBy on the result.
If none of the answers work for you and you must have the order expression as:
Expression<Func<TEntity,object>>
then try the following solution:
public class ExpressionHelper : ExpressionVisitor
{
private MemberExpression m_MemberExpression;
public MemberExpression GetPropertyAccessExpression(Expression expression)
{
m_MemberExpression = null;
Visit(expression);
return m_MemberExpression;
}
protected override Expression VisitMember(MemberExpression node)
{
var property = node.Member as PropertyInfo;
if (property != null)
{
m_MemberExpression = node;
}
return base.VisitMember(node);
}
}
public class DataClass<TEntity>
{
private readonly IQueryable<TEntity> m_Queryable;
public DataClass(IQueryable<TEntity> queryable)
{
m_Queryable = queryable;
}
public virtual IEnumerable<TEntity> GetSorted(
Expression<Func<TEntity, object>> order,
int skip, int take,
params Expression<Func<TEntity, object>>[] includes)
{
var property_access_expression = new ExpressionHelper().GetPropertyAccessExpression(order);
if(property_access_expression == null)
throw new Exception("Expression is not a property access expression");
var property_info = (PropertyInfo) property_access_expression.Member;
var covert_method = this.GetType().GetMethod("Convert").MakeGenericMethod(property_info.PropertyType);
var new_expression = covert_method.Invoke(this, new object[] {property_access_expression, order.Parameters });
var get_sorted_method = this.GetType()
.GetMethod("GetSortedSpecific")
.MakeGenericMethod(property_info.PropertyType);
return (IEnumerable<TEntity>)get_sorted_method.Invoke(this, new object[] { new_expression, skip, take, includes });
}
public virtual IEnumerable<TEntity> GetSortedSpecific<TSortedBy>(
Expression<Func<TEntity, TSortedBy>> order,
int skip, int take,
params Expression<Func<TEntity, object>>[] includes)
{
IQueryable<TEntity> query = m_Queryable;
//Here do your include logic and any other logic
IEnumerable<TEntity> data = query.OrderBy(order).Skip(skip).Take(take).ToList();
return data;
}
public Expression<Func<TEntity, TNewKey>> Convert<TNewKey>(
MemberExpression expression, ReadOnlyCollection<ParameterExpression> parameter_expressions )
{
return Expression.Lambda<Func<TEntity, TNewKey>>(expression, parameter_expressions);
}
}
Here is how I tested this:
void Test()
{
Expression<Func<Entity, object>> exp = (x) => x.Text;
List<Entity> entities = new List<Entity>();
entities.Add(new Entity()
{
Id = 1,
Text = "yacoub"
});
entities.Add(new Entity()
{
Id = 2,
Text = "adam"
});
DataClass<Entity> data = new DataClass<Entity>(entities.AsQueryable());
var result = data.GetSorted(exp, 0, 5, null);
}
I tested this with both integer and string properties.
Please note that this only works for simple property access expressions.
You can enhance the GetSorted method to make this work for more complex cases.

EntityFramework : FindBy (column name) select arbitrary column by expression tree

I have a very simple code of what I am trying to achieve here.
Basically the idea here would be to be able to do a simple FindBy(x => x.<the_column_name>, <the value>); therefore I wouldn't have to copy-paste the same query by changing only the column name.
For now I keep getting an exception from LINQ saying LINQ to Entities does not recognize the method 'System.String Invoke(Spunky.Entities.User)' method, and this method cannot be translated into a store expression
Is that something possible ? Or maybe we're not yet there with EF 6.1?
public class UsersRepository
{
private Lazy<IDataContext> context;
public UsersRepository()
: this(() => new DataContext())
{
}
public UsersRepository(Func<IDataContext> context)
{
this.context = new Lazy<IDataContext>(context);
}
public IQueryable<User> Find(int id)
{
return FindBy(u => u.Id, id);
}
public IQueryable<User> Find(string username)
{
return FindBy(u => u.Username, username);
}
public IQueryable<User> FindBy<T>(Func<User, T> property, T value)
{
return context.Value
.Users
.Where(u => property.Invoke(u).Equals(value));
}
}
If you change the signature
IQueryable<User> FindBy(Expression<Func<User, bool>> predicate)
{
return context.Users.Where(predicate);
}
you can call
return FindBy(u => u.Username == username);
The code hardly changes, but you don't need to manufacture expressions.
You have to enter an expression (not a Func) because expressions can be translated into SQL. A Func is just a .Net delegate, for which no SQL equivalent exists.
You need to construct an expression tree linq to entities can translate to sql:
public IQueryable<T> FindBy<TKey>(Expression<Func<T,TKey>> memberExpression, object value)
{
var parameterVisitor = new ParameterVisitor(memberExpression);
var parameter = parameterVisitor.Parameter;
var constant = Expression.Constant(value);
var equal = Expression.Equal(memberExpression,constant);
var predicate = Expression.Lambda(equal, parameter);
return context.Value.Users.Where(predicate);
}
public class ParameterVisitor : ExpressionVisitor
{
public ParameterExpression Parameter { get;set;}
public ParameterVisitor(Expression expr)
{
this.Visit(expr);
}
protected override VisitParameter(ParameterExpression parameter)
{
Parameter = parameter;
return parameter;
}
}
Still untested from the top of my head.
Edit: Code corrected.

Add a LINQ or DBContext extension method to get an element if not exist then create with data in predicate (FirstOrCreate)

I'm trying to add a LINQ or DbContext extension method to get an element (FirstOrDefault) but if one does not already exist then create a new instance with data (FirstOrCreate) instead of returning null.
is this possible?
i.e.:
public static class LINQExtension
{
public static TSource FirstOrCreate<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate)
{
if (source.First(predicate) != null)
{
return source.First(predicate);
}
else
{
return // ???
}
}
}
and a usage could be:
using (var db = new MsBoxContext())
{
var status = db.EntitiesStatus.FirstOrCreate(s => s.Name == "Enabled");
//Here we should get the object if we find one
//and if it doesn't exist create and return a new instance
db.Entities.Add(new Entity()
{
Name = "New Entity",
Status = status
});
}
I hope that you understand my approach.
public static class LINQExtension
{
public static TSource FirstOrCreate<TSource>(
this IQueryable<TSource> source,
Expression<Func<TSource, bool>> predicate,
Func<T> defaultValue)
{
return source.FirstOrDefault(predicate) ?? defaultValue();
}
}
usage
var status = db.EntitiesStatus.FirstOrCreate(s => s.Name == "Enabled",
() => new EntityStatus {Name = "Enabled"});
However you must note that this will not work quite like FirstOrDefault().
If you did the following
var listOfStuff = new List<string>() { "Enabled" };
var statuses = from s in listOfStuff
select db.EntitiesStatus.FirstOrCreate(s => s.Name == "Enabled",
() => new EntityStatus {Name = "Enabled"});
You would get O(n) hits to the database.
However I suspect if you did...
var listOfStuff = new List<string>() { "Enabled" };
var statuses = from s in listOfStuff
select db.EntitiesStatus.FirstOrDefault(s => s.Name == "Enabled")
?? new EntityStatus {Name = "Enabled"};
It is plausible it could work...
conclussion:
instead of implement an extension method the best solution is using ?? operator in that way:
var status = db.EntitiesStatus.FirstOrDefault(s => s.Name == "Enabled") ?? new EntityStatus(){Name = "Enabled"};
I am a self taught programmer and I am really bad at typing so i was looking for the exact same thing. I ended up writing my own. It took a few steps and revisions before it would work with more than 1 property. Of course there are some limitations and I haven't fully tested it but so far it seems to work for my purposes of keeping the records distinct in the DB and shortening the code (typing time).
public static class DataExtensions
{
public static TEntity InsertIfNotExists<TEntity>(this ObjectSet<TEntity> objectSet, Expression<Func<TEntity, bool>> predicate) where TEntity : class, new()
{
TEntity entity;
#region Check DB
entity = objectSet.FirstOrDefault(predicate);
if (entity != null)
return entity;
#endregion
//NOT in the Database... Check Local cotext so we do not enter duplicates
#region Check Local Context
entity = objectSet.Local().AsQueryable().FirstOrDefault(predicate);
if (entity != null)
return entity;
#endregion
///********* Does NOT exist create entity *********\\\
entity = new TEntity();
// Parse Expression Tree and set properties
//Hit a recurrsive function to get all the properties and values
var body = (BinaryExpression)((LambdaExpression)predicate).Body;
var dict = body.GetDictionary();
//Set Values on the new entity
foreach (var item in dict)
{
entity.GetType().GetProperty(item.Key).SetValue(entity, item.Value);
}
return entity;
}
public static Dictionary<string, object> GetDictionary(this BinaryExpression exp)
{
//Recurssive function that creates a dictionary of the properties and values from the lambda expression
var result = new Dictionary<string, object>();
if (exp.NodeType == ExpressionType.AndAlso)
{
result.Merge(GetDictionary((BinaryExpression)exp.Left));
result.Merge(GetDictionary((BinaryExpression)exp.Right));
}
else
{
result[((MemberExpression)exp.Left).Member.Name] = exp.Right.GetExpressionVaule();
}
return result;
}
public static object GetExpressionVaule(this Expression exp)
{
if (exp.NodeType == ExpressionType.Constant)
return ((ConstantExpression)exp).Value;
if (exp.Type.IsValueType)
exp = Expression.Convert(exp, typeof(object));
//Taken From http://stackoverflow.com/questions/238413/lambda-expression-tree-parsing
var accessorExpression = Expression.Lambda<Func<object>>(exp);
Func<object> accessor = accessorExpression.Compile();
return accessor();
}
public static IEnumerable<T> Local<T>(this ObjectSet<T> objectSet) where T : class
{
//Taken From http://blogs.msdn.com/b/dsimmons/archive/2009/02/21/local-queries.aspx?Redirected=true
return from stateEntry in objectSet.Context.ObjectStateManager.GetObjectStateEntries(
EntityState.Added |
EntityState.Modified |
EntityState.Unchanged)
where stateEntry.Entity != null && stateEntry.EntitySet == objectSet.EntitySet
select stateEntry.Entity as T;
}
public static void Merge<TKey, TValue>(this Dictionary<TKey, TValue> me, Dictionary<TKey, TValue> merge)
{
//Taken From http://stackoverflow.com/questions/4015204/c-sharp-merging-2-dictionaries
foreach (var item in merge)
{
me[item.Key] = item.Value;
}
}
}
Usage is as simple as:
var status = db.EntitiesStatus.InsertIfNotExists(s => s.Name == "Enabled");
The extension will check the database first, if is not found it will check the local context (so you do not add it twice), if it is still not found it creates the entity, parses the expression tree to get the properties and values from the lambda expression, sets those values on a new entity, adds the entity to the context and returns the new entity.
A few things to be aware of...
This does not handle all possible uses (assumes all the expressions in the lambda are ==)
The project I did this in is using an ObjectContext as apposed to a DBContext (I have not switched yet so I don't know if this would work with DBContext. I assume it would not be difficult to change)
I am self-taught so there maybe many ways to optimize this. If you have any input please let me know.
What about this extension that also adds the new created entity to the DbSet.
public static class DbSetExtensions
{
public static TEntity FirstOrCreate<TEntity>(
this DbSet<TEntity> dbSet,
Expression<Func<TEntity, bool>> predicate,
Func<TEntity> defaultValue)
where TEntity : class
{
var result = predicate != null
? dbSet.FirstOrDefault(predicate)
: dbSet.FirstOrDefault();
if (result == null)
{
result = defaultValue?.Invoke();
if (result != null)
dbSet.Add(result);
}
return result;
}
public static TEntity FirstOrCreate<TEntity>(
this DbSet<TEntity> dbSet,
Func<TEntity> defaultValue)
where TEntity : class
{
return dbSet.FirstOrCreate(null, defaultValue);
}
}
The usage with predicate:
var adminUser = DbContext.Users.FirstOrCreate(u => u.Name == "Admin", () => new User { Name = "Admin" });
or without predicate:
var adminUser = DbContext.Users.FirstOrCreate(() => new User { Name = "Admin" });

Use Include() method in repository

I have the following with EF 5:
var a = context.Posts.Include(x => x.Pack).Select(x => x.Pack.Id).ToList();
This works. Then I tried to replicate this in my generic repository:
public IQueryable<T> Include<T>(Expression<Func<T, Boolean>> criteria) where T : class
{
return _context.Set<T>().Include(criteria);
}
But in this case I am not able to do the following:
var b = repository.Include<Post>(x => x.Pack).Select(x => x.Pack.Id).ToList();
I get the error:
Cannot implicitly convert type 'Data.Entities.Pack' to 'bool'
How can I solve this?
What should I change in my Include() method?
Try:
Change
Expression<Func<T, Boolean>> criteria
To
Expression<Func<T, object>> criteria
Edit:
To Include multiple entities, you need to add an "include" extension:
public static class IncludeExtension
{
public static IQueryable<TEntity> Include<TEntity>(this IDbSet<TEntity> dbSet,
params Expression<Func<TEntity, object>>[] includes)
where TEntity : class
{
IQueryable<TEntity> query = null;
foreach (var include in includes)
{
query = dbSet.Include(include);
}
return query == null ? dbSet : query;
}
}
Then you can use it like this:
repository.Include(x => x.Pack, x => x.Pack.Roles, ...).Select(x => x.Pack.Id).ToList();
Just make sure "repository" return a "DbSet" Object.
I used the accepted answer but had to modify it a bit for EntityFramework Core.
In my experience I had to keep the chain going or the previous query references were overwritten.
public 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;
}
This is what we ended by doing in EF 6
public IEnumerable<T> GetIncludes(params Expression<Func<T, Object>>[] includes)
{
IQueryable<T> query = table.Include(includes[0]);
foreach (var include in includes.Skip(1))
{
query = query.Include(include);
}
return query.ToList();
}

Categories

Resources