I have a project with Microsoft.EntityFrameworkCore 5.0.17 on the runtime it is running under Npgsql.EntityFrameworkCore.PostgreSQL but now i'm writing a test over my class. I'm using an array field in my Entity:
// Enum of my array field
public enum MyEnum
{
One, Two, Three
}
// Entity
public class MyEntity
{
public int Id { get; set; }
public MyEnum[] Arr { get; set; }
}
So in my protected override void OnModelCreating(ModelBuilder builder) of my TestDatabaseContext I have to use a workaround to make it work:
var converter = new ValueConverter<MyEnum[], string>(
x => JsonConvert.SerializeObject(x),
x => JsonConvert.DeserializeObject<MyEnum[]>(x));
builder.Entity<MyEntity>().Property(x => x.Arr).HasConversion(converter);
But in my code I'm using such code that could not been translated during test:
public static IQueryable<MyEntity> WhereMyEnum(this IQueryable<MyEntity> queryable, params MyEnum[] args)
{
return queryable.Where(u => u.Arr.Any(x => args.Contains(x)))
}
Exception thrown:
The LINQ expression ... could not be translated. Either rewrite the query in a form that can be translated
Now I can only make such workaround on my code. But it would better to not change main logic because of Unit-tests...
if (!_context.Database.IsRelational())
queryable = queryable.Where(x => x.Arr.Contains(MyEnum.One));
else
queryable = queryable.WhereMyEnum(MyEnum.One);
I can also put that code to the local function and divide my implementation over database and memory. But maybe there is an another solution?
Related
I have the following situation in a generic class:
IQueryable<TQuote> query = GetQuotes(ctx)
where ctx is a database context and GetQuotes returns DbSet<TQuote>.
Then somewhere down the code the query is executed. Its simplified form is as follows:
var list = await query
.Select(v => new
{
v.Id,
TimeZone = v.Property != null ? (int?)v.Property.TimeZone : null
// and about 10 more simple properties like v.Id above
}
.ToListAsync();
where Property is a navigation property (to another table) and TimeZone is just a regular database column / property of type int? in C#.
Everything worked until I tried to use that generic class with the entity, which does not have a navigation property Property. So, I would like to replace the "hard coded" expression v.Property != null ? (int?)v.Property.TimeZone : null by an abstract member of the class and then override it differently for different TQuote types.
I tried something along this signature:
protected abstract Expression<Func<TQuote, int?>> GetTimeZone();
but then if I use it in LINQ (either directly or assigning to some variable first), then signature of TimeZone also changes to Expression<...> and if I try to ...Compile().Invoke(v), then LINQ complains that LINQ to entities does not support that.
I saw this answer: Create a Dynamic Linq to EF Expression to Select IQueryable into new class and assign properties However, it builds the whole selector by hands and given that I have total of 16 properties that would be a pain on the neck to create and maintain. So, I wonder if I can do something about this TimeZone only but leave the rest in a simple LINQ form as above.
Is it possible and if yes, then how exactly?
Try nuget LINQKit, https://github.com/scottksmith95/LINQKit.
Below is my attempt.
LinqKit provides the AsExpandable() and Invoke(v) methods.
I made up properties like Area.TimeZone and Region.RegionalTimeZone.
using LinqKit;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
class QuoteResult
{
public int Id { get; set; }
public int? TimeZone { get; set; }
public string Note { get; set; }
}
abstract class QuoteHelper<TQuote> where TQuote : Quote
{
protected abstract Expression<Func<TQuote, int?>> GetTimeZone { get; }
public IEnumerable<QuoteResult> GetQuoteResults(
EfExpressionPropertyDbContext ctx)
{
IQueryable<TQuote> query = GetQuotes(ctx);
var getTimeZone = GetTimeZone;
var list = query
.AsExpandable()
.Select(v => new QuoteResult
{
Id = v.Id,
TimeZone = getTimeZone.Invoke(v),
Note = v.Note
// and about 10 more simple properties like v.Id above
})
.ToList();
return list;
}
public IQueryable<TQuote> GetQuotes(
EfExpressionPropertyDbContext ctx)
{
return ctx.Set<TQuote>();
}
}
class CommonQuoteHelper : QuoteHelper<CommonQuote>
{
protected override Expression<Func<CommonQuote, int?>> GetTimeZone
{
get { return q => q.Area != null ? (int?)q.Area.TimeZone : null; }
}
}
class PeculiarQuoteHelper : QuoteHelper<PeculiarQuote>
{
protected override Expression<Func<PeculiarQuote, int?>> GetTimeZone
{
get { return q => q.Region != null ? (int?)q.Region.RegionalTimeZone : null; }
}
}
In my project I have a Linq queryable (actually an EF6 collection) that I need to convert to a collection of data transfer objects. I'm using AutoMapper throughout the project, especially for its ability to do type projection thereby reducing the amount of SQL generated by the Linq query.
But I've got a small problem with one of my DTO classes. The associated database column contains a nullable string, which I wish to map to a nullable enum. But at runtime an exception is thrown
No coercion operator is defined between types 'System.String' and 'System.Nullable`1[AutomapperEnumTest.Method]'.
Here's a complete test program that demonstrates the problem: (see .Net Fiddle)
using System;
using System.Linq;
using AutoMapper;
using AutoMapper.QueryableExtensions;
namespace AutomapperEnumTest
{
public class Program
{
public static void Main()
{
Configure();
var src = new SourceType[] { new SourceType { Name = "Rob", MethodName = "AUTO" } };
var results = src.AsQueryable()
.ProjectTo<DestType>();
foreach(var item in results)
{
Console.WriteLine(String.Format("DestType Name={0} Method={1}", item.Name, item.Method));
}
Console.WriteLine("Done");
}
private static void Configure()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<string, Method?>().ProjectUsing(src => src == "MANUAL" ? Method.MANUAL : Method.AUTO);
cfg.CreateMap<SourceType, DestType>()
.ForMember(dest => dest.Method, opt => opt.MapFrom(src => src.MethodName));
});
}
}
public enum Method
{
MANUAL=0,
AUTO=1
}
public class DestType
{
public string Name {get; set; }
public Method? Method {get; set; }
}
public class SourceType
{
public string Name {get; set; }
public string MethodName {get; set; }
}
}
Now if I change the property from Method? to Method it works fine (see this modification in .Net Fiddle). But I don't want to do this, so my question is how do I inform Linq/AutoMapper that it should use my ProjectUsing for the nullable enum?
Many thanks.
The same happens in the latest AutoMapper v5.2.0.
After looking at the source code, I think it is a bug inside the ExpressionBuilder class because for some unknown reason NullableExpressionBinder is given higher priority than CustomProjectionExpressionBinder (and others), so basically when you map non nullable type to nullable, all the custom mappings are ignored.
I would suggest you to report it on their repository. Until then, I could suggest you the following workaround (hack). Include the following custom class in your project:
using System.Linq.Expressions;
using System.Reflection;
using AutoMapper;
using AutoMapper.QueryableExtensions;
using AutoMapper.QueryableExtensions.Impl;
class NullableExpressionBinderEx : IExpressionBinder
{
public static void Install()
{
var bindersField = typeof(ExpressionBuilder).GetField("Binders", BindingFlags.NonPublic | BindingFlags.Static);
var binders = (IExpressionBinder[])bindersField.GetValue(null);
binders[0] = new NullableExpressionBinderEx();
}
IExpressionBinder baseBinder = new NullableExpressionBinder();
private NullableExpressionBinderEx() { }
public bool IsMatch(PropertyMap propertyMap, TypeMap propertyTypeMap, ExpressionResolutionResult result)
{
if (propertyTypeMap != null && propertyTypeMap.CustomProjection != null)
return false;
return baseBinder.IsMatch(propertyMap, propertyTypeMap, result);
}
public MemberAssignment Build(IConfigurationProvider configuration, PropertyMap propertyMap, TypeMap propertyTypeMap, ExpressionRequest request, ExpressionResolutionResult result, IDictionary<ExpressionRequest, int> typePairCount)
{
return baseBinder.Build(configuration, propertyMap, propertyTypeMap, request, result, typePairCount);
}
}
then add the following line to your Configure method:
NullableExpressionBinderEx.Install();
and the issue should be solved.
You could map MethodName to Method manually, unless I'm missing something in your question.
private static void Configure()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<SourceType, DestType>()
.ForMember(dest => dest.Method, opt => opt.MapFrom(src =>
src.MethodName == "MANUAL" ? Method.MANUAL : Method.AUTO));
});
}
Currently I have an entity that is "geolocatable" via a SqlGeography column that I can use via expressions for filtering and sorting. I am already able to get all entities within distance x of point y and sort by entities closest to (or furthest from) point y. However, in order to return the distance from the entity to y I have to recalculate the distance in the application because I have not yet determined how to materialize the result of the distance calculation from the database to the entities in the IQueryable. This is a mapped entity and a great deal of application logic surrounds the type of entity returned so projecting it into a dynamic object is not a viable option for this implementation (though I understand how that would work). I have also tried using an unmapped object that inherits from the mapped entity but that suffers the same problems. Essentially, as I understand it, I should be able to define the getter of an unmapped property to assign a computed value in a queryable extension IF I modify the expression tree that represents the IQueryable but the how escapes me. I've written expressions in this manner before but I think I need to be able to modify the existing select rather than just chaining on a new Expression.Call which is unexplored territory for me.
The following should code should properly illustrate the problem:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration;
using System.Data.Entity.Spatial; // from Microsoft.SqlServer.Types (Spatial) NuGet package
using System.Linq;
public class LocatableFoo
{
[Key]
public int Id { get; set; }
public DbGeography Geolocation { get; set; }
[NotMapped]
public double? Distance { get; set; }
}
public class PseudoLocatableFoo : LocatableFoo
{
}
public class LocatableFooConfiguration : EntityTypeConfiguration<LocatableFoo>
{
public LocatableFooConfiguration()
{
this.Property(foo => foo.Id).HasColumnName("id");
this.Property(foo => foo.Geolocation).HasColumnName("geolocation");
}
}
public class ProblemContext : DbContext
{
public DbSet<LocatableFoo> LocatableFoos { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new LocatableFooConfiguration());
base.OnModelCreating(modelBuilder);
}
}
public class Controller
{
public Controller(ProblemContext context) // dependency injection
{
this.Context = context;
}
private ProblemContext Context { get; set; }
/* PROBLEM IN THIS METHOD:
* Do not materialize results (ie ToList) and then calculate distance as is done currently <- double calculation of distance in DB and App I am trying to solve
* Must occur prior to materialization
* Must be assignable to "query" that is to type IQueryable<LocatableFoo>
*/
public IEnumerable<LocatableFoo> GetFoos(decimal latitude, decimal longitude, double distanceLimit)
{
var point = DbGeography.FromText(string.Format("Point({0} {1})", longitude, latitude), 4326); // NOTE! This expects long, lat rather than lat, long.
var query = this.Context.LocatableFoos.AsQueryable();
// apply filtering and sorting as proof that EF can turn this into SQL
query = query.Where(foo => foo.Geolocation.Distance(point) < distanceLimit);
query = query.OrderBy(foo => foo.Geolocation.Distance(point));
//// this isn't allowed because EF doesn't allow projecting to mapped entity
//query = query.Select( foo => new LocatableFoo { Id = foo.Id, Geolocation = foo.Geolocation, Distance = foo.Geolocation.Distance(point) });
//// this isn't allowed because EF doesn't allow projecting to mapped entity and PseudoLocatableFoo is considered mapped since it inherits from LocatableFoo
//query = query.Select( foo => new PseudoLocatableFoo { Id = foo.Id, Geolocation = foo.Geolocation, Distance = foo.Geolocation.Distance(point) });
//// this isn't allowed because we must be able to continue to assign to query, type must remain IQueryable<LocatableFoo>
//query = query.Select( foo => new { Id = foo.Id, Geolocation = foo.Geolocation, Distance = foo.Geolocation.Distance(point) });
// this is what I though might work
query = query.SelectWithDistance(point);
this.Bar(query);
var results = query.ToList(); // run generated SQL
foreach (var result in results) //problematic duplicated calculation
{
result.Distance = result.Geolocation.Distance(point);
}
return results;
}
// fake method representing lots of app logic that relies on knowing the type of IQueryable<T>
private IQueryable<T> Bar<T>(IQueryable<T> foos)
{
if (typeof(T) == typeof(LocatableFoo))
{
return foos;
}
throw new ArgumentOutOfRangeException("foos");
}
}
public static class QueryableExtensions
{
public static IQueryable<T> SelectWithDistance<T>(this IQueryable<T> queryable, DbGeography pointToCalculateDistanceFrom)
{
/* WHAT DO?
* I'm pretty sure I could do some fanciness with Expression.Assign but I'm not sure
* What to get the entity with "distance" set
*/
return queryable;
}
}
What about replacing the line
var results = query.ToList();
with
var results = query
.Select(x => new {Item = x, Distance = x.Geolocation.Distance(point)}
.AsEnumerable() // now you just switch to app execution
.Select(x =>
{
x.Item.Distance = x.Distance; // you don't need to calculate, this should be cheap
return x.Item;
})
.ToList();
The Distance field is logically not part of your table, since it represents a distance to a dynamically specified point. As such it should not be part of your entity.
At this point if you want it being calculated on the db, you should create a Stored procedure, or a TVF (or sg else) that returns your entity extended with the distance. This way you can map the return type to an Entity. It is a clearer design to me btw.
So I'm using the 'CodeFirst' methodology of Entity Framework and I have mapping files to map the table information and add in things such as validation so for instance:
this.Property(t => t.AccountName)
.IsRequired()
.HasMaxLength(25);
This is using the Fluent API and I'm wondering how to get the property name by string instead of t.AccountName. I'm wanting to dynamically set these properties and I just don't know how to do that programmatically.
Without commenting on whether this is advisable or not(!), you can achieve what you need because the Property() method takes an expression tree as its parameter. Consider the following:
public class MyEntity
{
[Key]
public int MyEntityId { get; set; }
public string MyProperty { get; set; }
}
public class MyContext : DbContext
{
public DbSet<MyEntity> MyEntities
{
get { return this.Set<MyEntity>(); }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
var param = Expression.Parameter(typeof(MyEntity));
var propertyExpression = Expression.Lambda(Expression.Property(param, "MyProperty"), param);
modelBuilder.Entity<MyEntity>()
.Property((Expression<Func<MyEntity, string>>)propertyExpression)
.HasColumnName("Fish");
}
}
Here I build configuration for the MyProperty column, which I refer to by name in a lambda expression.
The above code works for string properties, but would require some modification to work for any property type. The cast to Expression<Func<MyEntity, string>> hard-codes the property type, but we can eliminate the cast using the dynamic language feature.
var param = Expression.Parameter(typeof(MyEntity));
dynamic propertyExpression = Expression.Lambda(Expression.Property(param, "MyProperty"), param);
modelBuilder.Entity<MyEntity>()
.Property(propertyExpression)
.HasColumnName("FishFace");
My entity has besides other properties Keyword property which is of type list of strings.
public virtual IList<string> Keywords { get; set; }
so I tried to map this property using conformist mapping by code approach simple as possible like this
Property(x => x.Keywords);
but I'm getting following exception
NHibernate.MappingException : Could not determine type for:
System.Collections.Generic.IList`1[[System.String, mscorlib,
Version=4.0.0.0,.....
You could map this to a private string field and then use string.Split in your Keywords getter to get a list.
public class MyClass {
private string _keywords;
public virtual IEnumerable<string> Keywords {
get { return _keywords.Split(','); }
set { _keywords = string.Join(value, ","); }
}
}
I am not familiar with mapping by code that NH uses (I use FluentNH) but your mapping would probably be something like this:
Map("_keywords", map => {
map.Access(Access.Field);
// ...
});