EF send Includes list to repository via parametr - entity-framework

I at this moment I have repository filled with multiple gets methods.
E.q. Get1() => cxt.Entites.Include(e => e.obj1);
Get2() => cxt.Entities.Include(e => e.obj1).Include(e => e.obj2)
And so on.
Is there good method, pattern to have one GET method where I can send inclues via parameter?

public virtual IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}
See repository pattern in msdn
You can use
_sampleRepostiory.Get(h=>h.Id>1,null,"Employees.Departments");
Including same with lambda
public virtual IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
Expression<Func<TEntity, object>>[] includes)
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
if (includes != null)
{
query = includes.Aggregate(query,
(current, include) => current.Include(include));
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}
Consume it like this
var query = context.Customers
.Get(x=>x.Id>1,null,
c => c.Address,
c => c.Orders.Select(o => o.OrderItems));
Similar SO question

I did the following in my projects:
public Entity[] GetAll(bool includeObj1, bool includeAllOthers) {
IQueryable<Entity> entity = ctx.Entities;
if (includeObj1)
entity = entity.Include(e => e.obj1);
if (includeAllOthers) {
entity = entity
.Include(e => e.obj2)
.Include(e => e.obj3)
.Include(e => e.obj4)
.Include(e => e.obj5);
}
return entity.ToArray();
}
Providing arguments like includeObj1 and includeObj2 separates a consumer of repository from implementation and encapsulates any data access logic.
Passing direct "include these properties" orders to a repository means that you know how repository works and assume that it is some sort ORM which blurs abstractions.

Related

EF Core 2.1 Updating related entites query efficiency

I'm having a student model in which i have list of phone numbers and addresses.When i update the student the related data(phone and address) needs to be updated. I have written a PUT action in my student controller for that. It works fine, but I'm concerned about the efficiency of the query. Please check the code and suggest me improvisation if any. Thanks
public async Task<IActionResult> Put(long id, [FromBody] Student student)
{
var p = await _Context.Students
.Include(t => t.PhoneNumbers)
.Include(t => t.Addresses)
.SingleOrDefaultAsync(t => t.Id == id);
if (p == null)
{
return NotFound();
}
_Context.Entry(p).CurrentValues.SetValues(student);
#region PhoneNumber
var existingPhoneNumbers = p.PhoneNumbers.ToList();
foreach (var existingPhone in existingPhoneNumbers)
{
var phoneNumber = student.PhoneNumbers.SingleOrDefault(i => i.Id == existingPhone.Id);
if (phoneNumber != null)
_Context.Entry(existingPhone).CurrentValues.SetValues(phoneNumber);
else
_Context.Remove(existingPhone);
}
// add the new items
foreach (var phoneNumber in student.PhoneNumbers)
{
if (existingPhoneNumbers.All(i => i.Id != phoneNumber.Id))
{
p.PhoneNumbers.Add(phoneNumber);
}
}
#endregion
#region Address
var existingAddresses = p.Addresses.ToList();
foreach (var existingAddress in existingAddresses)
{
var address = student.Addresses.SingleOrDefault(i => i.Id == existingAddress.Id);
if (address != null)
_Context.Entry(existingAddress).CurrentValues.SetValues(address);
else
_Context.Remove(existingAddress);
}
// add the new items
foreach (var address in student.Addresses)
{
if (existingAddresses.All(i => i.Id != address.Id))
{
p.Addresses.Add(address);
}
}
#endregion
await _Context.SaveChangesAsync();
return NoContent();
}
Searching over small in-memory collections is not normally something you would worry about. So if a Student has dozens or hundreds of addresses, the repeated lookups are not going to be significant, especially compared with the time required to write to the database.
If you did want to optimize, you can copy the students Addresses to a Dictionary. Like this:
var existingAddresses = p.Addresses.ToList();
var studentAddresses = student.Addresses.ToDictionary(i => i.Id);
foreach (var existingAddress in existingAddresses)
{
if (studentAddresses.TryGetValue(existingAddress.Id, out Address address))
{
_Context.Entry(existingAddress).CurrentValues.SetValues(address);
}
else
{
_Context.Remove(existingAddress);
}
}
A query over an in-memory collection like this:
var address = student.Addresses.SingleOrDefault(i => i.Id == existingAddress.Id);
Will simply iterate all the student.Addresses comparint the Ids. A Dictionary<> acts like an index, providing very fast lookups.

How to simplify transforming entity framework entities to DTOs

I map to a data transformation object when retrieving items from an ASP.NET Web API like so for a list:
public async Task<IList<PromotionDTO>> GetPromotionsList()
{
return await _context.Promotions
.Select(p => new PromotionDTO
{
PromotionId = p.PromotionId,
Is_Active = p.Is_Active,
Created = p.Created,
Title = p.Title,
BusinessName = p.BusinessName,
})
.Where(x => x.Is_Active)
.OrderByDescending(x => x.Created)
.ToListAsync();
}
And like this for getting a single record:
public async Task<PromotionDTO> GetPromotion(int id)
{
return await _context.Promotions
.Select(p => new PromotionDTO
{
PromotionId = p.PromotionId,
Is_Active = p.Is_Active,
Created = p.Created,
Title = p.Title,
BusinessName = p.BusinessName,
})
.Where(x => x.Is_Active && x.PromotionId == id)
.FirstOrDefaultAsync();
}
I'm new to DTO's and I find that I'm using the same DTO transformation code at many places, and was wondering how I can simplify my code to only do this once?
Though it may be enough to map like you've stated, but when your project starts to grow it will just complicated things and cause additional work.
I suggest that you use some kind of mapping library like AutoMapper.
https://github.com/AutoMapper/AutoMapper
static MyRepositoryConstructor()
{
// Define your maps
Mapper.Initialize(cfg => {
cfg.CreateMap<PromotionEntity, PromotionDTO>();
});
}
public async Task<IList<PromotionDTO>> GetPromotionsList()
{
return Mapper.Map<IList<PromotionDTO>>(await _context.Promotions
.Where(x => x.Is_Active)
.OrderByDescending(x => x.Created)
.ToListAsync()
);
}
public async Task<PromotionDTO> GetPromotion(int id)
{
return Mapper.Map<PromotionDTO>(await _context.Promotions
.Where(x => x.Is_Active && x.PromotionId == id)
.FirstOrDefaultAsync()
);
}
One option is to create a method which returns an IQueryable and then use that in each
Private IQueryable<PromotionDTO> Query()
{
return _context.Promotions
.Select(p => new PromotionDTO
{
PromotionId = p.PromotionId,
Is_Active = p.Is_Active,
Created = p.Created,
Title = p.Title,
BusinessName = p.BusinessName,
});
}
public async Task<IList<PromotionDTO>> GetPromotionsList()
{
return await Query()
.Where(x => x.Is_Active)
.OrderByDescending(x => x.Created)
.ToListAsync();
}
public async Task<PromotionDTO> GetPromotion(int id)
{
return await Query()
.Where(x => x.Is_Active && x.PromotionId == id)
.FirstOrDefaultAsync();
}

LINQ Generic GroupBy And SelectBy With Method Syntax

I'm trying to make it generic but didn't succeed. It doesnt get related User entity.
return from transactions in context.Transaction
where transactions.Date.Month == date.Month && transactions.Date.Year == date.Year
join users in context.UserProfile on transactions.UserID equals users.UserID
orderby transactions.MonthlyTotalExperiencePoint
group transactions by transactions.UserID into groupedList
select groupedList.First()).Take(50).ToList();
I've tried that:
public async Task<List<object>> GetAllGroup<TReturn, TOrderKey, TGroupKey>(Expression<Func<IGrouping<TGroupKey, TEntity>, TReturn>> selectExp,
Expression<Func<TEntity, bool>> whereExp,
Expression<Func<TEntity, TOrderKey>> orderbyExp,
bool descending,
int top,
Expression<Func<TEntity, TGroupKey>> groupByExp,
params Expression<Func<TEntity, object>>[] includeExps)
{
var query = DbSet.Where(whereExp);
query = !descending ? query.OrderBy(orderbyExp) : query.OrderByDescending(orderbyExp);
if (includeExps != null)
query = includeExps.Aggregate(query, (current, exp) => current.Include(exp));
return await query.GroupBy(groupByExp).Select(selectExp).Take(top).ToListAsync();
}
It gives error at usage:
var item = await transactionRepository.GetAllGroup(x => x.FirstOrDefault(), x => x.Date != null, x => x.MonthlyTotalExperiencePoint, true, 50, x => x.MonthlyTotalExperiencePoint, x => x.User);

entityframework search value only to search not to update

I build a Repository just like Codefirst demo.
public virtual IQueryable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query);
}
else
{
return query;
}
}
But this search has a problem.
Sometimes other person use 'get' function to search value and this value will be used as param inside other function.
Just like this:
UserInfoBll userInfoBll = new UserInfoBll();
UserInfo userInfo = userInfoBll.Get(p => p.UserCode == "8001", null, "CorpShop").First();
ConsumeProcess.process(userInfo);
If userInfo value change in function ConsumeProcess.process when I process savechange.
It will update something I don't want to update, so I want to find a way. The search value is just for search, when value change it, not to update the value.
For this way I wrote this:
public virtual List<TEntity> GetList(Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
List<TEntity> tEntityList = Get(filter, orderBy, includeProperties).ToList();
SetEntityStates(EntityState.Detached, tEntityList);
return tEntityList;
}
protected void SetEntityStates(EntityState entityState, IEnumerable<TEntity> tEntity_params)
{
foreach (TEntity tEntity in tEntity_params)
{
if (tEntity != null)
{
context.Entry(tEntity).State = entityState;
}
}
}
Now it won't update, if somebody changed search value. But there is another problem.
If I use code like this, the property which is included can't get
UserInfoBll userInfoBll = new UserInfoBll();
userInfo = userInfoBll.GetList(p => p.UserCode == "8001", null, "CorpShop").First(); // CorpShop is include property
corpShop = userInfo.CorpShop; //userInfo.CorpShop is null
If you have an IQueryable, the easiest way to get entities disconnected from db is to use the AsNoTracking method.
public virtual List<TEntity> GetList(Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
return = Get(filter, orderBy, includeProperties).AsNoTracking().ToList();
}

How to use 'Or' in Where clauses?

I need something like this: Select name from users where id = 1 or id = 2.
I know I can do this:
_db.Users
.Where(u => u.Id == 1 || u.Id == 2);
But is there any alternative to this?
Is there something like this:
_db.User
.Where(u => u.Id == 1)
.Or
.Where(u => u.Id == 2)
Not directly. Remember, _db.Users.Where(u => u.Id == 1) is the user whose id is 1. You cannot get the user with id 2 from that, because it isn't there.
You can use some other approach, such as
var user1 = _db.Users.Where(u => u.Id == 1);
var user2 = _db.Users.Where(u => u.Id == 2);
var users = user1.Union(user2);
or
var userids = new int[] { 1, 2 };
var users = _db.Users.Where(u => userids.Contains(u.Id));
though.
I usually use next form to build dynamic linq with ORs and ANDs.
public class Linq
{
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expression1,
Expression<Func<T, bool>> expression2)
{
if (expression1 == null) throw new ArgumentNullException("expression1", "Consider setting expression1 to Linq.True<T>() or Linq.False<T>()");
var invokedExpr = Expression.Invoke(expression2, expression1.Parameters.Cast<Expression>());
return Expression.Lambda<Func<T, bool>>
(Expression.OrElse(expression1.Body, invokedExpr), expression1.Parameters);
}
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expression1,
Expression<Func<T, bool>> expression2)
{
if (expression1 == null) throw new ArgumentNullException("expression1", "Consider setting expression1 to Linq.True<T>() or Linq.False<T>()");
var invokedExpr = Expression.Invoke(expression2, expression1.Parameters.Cast<Expression>());
return Expression.Lambda<Func<T, bool>>
(Expression.AndAlso(expression1.Body, invokedExpr), expression1.Parameters);
}
public static Expression<Func<T, bool>> True<T>() { return f => true; }
public static Expression<Func<T, bool>> False<T>() { return f => false; }
}
And then use it in your code:
Expression<Func<User, bool>> searchExpression = Linq.False<User>();
searchExpression = Linq.Or<User>(searchExpression, b => b.Id==1);
searchExpression = Linq.Or<User>(searchExpression, b => b.Id==2);
Then you use this searchExpression as
_db.Users.Where(searchExpression);
With this form it is very easy to build dynamic queries, like:
Expression<Func<User, bool>> searchExpression = Linq.False<User>();
searchExpression = Linq.Or<User>(searchExpression, b => b.Id==1);
if (!String.IsNullOrEmpty(name))
searchExpression = Linq.Or<User>(searchExpression, b => b.Name.StartsWith(name));
//combine with OR / AND as much as you need
It will generate only one query to the DB.
You just want:
_db.Users.Where(u => (u.Id == 1 || u.Id == 2));