I'm using a modified version of LinqKit in order to have my extensions at one place.
Therefore I have a singel part of each of my entity-classes where I define my expression, e.g. for tblMain:
public partial class tblMain
{
public static Expression<Func<tblMain, bool>> IsVisible => (e) => e.MainStatus == "Visible";
}
In a query I am now able to write something like this
var visibleEntries = dbContext
.tblMain
.AsExpandable()
.Where(m => tblMain.IsVisible.Invoke(m))
.ToList();
which will return me all of the visible entries of the table tblMain.
I wondered if there is any way to not have this a static property. This would enable me to use Interfaces like IVisibilityEntity to force a public IsVisible property on specific types.
For now I've ended up with:
public Expression<Func<bool>> IsVisible2 => Expression.Lambda<Func<bool>>(Expression.Invoke(IsVisible, Expression.Variable(typeof(tblMain))));
but this throws me an exception
InvalidOperationException: variable 'm' of type 'tblMain' referenced from scope '', but it is not defined.
Same when using Expression.Constant(this, typeof(tblMain)) as second parameter.
What I would love to have is a query like
var visibleEntries = dbContext
.tblMain
.AsExpandable()
.Where(m => m.IsVisible.Invoke())
.ToList();
This may seem not that much of a change. But I really want to be able to use the interface-feature to describe my underlying database-model.
With interfaces it also allows checks, e.g. if(myEntity is IVisibilityEntity) to do specific visibility-stuff.
Any ideas if this is possible and how this can be achieved?
Edit 1
As of the first comment. I use LinqKit to enable the same logic for sub-queries:
var visibleEntries = dbContext
.tblMain
.AsExpandable()
.Where(m => tblMain.IsVisible.Invoke(m))
.Select(m => new
{
VisibleSubs = m.tblSub.Where(s => tblSub.IsVisible.Invoke(s)).Select(s => new
{
// ...
})
})
.ToList();
The query above would give me all visible main-entries and their related (and also visible) sub-entries. But this is not possible with simply writing m.tblSub.Where(tblSub.IsVisible) as this shows
CS1929 'ICollection<tblSub>' does not contain a definition for 'Where' and the best extension method overload 'Queryable.Where<tblSub>(IQueryable<tblSub>, Expression<Func<tblSub, bool>>)' requires a receiver of type 'IQueryable<tblSub>'
what about defining your properties like this: https://damieng.com/blog/2009/06/24/client-side-properties-and-any-remote-linq-provider
It is essentially property definition in the form of:
partial class Employee {
private static readonly CompiledExpression<Employee,string> fullNameExpression
= DefaultTranslationOf<Employee>.Property(e => e.FullName).Is(e => e.Forename + " " + e.Surname);
private static readonly CompiledExpression<Employee,int> ageExpression
= DefaultTranslationOf<Employee>.Property(e => e.Age).Is(e => DateTime.Now.Year - e.BirthDate.Value.Year - (((DateTime.Now.Month < e.BirthDate.Value.Month) || (DateTime.Now.Month == e.BirthDate.Value.Month && DateTime.Now.Day < e.BirthDate.Value.Day)) ? 1 : 0)));
public string FullName {
get { return fullNameExpression.Evaluate(this); }
}
public int Age {
get { return ageExpression.Evaluate(this); }
}
}
and the use the Microsoft.Linq.Translations package to resolve.
Related
So I have a property that I will parse into the Select method. This will work with one parameter but can I make it work with two, and if I can't what would be the approach? I am using EF Core 3.1.8. with the SqlServer 3.1.8 package.
private static Expression<Func<ClassOne, bool, ClassTwo> Summary
{
get
{
return (p, myBool) => new ClassTwo()
{
ListOfItems = p.ListWithMyItems.Where(i => i.Field == myBool)
}
}
}
This is my Expression. I query with this method.
public async Task<ClassTwo> GetSummaryAsync(bool isAdmin = false)
{
return await _context
.DatabaseTable // type of DbSet<ClassOne>
.Select(Summary) // how do I parse isAdmin to Summary?
.ToListAsync();
}
So I hope you can see my problem. I want to avoid the where clause in the method because I have at least 10 other methods that use this Expression in different ways and also in my case it would become an nested Where which is not possible which straight querying. I don't want C# to do the work for me, let SQL Server handle that.
Thanks in advance!
EDIT:
I tried this in the GetSummaryAsync but it is not possible:
.Select(i => (i, isAdmin))
Define extension method
public static class Extensions {
public static IQueryable<ClassTwo> Summary(this IQueryable<ClassOne> one, bool myBool)
{
return one.Select(p => new ClassTwo
{
ListOfItems = p.ListWithMyItems.Where(i => i.Field == myBool)
});
}
}
And use it like this.
public async Task<ClassTwo> GetSummaryAsync(bool isAdmin = false)
{
return await _context
.DatabaseTable
.Summary(isAdmin)
.ToListAsync();
}
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.) ...
});
So I read that it is bad design to have an interface parameter be checked as sending in an interface member is supposed to associate itself as an contract that the only the interface members are going to be used. As such I thought to simply overload the method. This seems like it would quickly spiral out of control however if multiple objects that implement the interface needs different implementations of the method.
public IArtist FindArtist(IArtist artist)
{
var activeArtist = _database.Artists.FirstOrDefault(a => a.Name == artist.Name);
return activeArtist;
}
public IArtist FindArtist(SpotifyArtist artist)
{
var spotifyArtists = _database.Artists.Where(a => a is SpotifyArtist).Cast<SpotifyArtist>();
SpotifyArtist activeArtist = spotifyArtists.FirstOrDefault(a => a.Name == artist.Name && a.SpotifyID == artist.SpotifyID);
return activeArtist;
}
In the above code snippet, when I need to call the FindArtist with a SpotifyArtist object (which implements IArtist), the function should look for an object that has the same Name and also SpotifyID. Whereas if it is any other type of IArtist, it is to just return based on the name (probably will modify it to prioritize non-SpotifyArtist objects later). Do you have any suggestions to what I should be doing here instead?
EDIT TableImplementation:
public class MusicObjectTable
{
public List<IArtist> Artists;
public List<IAlbum> Albums;
public List<ITrack> Tracks;
public MusicObjectTable()
{
this.Artists = new List<IArtist>();
this.Albums = new List<IAlbum>();
this.Tracks = new List<ITrack>();
}
public MusicObjectTable(List<IArtist> artists, List<IAlbum> albums, List<ITrack> tracks)
{
this.Artists = artists;
this.Albums = albums;
this.Tracks = tracks;
}
}
}
OK this is not directly answering your question, because I think it might be a little XY.
However, this is what I'd do, let relational data be relational data. I.e use a relational database.
Artists are a key concept, there is only of them. Make an Artists table.
One Artist may have multiple Spotify accounts, so we might need a Spotify table to hold things like urls, band name, pictures, whatever… I mean one Artist can be in multiple bands right. So the solve here is to have a one-to-many relationship with Artist to Spotify.
You could have the same with YouTube, one artist could have many videos. One-to-Many again.
Every time you need to add more connections (relationships) you just add a new table, you don’t have to expand on the one table (you don't have to keep adding loosely-related junk to the artist table) the only thing you need to add (if you wanted is a navigation collection property).
An example usage is this in a simple pseudo-code way
var artist = db.Include(x => x.SpotifyAccounts)
.Include(x => x.YouTubeVideos)
.Include(x => x.EbayStores)
.FirstOrDefault(x => x.Name == SearchName);
Console.WriteLine(artist.Name);
if(artist.SpotifyAccounts.Any)
foreach(var item in artist.SpotifyAccounts)
Console.WriteLine(" -- " + item.Url);
var spotify = db.SpotifyAccounts
.Include(x => x.Arists)
.FirstOrDefault(x => x.Id == SpotifyId);
Console.WriteLine(spotify.Id);
Console.WriteLine(spotify.Url);
Console.WriteLine(spotify.Artist.Name);
Note: This does away with your search methods and your inheritance and replaced with relationships. What are the down sides? Well not everything is in the one table, inheritance is really not an option. The pros are, as your models become more complex you just add relationships, you can add as many as you like, which actually doesn't touch your Artist table (unless it's one to one).
When searching for an Artist name, you have access to everything they have. if you search for a particular Spotify account you always have access to the Artist.
However this really depends how far you want to go down the rabbit-hole. If this is going to be any kind of system, I think relational is the way to go. It's scalable, and it's self consistent and it's how most large systems work.
It would be bad form if IArtist behaved differently for different types. It's OK if the implementation is different, and it does different things behind the scenes; it is not OK if the functional behavior differs when viewed as a black box. The contract promises something, and it ought to be consistent.
If you happen to be writing code where you know you have a SpotifyArtist, you also know you can call a different method, if you want to.
public IArtist FindArtistByName(IArtist artist)
{
var activeArtist = _database.Artists.FirstOrDefault(a => a.Name == artist.Name);
return activeArtist;
}
public IArtist FindArtistByNameAndID(SpotifyArtist artist)
{
var spotifyArtists = _database.Artists.Where(a => a is SpotifyArtist).Cast<SpotifyArtist>();
SpotifyArtist activeArtist = spotifyArtists.FirstOrDefault(a => a.Name == artist.Name && a.SpotifyID == artist.SpotifyID);
return activeArtist;
}
And you can also provide a convenience method:
public IArtist FindArtistByBestMethod(IArtist artist)
{
if (artist is SpotifyArtist)
{
return repo.FindArtistByNameAndID((SpotifyArtist)artist);
}
else
{
return repo.FindArtistByName(artist);
}
}
An answer using filters:
The filter:
public ArtistFilter
{
public string SearchString { get; set; }
public Type? Type { get; set; }
public int? MinimumRating { get; set; }
}
MinimumRating is just to show you how to extend the filter easily.
Secondly you have a method that converts the filter into a function:
private static Expression<Func<IArtist, bool>> CreateArtistFilterExpression(ArtistFilter filter)
{
Expression<Func<IArtist, bool>> expression = x => true;
if(filter == null)
{
return expression;
}
if(!string.IsNullOrWhiteSpace(filter.SearchString))
{
expression = expression.And(x => x.Name.Contains(filter.SearchString));
}
if(filter.Type != null)
{
expression = expression.And(x => x is Type.Value);
}
if(filter.MinimumRating != null)
{
expression = expression.And(x => x.Rating >= filter.MinimumRating);
}
return expression;
}
The And-extension-method is pretty small:
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2) {
var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters);
}
A last method reduces redundant code:
public List<IArtist> GetArtistsByFilter(ArtistFilter filter)
{
var expression = CreateArtistFilterExpression(filter);
return _database.Artists.Where(expression).ToList();
}
And you can get the best matching one like this:
var filter = new ArtistFilter {
SearchString = "Lennon",
Type = typeof(SpotifyArtist)
};
var matchingArtists = GetArtistsByFilter(filter);
var bestMatching = matchingArtists.FirstOrDefault();
You ignore the rating then. By setting the MinimumRating as well you can also filter for only-good artists.
Note: I typed most of this in stackoverflow, so I might have missed a semicolon or so.
For simplicity, let's guess there are two entities:
public class Entity
{
public string Value { get; set; }
public ChildEntity Child { get; set; }
}
public class ChildEntity
{
public string Value { get; set; }
}
I need to find all entities where either Value or Child.Value are insensitive like specified string query.
That's what I have by now:
Entity entity = null;
ChildEntity child = null;
var nhibernateQuery = session
.QueryOver(() => entity)
.JoinAlias(() => entity.Child, () => child);
if (!string.IsNullOrWhiteSpace(query))
{
nhibernateQuery = nhibernateQuery
.Where(
Restrictions.Or(
Restrictions.On(() => entity).IsInsensitiveLike(query),
Restrictions.On(() => child).IsInsensitiveLike(query)
)
);
}
return nhibernateQuery.List().ToArray();
I get the NullReferenceException - it seems like Restrictions.On does not handle alias correctly.
Another approach that I have tried is .JoinQueryOver() which is suggested by this post:
return session
.QueryOver<Entity>()
.Where(Restrictions.InsensitiveLike("Value", query))
.JoinQueryOver(e => e.Child)
.Where(Restrictions.InsensitiveLike("Value", query));
This thing works except for one thing: it returns all items where both Value and Child.Value are like query. I need the same thing, but with or logic.
What should be done to make it work? I would like to use .QueryOver(), either with or without aliases, but without .CreateCriteria(), but will appreciate if you help me with any working solution.
The problem has been solved by using NHibernate LINQ .Query<>().
It can resolve all joins and relations by itself.
At the same time, .Contains() method is translated into case-insensitive LIKE statement for MS SQL which suits me needs.
var nhibernateQuery = session
.Query<Entity>();
if (!string.IsNullOrWhiteSpace(query))
{
nhibernateQuery = nhibernateQuery
.Where(e => e.Value.Contains(query) || e.Child.Value.Contains(query));
}
return nhibernateQuery.ToArray();
I am trying to refactor Linq to Entity query to prevent writing duplicate code but I can't avoid a "LINQ to Entities does not recognize the method" exception. The reason is IsUserActive method.
I am providing the code below as example
public static bool IsUserActive(this User user)
{
return user.Orders.Any(c => c.Active && (c.TransactionType == TransactionType.Order || c.TransactionType == TransactionType.Subscription));
}
public IQueryable<UserView> GetView()
{
return context.users.Select(p => new UserView
{
Id = p.Id,
Active =p.IsUserActive()
});
}
Is it possible to refactor Linq code to prevent duplication in my situation?
I can't use IList instead of IQueryable.
I know why this exception happens, no need to explain.
If you want to use your method in a lot of places, I don't think you can do anything except store it as an expression and then referring to that expression wherever you were calling the original method:
public static Expression<Func<User, bool>> IsUserActive = user =>
user.Orders.Any(c => c.Active &&
(c.TransactionType == TransactionType.Order ||
c.TransactionType == TransactionType.Subscription));
public IQueryable<UserView> GetView()
{
return context.users.Select(p => new UserView
{
Id = p.Id,
Active = IsUserActive(p)
});
}
EDIT: OK, so this doesn't work. You can't just "call" an expression like this. The only alternative I can think of is to turn your whole Select parameter into an expression:
public static Expression<Func<User, UserView>> UserToUserView = user =>
new UserView
{
Id = user.Id,
Active = user.Orders.Any(c => c.Active &&
(c.TransactionType == TransactionType.Order ||
c.TransactionType == TransactionType.Subscription)
};
public IQueryable<UserView> GetView()
{
return context.users.Select(UserToUserView);
}
This is little tricky, but this is how we are doing it. You can combine multiple IQueryables into one as shown below.
public static IQueryable<Order> ActiveOrdersQuery(this MyDBEntities db)
{
return db.orders.Where(
o=> o.Active &&
(o.TransactionType == TransactionType.Order ||
o.TransactionType == TransactionType.Subscription )));
}
public IQueryable<UserView> GetView()
{
var q = context.ActiveOrdersQuery();
return context.users.Select( x=> new UserView{
Id = x.Id,
Active = q.Any( o=> o.UserId == x.Id)
});
}
The only problem is you have to explicitly compare foreign keys. But this results in exactly same SQL and will perform same.
This way you can refactor common queries and use them inside your views.