Entity Framework generic expression to selectively hide property - c#

I would like to selectively ignore a property from a table.
I have an API which exposes the following methods.
public interface IReadService
{
FullDTO Get();
HeaderDTO[] GetList();
}
My data structure looks like so:
public ServiceDTO : ServiceHeaderDTO
{
public string LargeXMLData { get; set; }
}
public ServiceHeaderDTO
{
public int Id { get; set; }
public string Description { get; set; }
//.... Other properties
}
I have a few services which have similar issues, So I would like to be able to ignore the XML property in some cases, so I'm not using extra time to send a large string property which will be ignored.
Normally you might write something like this to hide a property
var entities = context.Services.Select(x =>
new Service { Id = Id, Description = Description, LargeXMLData = "" }).ToArray();
var dtos = this.AdaptToDTO(entities);
Now this would be fine if I had to do this in a single service, but when you have 20 services duplicating the logic it gets annoying.
I would like the be able to just say:
var entities = context.Services.Excluding(x => x.LargeXMLData).ToArray();
var dtos = this.AdaptToHeaderDTO(entities);
Edit: I'm not using automapper. Alot of our code has mappings which cannot translate to expressions. I do not want to have to specify maps
Is there a simple way I can exclude a property from a query? Without having to manually build maps.
Preferably a way which uses the existing mappings internal to EF which maps the entity to the db object

Normally you might write something like this to hide a property
var entities = context.Services.Select(x =>
new Service { Id = Id, Description = Description, LargeXMLData = "" })
If you can do that manually, it should be doable automatically using the exact same concept, with little reflection and Expression APIs.
But note that this woult work only for EF Core, since EF6 does not support projecting to entity types, like new Service { ... } here, and projecting to dynamic types at runtime is not trivial and also will break the DTO mapping.
With that being said, following is a sample implementation of the aforementioned concept:
public static partial class QueryableExtensions
{
public static IQueryable<T> Excluding<T>(this IQueryable<T> source, params Expression<Func<T, object>>[] excludeProperties)
{
var excludeMembers = excludeProperties
.Select(p => ExtractMember(p.Body).Name)
.ToList();
if (excludeMembers.Count == 0) return source;
// Build selector like (T e) => new T { Prop1 = e.Prop1, Prop2 = e.Prop2, ... }
// for each public property of T not included in the excludeMembers list,
// which then will be used as argument for LINQ Select
var parameter = Expression.Parameter(typeof(T), "e");
var bindings = typeof(T).GetProperties()
.Where(p => p.CanWrite && !excludeMembers.Contains(p.Name))
.Select(p => Expression.Bind(p, Expression.MakeMemberAccess(parameter, p)));
var body = Expression.MemberInit(Expression.New(typeof(T)), bindings);
var selector = Expression.Lambda<Func<T, T>>(body, parameter);
return source.Select(selector);
}
static MemberInfo ExtractMember(Expression source)
{
// Remove Convert if present (for value type properties cast to object)
if (source.NodeType == ExpressionType.Convert)
source = ((UnaryExpression)source).Operand;
return ((MemberExpression)source).Member;
}
}
The usage would be exactly as desired:
var entities = context.Services.Excluding(x => x.LargeXMLData).ToArray();
The problem with this though is that it will automatically "include" navigation properties and/or unmapped properties.
So it would be better to use EF model metadata instead of reflection. The problem is that currently EF Core does not provide a good public way of plugging into their infrastructure, or to get access to DbContext (thus Model) from IQueryble, so it has to be passed as argument to the custom method:
public static IQueryable<T> Excluding<T>(this IQueryable<T> source, DbContext context, params Expression<Func<T, object>>[] excludeProperties)
{
var excludeMembers = excludeProperties
.Select(p => ExtractMember(p.Body).Name)
.ToList();
if (excludeMembers.Count == 0) return source;
// Build selector like (T e) => new T { Prop1 = e.Prop1, Prop2 = e.Prop2, ... }
// for each property of T not included in the excludeMembers list,
// which then will be used as argument for LINQ Select
var parameter = Expression.Parameter(typeof(T), "e");
var bindings = context.Model.FindEntityType(typeof(T)).GetProperties()
.Where(p => p.PropertyInfo != null && !excludeMembers.Contains(p.Name))
.Select(p => Expression.Bind(p.PropertyInfo, Expression.MakeMemberAccess(parameter, p.PropertyInfo)));
var body = Expression.MemberInit(Expression.New(typeof(T)), bindings);
var selector = Expression.Lambda<Func<T, T>>(body, parameter);
return source.Select(selector);
}
which makes the usage not so elegant (but doing the job):
var entities = context.Services.Excluding(context, x => x.LargeXMLData).ToArray();
Now the only remaining potential problem are shadow properties, but they cannot be handled with projection, so this technique simply cannot be used for entities with shadow properties.
Finally, the pure EF Core alternative of the above is to put the LargeXMLData into separate single property "entity" and use table splitting to map it to the same table. Then you can use the regular Include method to include it where needed (by default it would be excluded).

I needed to double-check this before answering, but are you using Automapper or some other mapping provider for the ProjectTo implementation? Automapper's ProjectTo extension method requires a mapper configuration, so it may be that your mapping implementation is materializing the entities prematurely.
With Automapper, your example projecting to a DTO that does not contain the large XML field would result in a query to the database that does not return the large XML without needing any new "Exclude" method.
For instance, if I were to use:
var config = new MappingConfiguration<Service, ServiceHeaderDTO>();
var services = context.Services
.ProjectTo<ServiceHeaderDTO>(config)
.ToList();
The resulting SQL would not return the XMLData because ServiceHeaderDTO does not request it.
It is equivalent to doing:
var services = context.Services
.Select(x => new ServiceHeaderDTO
{
ServiceId = x.ServiceId,
// ... remaining fields, excluding the XML Data
}).ToList();
As long as I don't reference x.LargeXMLData, it will not be returned by my resulting query. Where you can run into big data coming back is if something like the following happens behind the scenes:
var services = context.Services
.ToList()
.Select(x => new ServiceHeaderDTO
{
ServiceId = x.ServiceId,
// ... remaining fields, excluding the XML Data
}).ToList();
That extra .ToList() call will materialize the complete Service entity to memory including the XMLData field. Now Automapper's ProjectTo does not work against IEnumerable, only IQueryable so it is unlikely that any query fed to it was doing this, but if you are using a home-grown mapping implementation where ProjectTo is materializing the entities before mapping, then I would strongly recommend using Automapper as it's IQueryable implementation avoids this problem for you automatically.
Edit: Tested with EF Core and Automapper just in case the behaviour changed, but it also excludes anything not referenced in the mapped DTO.

Related

Entity Framework Core Invoke an Expression on Navigation property

Tried to search a lot for this but couldn't come up with an answer that works. Here's what I am trying to do:
I have Entity ObjectA that has a Navigation property ObjectB (not a Collection type, it's a virtual property that gets loaded via lazy loading). When I do a Where query for A, I want to also expand property B in the expression using another Expression that maps B.
Here's some code to demonstrate, question is in the ToObjectADto() function
public static Expression<Func<ObjectB, ObjectBDto>> ToObjectBDto()
{
return b => new ObjectBDto
{
Prop1 = b.Prop1,
Prop2 = b.Prop2;
};
}
public static Expression<Func<ObjectA, ObjectADto>> ToObjectADto()
{
return a => new ObjectADto
{
Name = a.Name,
SomeProperty = a.SomeProperty,
ObjectB = /* How can I call the ToObjectBDto Expression here without re-writing it? */
};
}
var aDto = _dbContext.ObjectAs.Where(q => q.SomeProperty > 0).Select(ToObjectADto());
I tried to create a compiled Expression:
private static _toBDtoCompiled = ToObjectBDto().Compile();
and then call it in ToObjectADto() like below but I get the API Error There is already an open DataReader associated error because it's doing it client side.
public static Expression<Func<ObjectA, ObjectADto>> ToObjectADto()
{
return a => new ObjectADto
{
Name = a.Name,
SomeProperty = a.SomeProperty,
ObjectB = _toBDto().Invoke(a.ObjectB)
};
}
My recommendation would be to save yourself the work and leverage AutoMapper. The benefit here is that Automapper can feed off EF's IQueryable implementation via ProjectTo to build queries and populate DTO graphs.
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<ObjectA, ObjectADto>();
cfg.CreateMap<ObjectB, ObjectBDto>();
});
var aDto = _dbContext.ObjectAs.Where(q => q.SomeProperty > 0).ProjectTo<ObjectADto>(config);
Any particular mapping that cannot be inferred between Object and DTO can be set up in the mapping. The benefit of ProjectTo vs. custom mapping is that it will build a related query without tripping out lazy load hits or risking triggering code that EF cannot translate into SQL. (One query to populate all relevant DTOs)
Automapper can assist with copying values from a DTO back into either a new entity or update an existing entity:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<NewObjectADto, ObjectA>();
cfg.CreateMap<UpdateObjectADto, ObjectA>();
});
var mapper = config.CreateMapper();
New..
var objectA = mapper.Map<ObjectA>(dto);
_dbContext.ObjectAs.Add(objectA);
... or Update existing.
var objectA = _dbContext.ObjectAs.Single(x => x.ObjectAId == dto.ObjectAId);
mapper.Map(objectA, dto);
Where the DTO reflects either the data needed to create a new object, or the data that the client is allowed to update for updating the existing object. The goal being to keep update/add/delete operations as atomic as possible vs. passing large complex objects /w relatives to be updated all at once. I.e. actions like "AddObjectBToA" "RemoveObjectBFromA" etc. rather than resolving all operations via a single "UpdateObjectA".
It's a pity that C# doesn't handle compiling a lambda into an expression, where one expression calls another. Particularly since an expression tree can represent this case. But then EF Core 3 or higher won't look through invoke expressions anyway.
Automapper is probably easier. But if you don't want to use 3rd party code, you'll have to inline the expression yourself. Including replacing any ParameterExpression with the argument to the method.
public static R Invoke<T, R>(this Expression<Func<T, R>> expression, T argument) => throw new NotImplementedException();
// etc for expressions with more parameters
public class InlineVisitor : ExpressionVisitor {
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.Name == "Invoke"
&& node.Object == null
&& node.Arguments.Count >= 1
&& node.Arguments[0] is LambdaExpression expr)
return Visit(
new ReplacingExpressionVisitor(
expr.Parameters.ToArray(),
node.Arguments.Skip(1).ToArray())
.Visit(expr.Body)
);
return base.VisitMethodCall(node);
}
}
// usage;
public static Expression<Func<ObjectA, ObjectADto>> ToObjectADto()
{
var ToBorNotToB = ToObjectBDto();
Expression<Func<ObjectA, ObjectADto>> expr = a => new ObjectADto
{
Name = a.Name,
SomeProperty = a.SomeProperty,
ObjectB = ToBorNotToB.Invoke(a.ObjectB)
};
return new InlineVisitor().VisitAndConvert(expr), "");
}

EF Core: Detached lazy-loading navigation property

I have the following query:
var query = _context.QuestOrders.Include(a => a.Driver).ThenInclude(i => i.DlStateProvince)
.Where(p => carrierIds.Contains(p.Driver.CarrierId))
....
;
and then try to call the following:
var queryDto = query.AsNoTracking().ProjectTo<DcReportDonorResultDto>(_mapperConfiguration);
var reports = new PagedList<DcReportDonorResultDto>(queryDto, pageIndex, pageSize);
where DcReportDonorResultDto has a property:
public string PrimaryId { get; set; }
which mapped the following:
CreateMap<QuestOrder, DcReportDonorResultDto>()
.ForMember(destinationMember => destinationMember.PrimaryId, opt => opt.MapFrom(src => src.Driver.PrimaryId))
and PrimaryId is defined in QuestOrder as:
public string PrimaryId
{
get
{
if (!string.IsNullOrWhiteSpace(DlNumber) && DlStateProvinceId.HasValue)
return DlStateProvince.Abbreviation + DlNumber.Replace("-", "");
else
return string.Empty;
}
}
I received the following error:
System.InvalidOperationException: 'Error generated for warning
'Microsoft.EntityFrameworkCore.Infrastructure.DetachedLazyLoadingWarning:
An attempt was made to lazy-load navigation property 'DlStateProvince'
on detached entity of type ''. Lazy-loading is not supported for
detached entities or entities that are loaded with 'AsNoTracking()'.'.
This exception can be suppressed or logged by passing event ID
'CoreEventId.DetachedLazyLoadingWarning' to the 'ConfigureWarnings'
method in 'DbContext.OnConfiguring' or 'AddDbContext'.'
How to solve this problem?
The problem is caused by the computed QuestOrder.PrimaryId property.
When used in LINQ to Entities queries, such properties cannot be translated to SQL and require client evaluation. And even when supported, client evaluation does not play well when accessing navigation properties inside - both eager or lazy loading do not function properly and lead to either runtime exceptions or wrong return value.
So the best is to make them translatable, which requires dealing with translatable expressions.
In all the cases, start by converting the computed property body from block to conditional operator (to make it translatable):
public string PrimaryId =>
!string.IsNullOrWhiteSpace(this.DlNumber) && this.DlStateProvinceId.HasValue) ?
this.DlStateProvince.Abbreviation + this.DlNumber.Replace("-", "") :
string.Empty;
Now, short turn, quick-and-dirty solution is to extract the actual expression for the computed property, copy/paste it in the mapping and replace this with src.Driver:
.ForMember(dst => dst.PrimaryId, opt => opt.MapFrom(src =>
//src.Driver.PrimaryId
!string.IsNullOrWhiteSpace(src.Driver.DlNumber) && src.Driver.DlStateProvinceId.HasValue) ?
src.Driver.DlStateProvince.Abbreviation + src.Driver.DlNumber.Replace("-", "") :
string.Empty
))
Long term, or if you have many properties like this, or you need to use it in other mappings/queries, or just because of the code duplication, this is not a good solution. You need a way to replace the computed property accessor inside query expression tree with the corresponding expression extracted from the body.
Neither C#, nor BCL or EF Core help in that regard. Several 3rd party packages are trying to address this problem to some sort of degree - LinqKit, NeinLinq etc., but there is a little not very well known gem called DelegateDecompiler which does that with minimum code changes.
All you need is to install the DelegateDecompiler or DelegateDecompiler.EntityFrameworkCore package, mark your computed properties with [Computed] attribute
[Computed] // <--
public string PrimaryId =>
!string.IsNullOrWhiteSpace(this.DlNumber) && this.DlStateProvinceId.HasValue) ?
this.DlStateProvince.Abbreviation + this.DlNumber.Replace("-", "") :
string.Empty;
and then call Decompile (or DecompileAsync) on the top level queryable
var queryDto = query.AsNoTracking()
.ProjectTo<DcReportDonorResultDto>(_mapperConfiguration)
.Decompile(); // <--
No special mappings are needed for AutoMapper, e.g. you can keep the usual
.ForMember(dst => dst.PrimaryId, opt => opt.MapFrom(src => src.Driver.PrimaryId)
For AutoMapper projection queries (produced with ProjectTo) you can even eliminate the need for calling Decompile / DecompileAsync by providing the following little `"bridge" between the two libraries:
namespace AutoMapper
{
using DelegateDecompiler;
using QueryableExtensions;
public static class AutoMapperExtensions
{
public static IMapperConfigurationExpression UseDecompiler(this IMapperConfigurationExpression config)
{
var resultConverters = config.Advanced.QueryableResultConverters;
for (int i = 0; i < resultConverters.Count; i++)
{
if (!(resultConverters[i] is ExpressionResultDecompiler))
resultConverters[i] = new ExpressionResultDecompiler(resultConverters[i]);
}
return config;
}
class ExpressionResultDecompiler : IExpressionResultConverter
{
IExpressionResultConverter baseConverter;
public ExpressionResultDecompiler(IExpressionResultConverter baseConverter) => this.baseConverter = baseConverter;
public bool CanGetExpressionResolutionResult(ExpressionResolutionResult expressionResolutionResult, PropertyMap propertyMap) => baseConverter.CanGetExpressionResolutionResult(expressionResolutionResult, propertyMap);
public bool CanGetExpressionResolutionResult(ExpressionResolutionResult expressionResolutionResult, ConstructorParameterMap propertyMap) => baseConverter.CanGetExpressionResolutionResult(expressionResolutionResult, propertyMap);
public ExpressionResolutionResult GetExpressionResolutionResult(ExpressionResolutionResult expressionResolutionResult, PropertyMap propertyMap, LetPropertyMaps letPropertyMaps) => Decompile(baseConverter.GetExpressionResolutionResult(expressionResolutionResult, propertyMap, letPropertyMaps));
public ExpressionResolutionResult GetExpressionResolutionResult(ExpressionResolutionResult expressionResolutionResult, ConstructorParameterMap propertyMap) => Decompile(baseConverter.GetExpressionResolutionResult(expressionResolutionResult, propertyMap));
static ExpressionResolutionResult Decompile(ExpressionResolutionResult result)
{
var decompiled = DecompileExpressionVisitor.Decompile(result.ResolutionExpression);
if (decompiled != result.ResolutionExpression)
result = new ExpressionResolutionResult(decompiled, result.Type);
return result;
}
}
}
}
and just call UseDecompiler() during AutoMapper initialization, for instance
var mapperConfig = new MapperConfiguration(config =>
{
config.UseDecompiler(); // <--
// the rest (add profiles, create maps etc.) ...
});

How can I load Child entities dynamically in Entity Framework 6

I have a Provider class and an Article one. Article has a int ProviderId{get;set} and a public virtual Provider Provider {get;set;} properties.
I know about Lazy loading and why I can't access the Provider property in Article outside the context but I have a generic method that returns the next T like this:
public static T Next<T>(T currentElement) where T : class, IModel {
T data;
if (currentElement.Id >= GetLastId<T>())
return currentElement;
using (DatabaseEntities context = new DatabaseEntities()) {
data = context.Set<T>().Single(el => el.Id == currentElement.Id + 1);
}
return data;
}
But I can't retrieve Child entities like Provider in Articles Class. How can I include all entities? Should I update the method and make one per entity?
I read about Eager loading and Explicit loading but I don't know how can I implement these in my method.
Note:
Not all my entities have entity children and I have more methods like Previous<T>(), First<T>() or Last<T>() that do the work you expect.
You could create an overload of your Next method that accepts an Expression<Func<T, object>>:
public static T Next<T>(T currentElement, Expression<Func<T, object>> navigationProperty) where T : class, IModel
{
T data;
if (currentElement.Id >= GetLastId<T>())
return currentElement;
using (DatabaseEntities context = new DatabaseEntities())
{
IQueryable<T> dbQuery = context.Set<T>();
if (navigationProperty != null)
dbQuery = dbQuery.Include<T, object>(navigationProperty);
data = dbQuery.AsNoTracking().Single(el => el.Id == currentElement.Id + 1);
}
return data;
}
Usage:
var entity = Next(instance, x => x.Provider);
Please refer to the following blog post for more information and a complete example.
Implementing a generic data access layer using Entity Framework: https://blog.magnusmontin.net/2013/05/30/generic-dal-using-entity-framework/
You could extend your method to take a list of possible includes. For example:
public static T Next<T>(T currentElement, params Expression<Func<T, object>>[] includes)
where T : class, IModel
{
if (currentElement.Id >= GetLastId<T>())
return currentElement;
using (DatabaseEntities context = new DatabaseEntities())
{
IQueryable<T> query = context.Set<T>();
//Deferred execution allows us to build up the query
foreach (var include in includes)
{
query = query.Include(include);
}
return query.Single(el => el.Id == currentElement.Id + 1);
}
}
And use it like this:
var someEntity = ...;
var nextEntity = Next(someEntity,
e => e.Childcollection1,
e => e.Childcollection2,
//
e => e.ChildcollectionN)
As an additional point, to get your next entity, you shouldn't rely on the ID values being sequential, for example, they will get out of sequence if entries are deleted. Instead, consider something like this:
return query.OrderBy(el => el.Id).First(el => el.Id > currentElement.Id);
So you have a one-to-many relation between Provider and Article: Every Provider has zero or more Articles, and every Article belongs to exactly one Provider.
If you have configured this one-to-many relationship correctly in Entity Framework, a Provider should have a reference to its many Articles:
public virtual ICollection<Article> Articles{get; set;}
Furthermore you have a function Next, that takes as input an object of class T, which is a class that implements IModel. Next returns the next element from the DbSet of Ts, assuming you have a proper definition for the next element.
Apparently you want to adapt function Next such that you can use this function to get the next Provider with all its Articles.
More generic: if T is a type that has properly designed one-to-many relationships in it, and you have an object of type T, you want the T with the first Id higher than the Id of the current T, inclusive all its ICollections.
I've divided this into sever smaller functions:
A function that, given a type T returns all properties that implement ICollection
A function that, given a DbSet returns all elements of the DbSet inclusive all its ICollections
Given those two, and functions like OrderBy and FirstOrDefault you will be able to get the first Provider with Id larger than current provider with all its Articles.
static class QueryableExtensions
{
// returns true if PropertyInfo implements ICollection<...>
public static bool ImplementsICollection(this PropertyInfo propertyInfo)
{
return propertyInfo.IsGenericType
&& propertyInfo.GetGenericTypeDefinition() == typeof(ICollection<>);
}
// returns all readable & writable PropertyInfos of type T that implement ICollection<...>
public static IEnumerable<PropertyInfo> CollectionProperties<T>()
{
return typeof(T).GetProperties()
.Where(prop => prop.CanRead
&& prop.CanWrite
&& prop.PropertyType.ImplementICollection();
}
// given an IQueryable<T>, adds the Include of all ICollection<...> T has
public static IQueryable<T> IncludeICollection<T>(IQueryable<T> query)
{
var iCollectionProperties = CollectionProperties<T>();
foreach (var collectionProperty in collectionProperties)
{
query = query.Include(prop.Name);
}
return query;
}
// given a IQueryable<T> and a T return the first T in the query with Id higher than T
// for this we need to be sure every T has an IComparable property Id
// so T should implement interface IId (see below)
public T Next<T>(this IQueryable<T> query, T currentItem)
where T : IId // makes sure every T has aproperty Id
{
T nextElement = query
// keep only those DbSet items that have larger Ids:
.Where(item => currentItem.Id < item.Id)
// sort in ascending Id:
.OrderBy(item => item.Id
// keep only the first element of this sorted collection:
.FirstOrDefault();
return T
}
}
I need to be sure that every T has an Id, otherwise you can't get the next Id. Probably you have this in your IModel interface:
public Interface IId
{
public int Id {get;}
}
After these functions your query will be like:
public static T Next<T>(T currentElement) where T : class, IModel, IId
{
T data;
if (currentElement.Id >= GetLastId<T>())
return currentElement;
using (DatabaseEntities context = new DatabaseEntities())
{
return context.Set<T>().Next(currentElement);
}
}
I'm not looking to rehash an answer, but here is a function that combines a little bit of reflection to automagically load all foreign key related data based on model declarations. I've written it as a way to load an object by something other than its primary key, but it can be modified to do so if needed.
private IQueryable<T> GetFullSet()
{
IEnumerable<IEntityType> entities = _dbContext.Model.GetEntityTypes();
IEntityType thisEntity = entities.SingleOrDefault(x => x.ClrType.Name == typeof(T).Name);
IQueryable<T> set = _dbContext.Set<T>();
foreach (IForeignKey fKey in thisEntity.GetReferencingForeignKeys())
{
set = set.Include(fKey.PrincipalToDependent.Name);
}
return set;
}

Projection of single entities in EF with extension methods

I like to do projection from my entity models into my view models using extension methods. This means I dont over/under fetch for my models and it makes the code nice and readable. It makes sense that sometimes the projections may include nested models, and I want to get reuse on those sub-projections.
I want to be able to do something like the following:
ctx.People.FiltersAndThings().ToViewModels();//the project my DB Models into view models
Extension methods for actual projection
public static IQueryable<PersonModel> ToViewModels(this IQueryable<Person> entities)
{
return entities.Select(x => new PersonModel {
Me = x.Me.ToViewModel(), //this method cannot be translated into a store expression
Friends = x.Friends.AsQueryable().ToViewModels() //works fine with some magic (tm)
});
}
public static IQueryable<ProfileModel> ToViewModels(this IQueryable<Profile> entities)
{
return entities.Select(x => new ProfileModel { Name = x.Name });
}
public static ProfileModel ToViewModel(this Profile entity)
{
return new ProfileModel { Name = entity.Name };
}
When using a Queryable (eg Friends = x.Friends.AsQueryable().ToViewModels()) we can use some magic to flatten this to an expression (see https://stackoverflow.com/a/10726256/1070291, answer by #LordTerabyte) But when we are doing an assignment with a new clause (eg Me = new ProfileModel { Name = x.Me.Name }) its not an expression so if we bundle this under an extension method (eg Me = x.Me.ToViewModel()) we can't flatten this to an expression.
How does an assignment to a new object work under the scenes in EF?
Is there a way to do convertion to a new object via an extension method?
Full demo code here: https://github.com/lukemcgregor/ExtensionMethodProjection
Edit:
I now have a blog post (Composable Repositories - Nesting Extensions) and nuget package to help with nesting extension methods in linq
Take a look at this answer. It does a very similar thing what you want. Basically you would define your transformation as an Expression tree, e.g:
public static Expression<Func<Profile, ProfileModel>> ToProfileViewModel()
{
return entity => new ProfileModel { Name = entity.Name };
}
And then do invocations of this (e.g. ExpressionsHelper.ToProfileViewModel().AsQuote()(p)).
If you prefer, you can modify the visitors, to allow a nicer syntax. Something along the lines:
[ReplacementInExpressionTrees(MethodName=nameof(ExpressionsHelper.ToProfileViewModel))]
public static ProfileModel ToViewModel(this Profile profile)
{
// this implementation is only here, so that if you call the method in a non expression tree, it will still work
return ExpressionsHelper.ToProfileViewModel().Compile()(profile); // tip: cache the compiled func!
Now you need to create a visitor, that checks all method calls, and when finds a method with this attribute, it changes the whole call to ExpressionsHelper.ToProfileViewModel().AsQuote()(profile). This is as an exercise for you :)
}

How can I declare IQueryable<T> when T is unknown at compile time

I'm creating a generic method to call a repository load method to retrieve database records from a table defined by class T where T is unknown until run time.
Additionally, the code below makes use of the known type BLL.Entities.Unit but I need to change this to use the modelType shown in the GetData method. I couldn't figure out how to format the param2 parameter without using the Unit directly.
Here's the code in my method:
public JsonResult GetData(string modelTypeString)
{
Type modelType = Type.GetType(modelTypeString);
MethodInfo generic = typeof(Repository).GetMethod("Load");
MethodInfo genericLoad = generic.MakeGenericMethod(modelType);
System.Linq.Expressions.Expression<System.Func<BLL.Entities.Unit, object>>[] param2 =
new System.Linq.Expressions.Expression<System.Func<BLL.Entities.Unit, object>>[1];
var returnObjects = genericLoad.Invoke(Repository, param2);
}
And here is the Repository Load method:
public IQueryable<T> Load<T>(params Expression<Func<T, object>>[] includeProperties) where T : class
{
IQueryable<T> query = entities.Set<T>();
if (includeProperties != null)
{
foreach (var includeProperty in includeProperties)
{
query = query.Include(includeProperty);
}
}
return query;
}
This code compiles and executes, but I can't do any queryable actions on the returnObjects. I need to do something like:
returnObjects.Select(u => new UnitViewModel
{
Id = u.Id,
Name = u.Name,
Description = u.Description,
DDLocationId = u.LocationId
})
.ToList();
Using your repository and map all the objects with AutoMapper is not a solution for you?
What you want is to turn your EntityFramework object into DTO.
I do not know anathing excepted automapper that can allow you to do that without any c# reflexion.
EDIT: And I think your question should be, How to I turn my EF object into DTO.
I was able to solve this issue with one line of code:
returnObjects.GetEnumerator();
This forces a retrieval of data from the database. After that it's just a matter of using additional reflection code to get at the individual field properties.

Categories

Resources