using IQueryable deferred execution - c#

I am working on a simple mapping EntityFramework <> DTO's , it's working perfecly excepto for the deferred execution , I have the following code :
public abstract class Assembler<TDto, TEntity> : IAssembler<TDto, TEntity>
where TEntity : EntityBase , new ()
where TDto : DtoBase, new ()
{
public abstract TDto Assemble(TEntity domainEntity);
public abstract TEntity Assemble(TEntity entity, TDto dto);
public virtual IQueryable<TDto> Assemble(IQueryable<TEntity> domainEntityList)
{
List<TDto> dtos = Activator.CreateInstance<List<TDto>>();
foreach (TEntity domainEntity in domainEntityList)
{
dtos.Add(Assemble(domainEntity));
}
return dtos.AsQueryable();
}
public virtual IQueryable<TEntity> Assemble(IQueryable<TDto> dtoList)
{
List<TEntity> domainEntities = Activator.CreateInstance<List<TEntity>>();
foreach (TDto dto in dtoList)
{
domainEntities.Add(Assemble(null, dto));
}
return domainEntities.AsQueryable();
}
}
Sample Assembler :
public partial class BlogEntryAssembler : Assembler<BlogEntryDto, BlogEntry>, IBlogEntryAssembler
{
public override BlogEntry Assemble(BlogEntry entity, BlogEntryDto dto)
{
if (entity == null)
{
entity = new BlogEntry();
}
/*
entity.Id = dto.Id;
entity.Created = dto.Created;
entity.Modified = dto.Modified;
entity.Header = dto.Header;
*/
base.MapPrimitiveProperties(entity, dto);
this.OnEntityAssembled(entity);
return entity;
}
public override BlogEntryDto Assemble(BlogEntry entity)
{
BlogEntryDto dto = new BlogEntryDto();
//dto.Id = entity.Id;
//dto.Modified = entity.Modified;
//dto.Created = entity.Created;
//dto.Header = entity.Header;
base.MapPrimitiveProperties(dto, entity);
dto.CategoryName = entity.Category.Name;
dto.AuthorUsername = entity.User.Username;
dto.AuthorFirstName = entity.User.FirstName;
dto.AuthorLastName = entity.User.LastName;
dto.TagNames = entity.Tags.Select(t => t.Name)
.ToArray();
dto.TagIds = entity.Tags.Select(t => t.Id)
.ToArray();
dto.VotedUpUsernames = entity.BlogEntryVotes.Where(v => v.Vote > 0)
.Select(t => t.User.Username)
.ToArray();
dto.VotedDownUsernames = entity.BlogEntryVotes.Where(v => v.Vote < 0)
.Select(t => t.User.Username)
.ToArray();
// Unmapped
dto.FileCount = entity.BlogEntryFiles.Count();
dto.CommentCount = entity.BlogEntryComments.Count();
dto.VisitCount = entity.BlogEntryVisits.Count();
dto.VoteCount = entity.BlogEntryVotes.Count();
dto.VoteUpCount = entity.BlogEntryVotes.Count(v => v.Vote.Equals(1));
dto.VoteDownCount = entity.BlogEntryVotes.Count(v => v.Vote.Equals(-1));
dto.VotePuntuation = entity.BlogEntryVotes.Sum(v => v.Vote);
dto.Published = entity.Visible && entity.PublishDate <= DateTime.Now;
this.OnDTOAssembled(dto);
return dto;
}
}
my service class :
public virtual PagedResult<BlogEntryDto> GetAll(bool includeInvisibleEntries, string tag, string search, string category, Paging paging)
{
var entries = this.Repository.GetQuery()
.Include(b => b.Tags)
.Include(b => b.User)
.Include(b => b.Category)
.Include(b => b.BlogEntryFiles)
.Include(b => b.BlogEntryComments)
.Include(b => b.BlogEntryPingbacks)
.Include(b => b.BlogEntryVisits)
.Include(b => b.BlogEntryVotes)
.Include(b => b.BlogEntryImages)
.AsNoTracking();
if (!includeInvisibleEntries)
{
entries = entries.Where(e => e.Visible);
}
if (!string.IsNullOrEmpty(category))
{
entries = entries.Where(e => e.Category.Name.Equals(category, StringComparison.OrdinalIgnoreCase));
}
if (!string.IsNullOrEmpty(tag))
{
entries = entries.Where(e => e.Tags.Count(t => t.Name.Equals(tag, StringComparison.OrdinalIgnoreCase)) > 0);
}
if (!string.IsNullOrEmpty(search))
{
foreach (var item in search.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries))
{
entries = entries.Where(e => e.Header.Contains(item));
}
}
return this.Assembler.Assemble(entries).GetPagedResult(paging);
}
When I call the GetAll method it returns and converts all the entities in the table to Dto's and only then it pages the resulting collection, of course that's not what I was expecting.. I would like to execute the code inside my Assemble method once the paging has been done, any idea?
PS : I know I could use Automapper, just trying to learn the internals.

In your assembler you add the projected DTOs to a List<TDto>. That's detrimental in two ways. First, it is forced execution, because the list is filled and then returned. Second, at that moment you switch from LINQ to Entities to LINQ to objects and there is no way back. You can convert the list to IQueryable again by AsQueryable, but that does not re-inject the EF query provider. In fact, the conversion is useless.
That's why AutoMapper's ProjectTo<T> statement is so cool. It transfers expressions that come after the To all the way back to the original IQueryable and, hence, its query provider. If these expressions contain paging statements (Skip/Take) these will be translated into SQL. So I think that you'll quickly come to the conclusion you better use AutoMapper after all.

public virtual IQueryable<TDto> Assemble(IQueryable<TEntity> domainEntityList)
{
List<TDto> dtos = Activator.CreateInstance<List<TDto>>();
foreach (TEntity domainEntity in domainEntityList)
{
dtos.Add(Assemble(domainEntity));
}
return dtos.AsQueryable();
}
The foreach loop in the above code is the point at which you're executing the query on the database server.
As you can see from this line:
return this.Assembler.Assemble(entries).GetPagedResult(paging);
This method is getting called before GetPagedResult(paging).. so that is the reason paging happens on the full result set.
You should understand that enumerating a query (foreach) requires the query to run. Your foreach loop processes each and every record returned by that query.. it's too late at this point for a Paging method to do anything to stop it!

Related

Can I get entity relationships dynamically in SaveChanges()?

TargetFramework: netstandard2.0
EntityFrameworkCore: 2.2.6
I have the following code in OnModelCreating:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<SlotOrder>(entity =>
{
entity.Property(e => e.Id).HasConversion(
v => v.ToString(),
v => new Guid(v));
});
modelBuilder.Entity<SlotOrderDetail>(entity =>
{
entity.Property(e => e.Id).HasConversion(
v => v.ToString(),
v => new Guid(v));
entity.HasOne<SlotOrder>()
.WithMany()
.HasForeignKey(c => c.SlotOrderId);
});
}
I do not use navigation properties and need to load all relationships of a particular entity in SaveChangesAsync. In my case if the entity is SlotOrder I need to determine that it has a child entity SlotOrderDetail:
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
ChangeTracker.DetectChanges();
var utcNow = DateTime.UtcNow;
var added = ChangeTracker.Entries()
.Where(t => t.State == EntityState.Added)
.Select(t => t.Entity)
.ToList();
added.ForEach(entity =>
{
if (entity is IAuditable auditable)
{
auditable.CreatedAt = utcNow;
auditable.UpdatedAt = utcNow;
}
// var relationships = ...
});
return base.SaveChangesAsync(cancellationToken);
}
Any clue how to do that?
The relationship metadata is provided by IForeignKey interface.
Given an IEntityType, there are two methods that you can use to obtain information for entity relationships - GetForeignKeys which returns the relationships where the entity is the dependent, and GetReferencingForeignKeys which return the relationships where the entity is the principal.
In your case, don't select the .Entity property, use the EntityEntry which gives you access to the IEntityType via Metadata property, e.g.
var addedEntries = ChangeTracker.Entries()
.Where(t => t.State == EntityState.Added)
.ToList();
addedEntries.ForEach(entry =>
{
if (entry.Entity is IAuditable auditable)
{
auditable.CreatedAt = utcNow;
auditable.UpdatedAt = utcNow;
}
var foreignKeys = entry.Metadata.GetForeignKeys();
var referencingForeignKeys = entry.Metadata.GetReferencingForeignKeys();
});
I don't think it's possible to do that but I just got another idea, something like this. Create new function to do SaveChanges then load all.
In any class you create you like.
public IQueryable<T> CommitLoad<T>() where T : class
{
db.SaveChanges();
var list = db.Set<T>().AsQueryable();
var key = db.Model.FindEntityType(typeof(T)).FindPrimaryKey().Properties.FirstOrDefault();
var foreignkeys = key.GetContainingPrimaryKey().GetReferencingForeignKeys();
if (foreignkeys.Count() > 0)
{
foreach (var item in foreignkeys)
list = list.Include<T>(item.DeclaringEntityType.DisplayName());
}
return list;
}
Any class or page
public IQueryable<SlotOrder> GetTest()
{
//Save record to table
//After saving record, savechanges + load all
var list = CommitLoad<SlotOrder>();
return list;
}
Here is result screenshot

Querying array of strings by array of strings in elasticsearch.net

I'm using elasticsearch.net library in C# and I'm trying to query for objects matching specified filter.
I would like the query to return objects where at least one of input names from filter exists in object's Names collection.
The problem is that I always get 0 hits as result with this query, even tho I am certain that data matching specified filter does exist in the database and I would love to find out what's wrong with my query...
The model:
public class A
{
public int AId { get; set; }
public IEnumerable<string> Names { get; set; }
}
The filtering object:
public class Filter
{
public IEnumerable<string> NamesToSearch { get; set; }
}
The method for querying data:
public async Task<IEnumerable<A>> GetFilteredData(Filter filter)
{
var query = await _elasticClient.SearchAsync<A>(x => x.Query(q => q.Terms(a => a.Names, filter.NamesToSearch))
.Fields(a => a.AId, a => a.Names));
return query.Hits
.Select(x => new A
{
AId = x.Fields.FieldValues<A, int>(a => a.AId)[0]
})
.ToList();
}
I have also tried following query, but it didn't yield expected result neither:
var query = await _elasticClient.SearchAsync<A>(x => x.Query(q => q.Nested(n => n.Filter(f => f.Terms(y => y.Names, filter.NamesToSearch))))
.Fields(a => a.AId, a => a.Names));
SOLUTION WHICH WORKED FOR ME:
I have upgraded a bit code from SÅ‚awomir Rosiek's answer to actually compile using ElasticSearch.net 1.7.1 and be type-safe (no references to field name by string) and ended up with following extension method, which worked like a charm for my scenario:
public static QueryContainer MatchAnyTerm<T>(this QueryDescriptor<T> descriptor, Expression<Func<T, object>> field, object[] values) where T : class, new()
{
var queryContainer = new QueryContainer();
foreach (var value in values)
{
queryContainer |= descriptor.Term(t => t.OnField(field).Value(value));
}
return queryContainer;
}
and usage:
var query = await _elasticClient.SearchAsync<A>(x => x.Query(q =>
q.Bool(b =>
b.Should(s => s.MatchAnyTerm(a => a.Names, filter.NamesToSearch.ToArray()))
.Fields(a => a.AId, a => a.Names));
I think that your problem is that you tries to pass whole array to query. Instead of that you should treat that as OR expression.
Below is the raw query that you should use:
{
"query": {
"bool": {
"should": [
{ "term": {"names": "test" } },
{ "term": {"names": "xyz" } }
]
}
}
}
And that the C# code to achive that. First I have defined helper function:
private static QueryContainer TermAny<T>(QueryContainerDescriptor<T> descriptor, Field field, object[] values) where T : class
{
QueryContainer q = new QueryContainer();
foreach (var value in values)
{
q |= descriptor.Term(t => t.Field(field).Value(value));
}
return q;
}
And now the query:
string[] values = new[] { "test", "xyz" };
client.Search<A>(x => x.Query(
q => q.Bool(
b => b.Should(s => TermAny(s, "names", values)))));

Entity framework Include - Can we have it condition based .Include(p => includeFiles == true ? p.FileMasters : null)

Is it possible to have Include work based on function parameter as used in code below. By going throug MSDN documentation for Include, looks like it's not possible.
public static Response<Person> GetById(int id, bool includeAddress = false
, bool includeFiles = false
, bool includeTags = false)
{
var output = new Response<Person>();
using (var dbContext = new SmartDataContext())
{
dbContext.Configuration.ProxyCreationEnabled = false;
output.Entity = dbContext.EntityMasters.OfType<Person>()
.Include(p => includeFiles == true ? p.FileMasters : null)
.Include(p => includeAddress == true ? p.Addresses : null)
.Include(p => includeTags == true ? p.Tags : null)
.FirstOrDefault(e => e.EntityId == id);
}
return output;
}
Is there any trick to handle it directly or I have to build expression instead. I am not sure even how would I build an expression for this rather checking in dbContext. I am thinking if I can build expression before entering dbContext scope.
What I am looking for is to have all the conditions resolved before jumping into USING statement. In example below I creating an expression and using it inside USING
public static Response<IEnumerable<ConfigurationType>> GetByAttributeType(int attributeType)
{
Response<IEnumerable<ConfigurationType>> output = new Response<IEnumerable<ConfigurationType>>();
System.Linq.Expressions.Expression<System.Func<ConfigurationType, bool>> expressions=null;
switch (attributeType)
{
case 1:
expressions = a => a.IsItemAttribute == true;
break;
case 2:
expressions = a => a.IsReadPointAttribute == true;
break;
default:
expressions = a => a.IsPersonAttribute == true;
break;
}
using (var context = new SmartDataContext())
{
context.Configuration.ProxyCreationEnabled = false;
output.Entity = context.ConfigurationTypes.Where(expressions).ToList();
}
return output;
}
Similarly what I am expecting is something like this. This sounds weird, just trying to overthink may be if there is a way to resolve p somehow.
IQueryable<Person> query = includeFiles?Include(p=>p.Files):null; //p is undefined
query.Append(includeTags?Include(p=>p.Tags):null);
I am not sure if it's possible or not. If not, please help me understand the reason.
Build your query up piece by piece, calling Include conditionally. If you want to perform this work using logic outside of the using statement (and / or outside of the method), you could pass a type that encapsulates the logic for applying the necessary Include statements.
For example:
public static Response<Person> GetById(int id, IIncludeConfiguration<Person> includeConfiguration = null)
{
var output = new Response<Person>();
using (var dbContext = new SmartDataContext())
{
dbContext.Configuration.ProxyCreationEnabled = false;
var query = dbContext.EntityMasters.OfType<Person>();
if(includeConfiguration != null)
{
query = includeConfiguration.ApplyIncludes(query);
}
output.Entity = query.FirstOrDefault(e => e.EntityId == id);
}
return output;
}
public interface IIncludeConfiguration<TEntity> where TEntity : class;
{
IQueryable<TEntity> ApplyIncludes(IQueryable<TEntity> query)
}
public class PersonIncludeConfiguration : IIncludeConfiguration<Person>
{
public bool IncludeFiles {get;set;}
public bool IncludeAddresses {get;set;}
....
public IQueryable<Person> ApplyIncludes(IQueryable<Person> query)
{
if(IncludeFiles) query = query.Include(x => x.FileMasters);
if(IncludeAddresses) query = query.Include(x => x.Addresses);
....
return query;
}
GetById(1234, new PersonIncludeConfiguration{IncludeFiles = true});
You can't do it that way but you can always build up the IQuerable on your own. Something like:
var queryable = dbContext.EntityMasters.OfType<Person>();
if (includeFiles)
{
queryable = queryable.Include(p => p.FileMasters);
}

NHibernate extension for querying non mapped property

I'm looking for a way to get total price count from the Costs list in my object. I can't get Projections.Sum to work in my QueryOver so I tried another way but I'm having problems with it. I want to use a unmapped property in my QueryOver. I found this example but it's giving an error.
Object:
public class Participant
{
public int Id { get; set; }
public double TotalPersonalCosts { get { return Costs.Where(x => x.Code.Equals("Persoonlijk") && x.CostApprovalStatus == CostApprovalStatus.AdministratorApproved).Sum(x => x.Price.Amount); } }
public IList<Cost> Costs { get; set; }
}
The property TotalPersonalCosts is not mapped and contains the total price count.
Extension Class:
public static class ParticipantExtensions
{
private static string BuildPropertyName(string alias, string property)
{
if (!string.IsNullOrEmpty(alias))
{
return string.Format("{0}.{1}", alias, property);
}
return property;
}
public static IProjection ProcessTotalPersonalCosts(System.Linq.Expressions.Expression expr)
{
Expression<Func<Participant, double>> w = r => r.TotalPersonalCosts;
string aliasName = ExpressionProcessor.FindMemberExpression(expr);
string totalPersonalCostName = ExpressionProcessor.FindMemberExpression(w.Body);
PropertyProjection totalPersonalCostProjection =
Projections.Property(BuildPropertyName(aliasName, totalPersonalCostName));
return totalPersonalCostProjection;
}
}
My QueryOver:
public override PagedList<AccountantViewInfo> Execute()
{
ExpressionProcessor.RegisterCustomProjection(
() => default(Participant).TotalPersonalCosts,
expr => ParticipantExtensions.ProcessTotalPersonalCosts(expr.Expression));
AccountantViewInfo infoLine = null;
Trip tr = null;
Participant pa = null;
Cost c = null;
Price p = null;
var infoLines = Session.QueryOver(() => tr)
.JoinAlias(() => tr.Participants, () => pa);
if (_status == 0)
infoLines.Where(() => pa.TotalCostApprovalStatus == TotalCostApprovalStatus.CostPrinted || pa.TotalCostApprovalStatus == TotalCostApprovalStatus.CostPaid);
else if (_status == 1)
infoLines.Where(() => pa.TotalCostApprovalStatus == TotalCostApprovalStatus.CostPrinted);
else
infoLines.Where(() => pa.TotalCostApprovalStatus == TotalCostApprovalStatus.CostPaid);
infoLines.WhereRestrictionOn(() => pa.Employee.Id).IsIn(_employeeIds)
.Select(
Projections.Property("pa.Id").WithAlias(() => infoLine.Id),
Projections.Property("pa.Employee").WithAlias(() => infoLine.Employee),
Projections.Property("pa.ProjectCode").WithAlias(() => infoLine.ProjectCode),
Projections.Property("tr.Id").WithAlias(() => infoLine.TripId),
Projections.Property("tr.Destination").WithAlias(() => infoLine.Destination),
Projections.Property("tr.Period").WithAlias(() => infoLine.Period),
Projections.Property("pa.TotalPersonalCosts").WithAlias(() => infoLine.Period)
);
infoLines.TransformUsing(Transformers.AliasToBean<AccountantViewInfo>());
var count = infoLines.List<AccountantViewInfo>().Count();
var items = infoLines.List<AccountantViewInfo>().ToList().Skip((_myPage - 1) * _itemsPerPage).Take(_itemsPerPage).Distinct();
return new PagedList<AccountantViewInfo>
{
Items = items.ToList(),
Page = _myPage,
ResultsPerPage = _itemsPerPage,
TotalResults = count,
};
}
Here the .Expression property is not found from expr.
I don't know what I'm doing wrong. Any help or alternatives would be much appreciated!
Solution with Projection.Sum() thx to xanatos
.Select(
Projections.Group(() => pa.Id).WithAlias(() => infoLine.Id),
Projections.Group(() => pa.Employee).WithAlias(() => infoLine.Employee),
Projections.Group(() => pa.ProjectCode).WithAlias(() => infoLine.ProjectCode),
Projections.Group(() => tr.Id).WithAlias(() => infoLine.TripId),
Projections.Group(() => tr.Destination).WithAlias(() => infoLine.Destination),
Projections.Group(() => tr.Period).WithAlias(() => infoLine.Period),
Projections.Sum(() => c.Price.Amount).WithAlias(() => infoLine.TotalPersonalCost)
);
You can't use unmapped columns as projection columns of a NHibernate query.
And the way you are trying to do it is conceptually wrong: the ParticipantExtensions methods will be called BEFORE executing the query to the server, and their purpose is to modify the SQL query that will be executed. An IProjection (the "thing" that is returned by ProcessTotalPersonaCosts) is a something that will be put between the SELECT and the FROM in the query. The TotalCosts can't be returned by the SQL server because the SQL doesn't know about TotalCosts

Dynamic Include statements for eager loading in a query - EF 4.3.1

I have this method:
public CampaignCreative GetCampaignCreativeById(int id)
{
using (var db = GetContext())
{
return db.CampaignCreatives
.Include("Placement")
.Include("CreativeType")
.Include("Campaign")
.Include("Campaign.Handshake")
.Include("Campaign.Handshake.Agency")
.Include("Campaign.Product")
.AsNoTracking()
.Where(x => x.Id.Equals(id)).FirstOrDefault();
}
}
I would like to make the list of Includes dynamic. I tried:
public CampaignCreative GetCampaignCreativeById(int id, string[] includes)
{
using (var db = GetContext())
{
var query = db.CampaignCreatives;
foreach (string include in includes)
{
query = query.Include(include);
}
return query.AsNoTracking()
.Where(x => x.Id.Equals(id)).FirstOrDefault();
}
}
But it didn't compile. I got this error:
Cannot implicitly convert type 'System.Data.Entity.Infrastructure.DbQuery' to 'System.Data.Entity.DbSet'. An explicit conversion exists (are you missing a cast?)
Does anyone know how to make the list of Includes dynamic?
Thanks
I am more fond of the non-string expressive way of defining includes. Mainly because it doesn't rely on magic strings.
For the example code, it would look something like this:
public CampaignCreative GetCampaignCreativeById(int id) {
using (var db = GetContext()) {
return db.CampaignCreatives
.Include(cc => cc.Placement)
.Include(cc => cc.CreativeType)
.Include(cc => cc.Campaign.Select(c =>
c.Handshake.Select(h => h.Agency)))
.Include(cc => cc.Campaign.Select(c => c.Product)
.AsNoTracking()
.Where(x => x.Id.Equals(id))
.FirstOrDefault();
}
}
And to make those dynamic, this is how you do that:
public CampaignCreative GetCampaignCreativeById(
int id,
params Expression<Func<T, object>>[] includes
) {
using (var db = GetContext()) {
var query = db.CampaignCreatives;
return includes
.Aggregate(
query.AsQueryable(),
(current, include) => current.Include(include)
)
.FirstOrDefault(e => e.Id == id);
}
}
Which is used like this:
var c = dataService.GetCampaignCreativeById(
1,
cc => cc.Placement,
cc => cc.CreativeType,
cc => cc.Campaign.Select(c => c.Handshake.Select(h => h.Agency)),
cc => cc.Campaign.Select(c => c.Product
);
Make the query variable queryable:
public CampaignCreative GetCampaignCreativeById(int id, string[] includes)
{
using (var db = GetContext())
{
var query = db.CampaignCreatives.AsQueryable();
foreach (string include in includes)
{
query = query.Include(include);
}
return query
.AsNoTracking()
.Where(x => x.Id.Equals(id))
.FirstOrDefault();
}
}
Giving the compiler a hint by using IQueryable<CampaignCreative> instead of var will work too.
IQueryable<CampaignCreative> query = db.CampaignCreatives;
// or
DbQuery<CampaignCreative> query = db.CampaignCreatives;
When using var the compiler infers DbSet<T> for query which is more specific than the type returned by Include (which is DbQuery<T> (=base class of DbSet<T>) implementing IQueryable<T>), so you can't assign the result to the query variable anymore. Hence the compiler error on the query = query.Include(include) line.
I wrote this method to retrieve any set of entity dynamically based on their types. I used the IDbEntity interface to provide a valid key to search the userId in all the classes.
The Util.GetInverseProperties<T>() method is used to get the properties needed in the Include statement.
public IEnumerable<T> GetItems<T>(string userId) where T : class, IDbEntity
{
var query = db.Set<T>().Where(l => l.UserId==userId);
var props = Util.GetInverseProperties<T>();
foreach (var include in props)
query = query.Include(include.Name);
return query
.AsNoTracking()
.ToList();
}
public interface IDbEntity
{
public string UserId { get; set; }
}
public static List<PropertyInfo> GetInverseProperties<T>()
{
return typeof(T)
.GetProperties()
.Where(p => Attribute.IsDefined(p, typeof(InversePropertyAttribute)))
.ToList();
}

Categories