I have a DataSet populated during an ADO.NET call which I want to then use AutoMapper to convert to a DTO.
I have defined a generic-use mapper for many of our DTO types:
IMapper DataReaderMapper = new MapperConfiguration(cfg => {
cfg.AddDataReaderMapping();
cfg.CreateMap<IDataReader, MyApp>();
cfg.CreateMap<IDataReader, MyRole>();
cfg.CreateMap<IDataReader, MyBill>();
}).CreateMapper();
This works great when mapping for all of these DTO types:
var apps = DataReaderMapper.Map<IList<AppDTO>>(dataSet.Tables[0].CreateDataReader());
However, I have now added this mapping:
cfg.CreateMap<IDataReader, Money>();
However, this Money type contains two float properties which appear to be giving AutoMapper some issues, with this exception:
Error mapping types.
Mapping types: IDataReader -> Money System.Data.IDataReader ->
Common.Models.Money
Type Map configuration: IDataReader -> Money System.Data.IDataReader
-> Common.Models.Money
Destination Member:
Amount
Which contains this InnerException:
Specified cast is not valid.
I have tried specifying a custom value mapping:
cfg.CreateMap<IDataReader, Money>().ForMember(x => x.Amount, opt => opt.MapFrom(rdr => Convert.ToDouble(rdr["Amount"])));
But this is not even changing the exception.
How can I tell AutoMapper that this Money type should have its float Amount property populated by converting from the SQL field float Amount?
Thanks to #lucian-bargaoanu for the link. This prompted me to take another look at the documentation which resulted in providing a resolver.
I have now fleshed out the mappings with some custom resolvers:
IMapper DataReaderMapper = new MapperConfiguration(cfg => {
cfg.AddDataReaderMapping();
cfg.CreateMap<IDataReader, MyApp>();
cfg.CreateMap<IDataReader, MyRole>();
cfg.CreateMap<IDataReader, MyBill>();
cfg.CreateMap<IDataReader, Money>()
.ForMember(dest => dest.Amount, opt => opt.MapFrom<MoneyAmountResolver>())
.ForMember(dest => dest.SubTotal, opt => opt.MapFrom<MoneySubTotalResolver>());
}).CreateMapper();
The resolvers are just small classes:
public class MoneyAmountResolver: IValueResolver<IDataReader, Money, float>
{
public float Resolve(IDataReader source, Moneydestination, float member, ResolutionContext context)
{
return (float)Convert.ToDouble(source["Amount"]);
}
}
public class MoneySubTotalResolver: IValueResolver<IDataReader, Money, float>
{
public float Resolve(IDataReader source, Moneydestination, float member, ResolutionContext context)
{
return (float)Convert.ToDouble(source["SubTotal"]);
}
}
Related
On saving changes to the database, we want to update our shadow properties (CreatedOn & ModifiedOn) automatically. This can be done by using overriding SaveChangesAsync method in the DbContext class.
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
ChangeTracker.DetectChanges();
var timestamp = systemClock.UtcNow.DateTime;
foreach (var entry in ChangeTracker.Entries()
.Where(e => e.Entity is BaseIdentifierEntity)
.Where(e => e.State == EntityState.Added || e.State == EntityState.Modified))
{
if (entry.State == EntityState.Added)
{
entry.Property(nameof(BaseIdentifierEntity.CreatedOn)).CurrentValue = timestamp;
}
if (entry.State == EntityState.Modified)
{
entry.Property(nameof(BaseIdentifierEntity.ModifiedOn)).CurrentValue = timestamp;
}
};
return base.SaveChangesAsync(cancellationToken);
}
Now we want to use the ExecuteUpdateAsync EF code method to update records in bulk but those changes are not detected by the change tracker.
Eg.
await context.Invoices
.Where(_ => _.Status == InvoiceStatusEnum.Draft)
.ExecuteUpdateAsync(_ => _.SetProperty(invoice => invoice.Status, InvoiceStatusEnum.Approved), cancellationToken);
One possible solution we're thinking about, is having a ExecuteUpdateWithShadowPropertiesAsync method but we don't succeed to merge the 2 expressions into one.
public static class EntityFrameworkExtensions
{
public static Task<int> ExecuteUpdateWithShadowPropertiesAsync<TSource>(this IQueryable<TSource> source, Expression<Func<SetPropertyCalls<TSource>, SetPropertyCalls<TSource>>> setPropertyCalls, CancellationToken cancellationToken = default) where TSource : BaseIdentifierEntity
{
Expression<Func<SetPropertyCalls<TSource>, SetPropertyCalls<TSource>>> setShadowPropertyCalls = _ => _.SetProperty(p => p.ModifiedOn, DateTime.UtcNow);
// TODO: A method to combine both expressions into one expression
var mergedPropertyCalls = Merge(setPropertyCalls, setShadowPropertyCalls);
return source.ExecuteUpdateAsync(mergedPropertyCalls, cancellationToken: cancellationToken);
}
}
Actually there are two or three questions here, so let handle them separately.
How to update shadow properties with Entity Framework Core ExecuteUpdate method?
Shadow properties inside any EF Core query expression tree are accessed through EF.Property method, which is EF Core generic property accessor expression and works for both shadow and regular properties.
So if your ModifiedOn was a shadow property (it isn't) of type DateTime, it can be updated as follows:
query.ExecuteUpdateAsync(s => s
.SetProperty(p => EF.Property<DateTime>("ModifiedOn"), DateTime.UtcNow)
...);
How to combine lambda expressions?
This has been covered by many answers on SO, or over internet. But basically you need to emulate "call" to one of the expressions passing the other as argument. This is achieved with either Expression.Invoke which is not always supported by query translators (including EF Core), or (which always works) by replacing the parameter of the "called" lambda expression with the body of the other lambda expression.
The later is achieved with custom ExpressionVisitor. You can find many implementations, EF Core also provides its own called ParameterReplacingVisitor, but I'm using my own little expression helper class, which is general and have no EF Core or other 3rd party dependencies. It is quite simple:
namespace System.Linq.Expressions;
public static class ExpressionUtils
{
public static Expression ReplaceBodyParameter<T, TResult>(this Expression<Func<T, TResult>> source, Expression value)
=> source.Body.ReplaceParameter(source.Parameters[0], value);
public static Expression ReplaceParameter(this Expression source, ParameterExpression target, Expression replacement)
=> new ParameterReplacer(target, replacement).Visit(source);
class ParameterReplacer : ExpressionVisitor
{
readonly ParameterExpression target;
readonly Expression replacement;
public ParameterReplacer(ParameterExpression target, Expression replacement)
=> (this.target, this.replacement) = (target, replacement);
protected override Expression VisitParameter(ParameterExpression node)
=> node == target ? replacement : node;
}
}
With that helper, the method you are looking for would be:
// TODO: A method to combine both expressions into one expression
var mergedPropertyCalls = Expression.Lambda<Func<SetPropertyCalls<TSource>, SetPropertyCalls<TSource>>>(
setShadowPropertyCalls.ReplaceBodyParameter(setPropertyCalls.Body),
setPropertyCalls.Parameters);
You can go further and add a shortcut helper method specific for SetPropertyCalls:
public static Expression<Func<SetPropertyCalls<TSource>, SetPropertyCalls<TSource>>> Append<TSource>(
this Expression<Func<SetPropertyCalls<TSource>, SetPropertyCalls<TSource>>> target,
Expression<Func<SetPropertyCalls<TSource>, SetPropertyCalls<TSource>>> source)
where TSource : class
=> Expression.Lambda<Func<SetPropertyCalls<TSource>, SetPropertyCalls<TSource>>>(
source.ReplaceBodyParameter(target.Body), target.Parameters);
and then the generic method in question would be simply:
public static Task<int> ExecuteUpdateWithShadowPropertiesAsync<TSource>(
this IQueryable<TSource> source,
Expression<Func<SetPropertyCalls<TSource>, SetPropertyCalls<TSource>>> setPropertyCalls,
CancellationToken cancellationToken = default)
where TSource : BaseIdentifierEntity
=> source.ExecuteUpdateAsync(setPropertyCalls
.Append(s => s.SetProperty(p => p.ModifiedOn, DateTime.Now)),
cancellationToken);
Now having the previous two questions answered, the next would be - Instead of using custom extension method, can this be done better in EF Core? Ideally on a similar fashion as the change tracker (SaveChanges) approach.
And the answer is yes. EF Core 7.0 along with batch updates introduced a long asked and very handy feature called Interception to modify the LINQ expression tree (unfortunately not documented yet). It allows you to intercept and modify LINQ query expression tree before EF Core. In this case, it could be used to add more update properties to ExecuteUpdate query.
In order to utilize it, we first define interceptor class
#nullable disable
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Query;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace Microsoft.EntityFrameworkCore;
internal class ExecuteUpdateInterceptor : IQueryExpressionInterceptor
{
List<(Type Type, Delegate Calls, Func<IEntityType, bool> Filter)> items = new();
public ExecuteUpdateInterceptor Add<TSource>(
Func<Expression<Func<SetPropertyCalls<TSource>, SetPropertyCalls<TSource>>>> source,
Func<IEntityType, bool> filter = null)
{
items.Add((typeof(TSource), source, filter));
return this;
}
Expression IQueryExpressionInterceptor.QueryCompilationStarting(
Expression queryExpression, QueryExpressionEventData eventData)
{
if (queryExpression is MethodCallExpression call &&
call.Method.DeclaringType == typeof(RelationalQueryableExtensions) &&
call.Method.Name == nameof(RelationalQueryableExtensions.ExecuteUpdate))
{
var setPropertyCalls = (LambdaExpression)((UnaryExpression)call.Arguments[1]).Operand;
var body = setPropertyCalls.Body;
var parameter = setPropertyCalls.Parameters[0];
var targetType = eventData.Context?.Model.FindEntityType(parameter.Type.GetGenericArguments()[0]);
if (targetType != null)
{
foreach (var item in items)
{
if (!item.Type.IsAssignableFrom(targetType.ClrType)) continue;
if (item.Filter != null && !item.Filter(targetType)) continue;
var calls = (LambdaExpression)item.Calls.Method.GetGenericMethodDefinition()
.MakeGenericMethod(targetType.ClrType)
.Invoke(null, null);
body = calls.Body.ReplaceParameter(calls.Parameters[0], body);
}
if (body != setPropertyCalls.Body)
return call.Update(call.Object, new[] { call.Arguments[0], Expression.Lambda(body, parameter) });
}
}
return queryExpression;
}
}
This requires a bit more knowledge of expressions, so you can just use it as is. Basically what it does is intercepting the ExecuteUpdate "calls" and appending additional SetProperty "calls" based on statically configured rules and filters.
The only remaining is to create, configure and add the interceptor inside your OnConfigure override:
optionsBuilder.AddInterceptors(new ExecuteUpdateInterceptor()
//.Add(...)
//.Add(...)
);
The configuration is based on delegates, with only limitation/requirement the SetPropertyCalls generic Func to be a real generic method and not anonymous delegate (I haven't found a way to make it easy for use and at the same time being anonymous).
So here are some usages:
property of a base class (your case):
optionsBuilder.AddInterceptors(new ExecuteUpdateInterceptor()
.Add(SetModifiedOn<BaseIdentifierEntity>)
);
static Expression<Func<SetPropertyCalls<TSource>, SetPropertyCalls<TSource>>> SetModifiedOn<TSource>()
where TSource : BaseIdentifierEntity
=> s => s.SetProperty(p => p.ModifiedOn, DateTime.UtcNow);
entity property with specific name and type (shadow or regular). Uses presence of the property as a filter. Also works in your case.
const string ModifiedOn = nameof(ModifiedOn);
optionsBuilder.AddInterceptors(new ExecuteUpdateInterceptor()
.Add(SetModifiedOn<object>, t => t.FindProperty(ModifiedOn) is { } p && p.ClrType == typeof(DateTime))
);
static Expression<Func<SetPropertyCalls<TSource>, SetPropertyCalls<TSource>>> SetModifiedOn<TSource>()
where TSource : class
=> s => s.SetProperty(p => EF.Property<DateTime>(p,ModifiedOn), DateTime.UtcNow);
Note: The SetPropertyCalls func must be generic, to allow binding it to the actual source type from the query.
Also, I haven't mentioned it explicitly till now, but with the last approach, you just use a standard ExecuteUpdate or ExecuteUpdateAsync methods, and the interceptor adds the cofigured additional SetProperty expressions.
The following extension updates shadow property with other fields:
public static class EntityFrameworkExtensions
{
public static Task<int> ExecuteUpdateWithShadowPropertiesAsync<TSource>(this IQueryable<TSource> source,
Expression<Func<SetPropertyCalls<TSource>, SetPropertyCalls<TSource>>> setPropertyCalls,
CancellationToken cancellationToken = default)
where TSource : class
{
Expression<Func<SetPropertyCalls<TSource>, SetPropertyCalls<TSource>>> setShadowPropertyCalls =
x => x.SetProperty(p => EF.Property<DateTime>(p, "ModifiedOn"), p => DateTime.UtcNow);
var mergedPropertyCalls = Merge(setPropertyCalls, setShadowPropertyCalls);
return source.ExecuteUpdateAsync(mergedPropertyCalls, cancellationToken: cancellationToken);
}
static Expression<Func<SetPropertyCalls<TSource>, SetPropertyCalls<TSource>>> Merge<TSource>(
Expression<Func<SetPropertyCalls<TSource>, SetPropertyCalls<TSource>>> setPropertyCalls,
Expression<Func<SetPropertyCalls<TSource>, SetPropertyCalls<TSource>>> additional)
{
var newBody = ReplacingExpressionVisitor.Replace(additional.Parameters[0], setPropertyCalls.Body, additional.Body);
return Expression.Lambda<Func<SetPropertyCalls<TSource>, SetPropertyCalls<TSource>>>(newBody,
setPropertyCalls.Parameters);
}
}
I don't understand why this doesn't translate. It seems to be exactly the use case described here.
The LINQ expression
DbSet<A>()
.GroupJoin(
inner: DbSet<B>(),
outerKeySelector: a => a.AId,
innerKeySelector: b => b.AId,
resultSelector: (a, bs) => new {
a = a,
bs = bs
})
produces the error:
could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
The LINQ code producing the exception is
from a in ctx.As
join b in ctx.Bs on a.aId equals b.aId into bs
select new {A = a, Bs = bs.ToList()};
Edit: maybe I misunderstood the doc and this is an example of something that does NOT translate.
Executing a query like the following example generates a result of Blog & IEnumerable. Since databases (especially relational databases) don't have a way to represent a collection of client-side objects, GroupJoin doesn't translate to the server in many cases. It requires you to get all of the data from the server to do GroupJoin without a special selector (first query below). But if the selector is limiting data being selected then fetching all of the data from the server may cause performance issues (second query below). That's why EF Core doesn't translate GroupJoin.
But then my question becomes instead : how do I achieve the result I'm looking for without requiring navigational properties ?
The explanation in the linked documentation just follows the EF Core team vision and is ridiculous, because of course it can easily be translated - I had a long discussion with the team here Query with GroupBy or GroupJoin throws exception #17068 and continue here Query: Support GroupJoin when it is final query operator #19930, trying to convince them why it should be supported, with no luck regardless of the arguments.
The whole point is (and that's the current workaround) it can be processed like if it was correlated subquery (SelectMany), which is translated and processed properly (even though the query result shape has no SQL equivalent.
Anyway, the current status is "Needs Design" (whatever that means), and the workaround is to replace the join with correlated subquery (which is what EF Core is using internally when "expanding" collection navigation properties during the query translation).
In your case, replace
join b in ctx.Bs on a.aId equals b.aId into bs
with
let bs = ctx.Bs.Where(b => a.aId == b.aId)
However, I highly recommend adding and using navigation properties. Not sure why you "can't use" them, in LINQ to Entities which do not project entities they serve just metadata for relationships, thus produce automatically the necessary joins. By not defining them you just put on yourself unneeded limitations (additionally to EF Core limitations/bugs). In general EF Core works better and support more things when using navigation properties instead of manual joins.
Try this query, it should work with EF Core:
var query =
from a in ctx.As
select new {A = a, Bs = ctx.Bs.Where(b => b.Id == a.aId).ToList()};
Probably the .ToList() cannot be translated. Use include instead
var result = ctx.As
.Include(a => a.Bs)
.ToList();
Where you must have a navigation property for the Bs in the A class:
public class A
{
public int aId { get; set; }
public List<B> Bs { get; set; }
}
See:
Eager Loading of Related Data
Relationships - EF Core
EF Core will not allow you to do a GroupJoin without following up with a SelectMany in order to flatten the list. The GroupJoin has no equivalent implementation in SQL, however the GroupJoin/SelectMany is equivalent to an inner-join or left-join (depending on if you use DefaultIfEmpty) so it works fine:
context.Users.GroupJoin(
context.UserRoles,
u => u.UserId,
r => r.UserId,
(user, roles) => new { user, roles })
//Will not work without this line
.SelectMany(x => x.roles.DefaultIfEmpty(), (x, r) => new { x.user, role = r })
.ToList();
If you actually want your results to be grouped (as opposed to trying to do a left-join) you have a few options:
You can materialize the results of a left join, then group the results in-memory (the code below uses my LeftJoin function shown in LEFT OUTER JOIN in LINQ):
context.Users.LeftJoin(
context.UserRoles,
u => u.UserId,
r => r.UserId,
(user, roles) => new { user, roles })
.ToList()
.GroupBy(x => x.user, (u, x) => new
{
User = u,
Roles = x.Select(z => z.role).Where(r => r != null).ToList()
})
.ToList();
You can use a sub-query. Note that EF is smart enough to use a left-join when it generates the SQL:
context.Users.Select(u => new
{
User = u,
Roles = context.UserRoles.Where(r => r.UserId == u.UserId).ToList()
})
.ToList();
If you prefer the GroupJoin syntax, but don't want to have to keep calling all the other functions to flatten, materialize, then re-group the results, you can use my JoinMany() extension method. This method uses the sub-query approach but wraps it in a generic method that looks very similar to the GroupJoin function:
context.Users.JoinMany(
context.UserRoles,
(u, r) => u.UserId == r.UserId,
(user, roles) => new { user, roles })
.ToList();
Supporting code:
public static class QueryableExtensions
{
public static IQueryable<TResult> LeftJoin<TOuter, TInner, TKey, TResult>(
this IQueryable<TOuter> outer,
IEnumerable<TInner> inner, Expression<Func<TOuter, TKey>> outerKeySelector,
Expression<Func<TInner, TKey>> innerKeySelector,
Expression<Func<TOuter, TInner, TResult>> resultSelector)
{
return outer
.GroupJoin(inner, outerKeySelector, innerKeySelector, (o, i) => new { o, i })
.SelectMany(o => o.i.DefaultIfEmpty(), (x, i) => new { x.o, i })
.ApplySelector(x => x.o, x => x.i, resultSelector);
}
public static IQueryable<TResult> JoinMany<TOuter, TInner, TResult>(
this IQueryable<TOuter> outers, IQueryable<TInner> inners,
Expression<Func<TOuter, TInner, bool>> condition,
Expression<Func<TOuter, IEnumerable<TInner>, TResult>> resultSelector)
{
//Use a placeholder "p => true" expression for the sub-query
Expression<Func<TOuter, JoinResult<TOuter, IEnumerable<TInner>>>> joinSelector = o =>
new JoinResult<TOuter, IEnumerable<TInner>> { Outer = o, Inner = inners.Where(p => true) };
//Create the where-clause that will be used for the sub-query
var whereClause = Expression.Lambda<Func<TInner, bool>>(
condition.Body.ReplaceParameter(condition.Parameters[0], joinSelector.Parameters[0]),
condition.Parameters[1]);
//Replace the placeholder expression with our new where clause
joinSelector = Expression.Lambda<Func<TOuter, JoinResult<TOuter, IEnumerable<TInner>>>>(
joinSelector.Body.VisitExpression(node =>
(node is LambdaExpression le && le.Parameters.Count == 1 && le.Parameters[0].Type == typeof(TInner)
&& le.Body is ConstantExpression ce && ce.Value is bool b && b)
? whereClause : null),
joinSelector.Parameters[0]);
return outers.Select(joinSelector).ApplySelector(x => x.Outer, x => x.Inner, resultSelector);
}
private static IQueryable<TResult> ApplySelector<TSource, TOuter, TInner, TResult>(
this IQueryable<TSource> source,
Expression<Func<TSource, TOuter>> outerProperty,
Expression<Func<TSource, TInner>> innerProperty,
Expression<Func<TOuter, TInner, TResult>> resultSelector)
{
var p = Expression.Parameter(typeof(TSource), $"param_{Guid.NewGuid()}".Replace("-", string.Empty));
Expression body = resultSelector?.Body
.ReplaceParameter(resultSelector.Parameters[0], outerProperty.Body.ReplaceParameter(outerProperty.Parameters[0], p))
.ReplaceParameter(resultSelector.Parameters[1], innerProperty.Body.ReplaceParameter(innerProperty.Parameters[0], p));
var selector = Expression.Lambda<Func<TSource, TResult>>(body, p);
return source.Select(selector);
}
public class JoinResult<TOuter, TInner>
{
public TOuter Outer { get; set; }
public TInner Inner { get; set; }
}
}
public static class ExpressionExtensions
{
public static Expression ReplaceParameter(this Expression source, ParameterExpression toReplace, Expression newExpression)
=> new ReplaceParameterExpressionVisitor(toReplace, newExpression).Visit(source);
public static Expression VisitExpression(this Expression source, Func<Expression, Expression> onVisit)
=> new DelegateExpressionVisitor (onVisit).Visit(source);
}
public class DelegateExpressionVisitor : ExpressionVisitor
{
Func<Expression, Expression> OnVisit { get; }
public DelegateExpressionVisitor(Func<Expression, Expression> onVisit)
{
this.OnVisit = onVisit;
}
public override Expression Visit(Expression node)
{
return OnVisit(node) ?? base.Visit(node);
}
}
public class ReplaceParameterExpressionVisitor : ExpressionVisitor
{
public ParameterExpression ToReplace { get; }
public Expression ReplacementExpression { get; }
public ReplaceParameterExpressionVisitor(ParameterExpression toReplace, Expression replacement)
{
this.ToReplace = toReplace;
this.ReplacementExpression = replacement;
}
protected override Expression VisitParameter(ParameterExpression node)
=> (node == ToReplace) ? ReplacementExpression : base.VisitParameter(node);
}
ASP .NET 5 MVC Core application.
Following method is used to find entity by key:
class MyStudents: Students { }
public TEntity FindNormalized<TEntity>(params object[] keyValues)
where TEntity : class
{
return Find<TEntity>(keyValues);
}
void FindNormalized<TEntity>(TEntity entity, params object[] keyValues)
where TEntity : class
{
var res = FindNormalized<TEntity>(keyValues);
if (res == null)
throw new KeyNotFoundException(entity.GetType().Name + " entity key not found " + keyValues[0]);
CopyPropertiesTo(res, entity);
}
static void CopyPropertiesTo<T, TU>(T source, TU dest)
{ // https://stackoverflow.com/questions/3445784/copy-the-property-values-to-another-object-with-c-sharp
var sourceProps = typeof(T).GetProperties().Where(x => x.CanRead).ToList();
var destProps = typeof(TU).GetProperties()
.Where(x => x.CanWrite)
.ToList();
foreach (var sourceProp in sourceProps)
if (destProps.Any(x => x.Name == sourceProp.Name))
{
var p = destProps.First(x => x.Name == sourceProp.Name);
p.SetValue(dest, sourceProp.GetValue(source, null), null);
}
}
Using it with subclass
FindNormalized<MyStudents>(1);
throws exception
Cannot create a DbSet for 'MyStudents' because this type is not
included in the model for the context.
FindNormalized<Students>(1);
works.
How to fix this so that it can used with subclass type also ?
For getting table attribute from subclass code from Dynamic Linq works:
string GetTableAttrib(Type t)
{
foreach (Type t2 in SelfAndBaseClasses(t))
{
IEntityType entityType = Model.FindEntityType(t2);
if (entityType == null)
continue;
return entityType.GetTableName();
}
throw new ApplicationException(t.FullName + " no table attribute");
}
/// <summary>
/// Enumerate inheritance chain - copied from DynamicLinq
/// </summary>
static IEnumerable<Type> SelfAndBaseClasses(Type type)
{
while (type != null)
{
yield return type;
type = type.BaseType;
}
}
Maybe this can used to implement Find also. Unfortunately Find throws exception. Is it reasonable to wrap Find using try/catch or is there better way ?
The EFCore Find method throws exception because the TEntity is not an entity type but a type deriving from the entity type. So to solve this, we need to first get the most derived type which is the ancestor type of the passed in TEntity type, so when passing in MyStudents you need to get type Students first to use as type argument for Find<> method. Because that type is not known at design time, so we need to use reflection to invoke that Find<> method or better use the other overload of Find that accepts the first argument of entity type (Type). Of course I understand that your Find<> method in your question is from DbContext.Find.
So the solution can be simple like this, firstly we need method to get the most derived entity type from the passed in type (which inherits from the entity type):
public static class DbContextExtensions {
public static Type GetMostDerivedEntityType(this DbContext dbContext, Type type){
while(type != null && dbContext.Model.FindEntityType(type) == null) {
type = type.BaseType;
}
return type;
}
}
Next just use that extension method in your FindNormalized method to find the most derived entity type first before using Find:
//I suppose that this is in the context (class) of your custom DbContext
void FindNormalized<TEntity>(TEntity entity, params object[] keyValues)
where TEntity : class
{
var actualEntityType = this.GetMostDerivedEntityType(typeof(TEntity)) ??
throw new InvalidOperationException($"The type {typeof(TEntity)} is not an entity type or a derive from an entity type");
//use the actualEntityType instead
var res = Find(actualEntityType, keyValues);
if (res == null)
throw new KeyNotFoundException(entity.GetType().Name + " entity key not found " + keyValues[0]);
//copy the properties
CopyPropertiesTo(res, entity);
}
Note that in the above code I use another overload (non-generic version) of Find which accepts the first argument of Type (entity type) directly instead of using your wrapper FindNormalized which is generic only (you can add your one overload of that wrapper which wraps the non-generic Find as used directly in my code above).
I've not tested the code at all. Just try it and let me know if there is any error.
We have a need to convert between units of measure on the fly, and Units.NET seems like the ideal solution, however, it is lacking some of the units we need (gallons per minute). I know it is trivial to add to the specification file and recreate the classes, but it would be preferable to be able to simply use the nuget package. Does anyone know of a way to add units to Units.NET without recompiling the Units.NET solution?
It should be noted that we aren't married to Units.NET, we just need to be able to convert between arbitrary units of flow and length.
Thanks!
You will probably get a better response asking this on Units.NET's github issues page. There is a wiki page on adding custom units outside the main lib, but I admit this part is not very polished and could use improvement.
Copied from the wiki:
If you are looking to add new quantities or units to the UnitsNet nuget, please see https://github.com/angularsen/UnitsNet/wiki/Adding-a-New-Unit.
Units.NET structure
Units.NET roughly consists of these parts:
Quantities like Length and Force
Unit enum values like LengthUnit.Meter and ForceUnit.Newton
UnitAbbreviationsCache, UnitParser, QuantityParser and UnitConverter for parsing and converting quantities and units
JSON files for defining units, conversion functions and abbreviations
CodeGen.exe to generate C# code based on JSON files
Example: Custom quantity HowMuch with units HowMuchUnit
Map unit enum values to unit abbreviations
UnitAbbreviationsCache.Default.MapUnitToDefaultAbbreviation(HowMuchUnit.Some, "sm");
UnitAbbreviationsCache.Default.MapUnitToDefaultAbbreviation(HowMuchUnit.Lots, "lts");
UnitAbbreviationsCache.Default.MapUnitToDefaultAbbreviation(HowMuchUnit.Tons, "tns");
Lookup unit abbrevations from enum values
Console.WriteLine("GetDefaultAbbreviation(): " + string.Join(", ",
UnitAbbreviationsCache.Default.GetDefaultAbbreviation(HowMuchUnit.Some), // "sm"
UnitAbbreviationsCache.Default.GetDefaultAbbreviation(HowMuchUnit.Lots), // "lts"
UnitAbbreviationsCache.Default.GetDefaultAbbreviation(HowMuchUnit.Tons) // "tns"
));
Parse unit abbreviations back to enum values
Console.WriteLine("Parse<HowMuchUnit>(): " + string.Join(", ",
UnitParser.Default.Parse<HowMuchUnit>("sm"), // Some
UnitParser.Default.Parse<HowMuchUnit>("lts"), // Lots
UnitParser.Default.Parse<HowMuchUnit>("tns") // Tons
));
Convert between units of custom quantity
var unitConverter = UnitConverter.Default;
unitConverter.SetConversionFunction<HowMuch>(HowMuchUnit.Lots, HowMuchUnit.Some, x => new HowMuch(x.Value * 2, HowMuchUnit.Some));
unitConverter.SetConversionFunction<HowMuch>(HowMuchUnit.Tons, HowMuchUnit.Lots, x => new HowMuch(x.Value * 10, HowMuchUnit.Lots));
unitConverter.SetConversionFunction<HowMuch>(HowMuchUnit.Tons, HowMuchUnit.Some, x => new HowMuch(x.Value * 20, HowMuchUnit.Some));
var from = new HowMuch(10, HowMuchUnit.Tons);
IQuantity Convert(HowMuchUnit toUnit) => unitConverter.GetConversionFunction<HowMuch>(from.Unit, toUnit)(from);
Console.WriteLine($"Convert 10 tons to:");
Console.WriteLine(Convert(HowMuchUnit.Some)); // 200 sm
Console.WriteLine(Convert(HowMuchUnit.Lots)); // 100 lts
Console.WriteLine(Convert(HowMuchUnit.Tons)); // 10 tns
Sample quantity
public enum HowMuchUnit
{
Some,
Lots,
Tons
}
public struct HowMuch : IQuantity
{
public HowMuch(double value, HowMuchUnit unit)
{
Unit = unit;
Value = value;
}
Enum IQuantity.Unit => Unit;
public HowMuchUnit Unit { get; }
public double Value { get; }
#region IQuantity
private static readonly HowMuch Zero = new HowMuch(0, HowMuchUnit.Some);
public QuantityType Type => QuantityType.Undefined;
public BaseDimensions Dimensions => BaseDimensions.Dimensionless;
public QuantityInfo QuantityInfo => new QuantityInfo(Type,
new UnitInfo[]
{
new UnitInfo<HowMuchUnit>(HowMuchUnit.Some, BaseUnits.Undefined),
new UnitInfo<HowMuchUnit>(HowMuchUnit.Lots, BaseUnits.Undefined),
new UnitInfo<HowMuchUnit>(HowMuchUnit.Tons, BaseUnits.Undefined),
},
HowMuchUnit.Some,
Zero,
BaseDimensions.Dimensionless);
public double As(Enum unit) => Convert.ToDouble(unit);
public double As(UnitSystem unitSystem) => throw new NotImplementedException();
public IQuantity ToUnit(Enum unit)
{
if (unit is HowMuchUnit howMuchUnit) return new HowMuch(As(unit), howMuchUnit);
throw new ArgumentException("Must be of type HowMuchUnit.", nameof(unit));
}
public IQuantity ToUnit(UnitSystem unitSystem) => throw new NotImplementedException();
public override string ToString() => $"{Value} {UnitAbbreviationsCache.Default.GetDefaultAbbreviation(Unit)}";
public string ToString(string format, IFormatProvider formatProvider) => $"HowMuch ({format}, {formatProvider})";
public string ToString(IFormatProvider provider) => $"HowMuch ({provider})";
public string ToString(IFormatProvider provider, int significantDigitsAfterRadix) => $"HowMuch ({provider}, {significantDigitsAfterRadix})";
public string ToString(IFormatProvider provider, string format, params object[] args) => $"HowMuch ({provider}, {string.Join(", ", args)})";
#endregion
}
I am trying to order an IQueryable of entities by date from a passed in Expression< Func< T, object>> and am getting the error: "Unable to cast the type 'System.Nullable`1' to type 'System.Object'. LINQ to Entities only supports casting Entity Data Model primitive types." The entity has a nullable datetime property on it on which I am trying to sort:
Example: (where e.Date is a nullable DateTime)
Expression<Func<T,object>> sorter = (e) => e.Date;
IOrderedQueryable<T> sortedData = data.OrderBy(sorter);
Thanks in advance!
I wrote a simple class for ordering entities based on a lambda expression at runtime.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace DataModeling
{
public class QueryOrderer<TEntity>
where TEntity : class
{
private LambdaExpression defaultSortExpression;
private Dictionary<string, LambdaExpression> orderFieldLookup;
public QueryOrderer()
{
orderFieldLookup = new Dictionary<string, LambdaExpression>();
}
public void AddOrderMapping<TProp>(string fieldName, Expression<Func<TEntity, TProp>> selector)
{
orderFieldLookup[fieldName] = selector;
}
public void SetDefaultSortExpression<TProp>(Expression<Func<TEntity, TProp>> selector)
{
defaultSortExpression = selector;
}
public IQueryable<TEntity> GetOrderedEntries(string field, bool isDescending, IQueryable<TEntity> entries)
{
return orderEntries(entries, field, isDescending);
}
private IQueryable<TEntity> orderEntries(IQueryable<TEntity> entries, string fieldName, bool isDescending)
{
dynamic lambda = getOrderByLambda(entries, fieldName);
if (lambda == null)
{
return entries;
}
if (isDescending)
{
return Queryable.OrderByDescending(entries, lambda);
}
else
{
return Queryable.OrderBy(entries, lambda);
}
}
private dynamic getOrderByLambda(IQueryable<TEntity> entries, string fieldName)
{
if (!String.IsNullOrWhiteSpace(fieldName) && orderFieldLookup.ContainsKey(fieldName))
{
return orderFieldLookup[fieldName];
}
else
{
return defaultSortExpression;
}
}
}
}
You use this class by initially setting up all of the fields:
QueryOrderer<User> orderer = new QueryOrderer<User>();
orderer.SetDefaultSortExpression(u => u.FullName);
orderer.AddOrderMapping("UserId", u => u.UserId);
orderer.AddOrderMapping("Name", u => u.FullName);
orderer.AddOrderMapping("Email", u => u.Email);
orderer.AddOrderMapping("CreatedOn", u => u.CreatedOn);
...
var users = orderer.GetOrderedEntries("CreatedOn", isDescending: false, context.Users);
I nice feature of this code is that it handles look-up values perfectly. For instance, if you're trying to sort using the description rather than a key, you can use the outer context when building up the sort expression:
orderer.AddOrderMapping("UserType",
u => context.UserTypes
.Where(t => t.UserTypeId == u.UserTypeId)
.Select(t => t.Description)
.FirstOrDefault());
Entity Framework is smart enough to just fold the sub-query right into the outer query.
Two problem here: First you use object in your sorter, you should use DateTime. Secondly every element must have a place in the order so you have to define what should happen with elements where Date is null:
Expression<Func<T, DateTime>> sorter = (e) => e.Date ?? DateTime.MaxValue;
IOrderedQueryable<T> sortedData = data.OrderBy(sorter);
Try to reconstruct expression body
private LambdaExpression CreateLambdaPropertyGetter(Expression<Func<TEntity, object>> expression)
{
Expression body;
if (expression.Body is UnaryExpression && ((UnaryExpression)expression.Body).NodeType == ExpressionType.Convert)
body = ((UnaryExpression)expression.Body).Operand;
else
body = expression.Body;
var lambda = Expression.Lambda(body, expression.Parameters);
return lambda;
}
Try using Func delegate instead on Expression<Func>
Func<T,object> sorter = (e) => e.Date;
IOrderedEnumerable<T> sortedData = data.OrderBy(sorter);