How to use expression to assign property in LINQ Select new - c#

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; }
}
}

Related

Linq to Entities could not be translated

I have this as my model (modified for obvious reasons)
public class Model
{
public int Id {get; set;}
public string Data {get; set;}
public TypeDomainValue ModelType {get; set;}
}
ModelType is known in the database as a string value only (no tables connected).
However if I want to filter on the value of TypeDomainValue in a linq statement
models.Where(c => c.ModelType.Value.Contains(searchString));
I get the error that the Linq expression cannot be translated.
I already tried using EF.Functions.Like which creates a similar error.
How can I get this to be translated properly as I do not want to load the entire table into memory.
Edit:
I use the following ValueConverter
public class ModelTypeDomainValueConverter : ValueConverter<ModelTypeDomainValue, string>
{
public ModelTypeDomainValueConverter([CanBeNull] ConverterMappingHints mappingHints = null) : base(ConvertToString(), ConvertToDomainValue(), mappingHints)
{
}
private static Expression<Func<ModelTypeDomainValue, string>> ConvertToString()
{
return x => x.Value;
}
private static Expression<Func<string, ModelTypeDomainValue>> ConvertToDomainValue()
{
return x => ModelTypeDomainValue.CreateByValue(x);
}
}
Which gets added with the following extension:
public static ModelBuilder UseValueConverter(this ModelBuilder modelBuilder, ValueConverter converter)
{
var type = converter.ModelClrType;
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
var properties = entityType.ClrType.GetProperties().Where(p => p.PropertyType == type);
foreach (var property in properties)
{
modelBuilder.Entity(entityType.Name).Property(property.Name).HasConversion(converter);
}
}
return modelBuilder;
}
Make ur life easy and Use owned properties in Entity framework core.
bcs TypeDomainValue smells like that one.
it has not its own table.
it don't have any primary key.
it resides in same table as Mode
so its own property.
I m not saying own properties can not have those but typically they are like them
and we use them as Value objects

How to add multiple arguments to lambda in C#

I am currently working on a library management project. It has 3 tables, TblUser,TblBook and TBlBookStatus. When a user reserves a book, the userID from TblUser,bookID from TblBook is stored in another table(TblBookStatus). I am joining and creating a new list on my database tables on c3. They are stored as lists in my C# code. The code below creates a list that it displays the reserved books.
Note: I did not connect bookID and userID as foreign key on TblBookStatus due to some project specific reasons
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using LibMan.Models.DB;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
namespace LibMan.Pages
{
public class LibraryModel : PageModel
{
private readonly LibMan.Models.DB.LibManContext _context;
public string Message { get; set; }
public LibraryModel(LibMan.Models.DB.LibManContext context)
{
_context = context;
DisplayBook = new List<TblBook>();
; }
public IList<TblBookStatus> TblBookStatus { get; set; }
public IList<TblBook> TblBook { get; set; }
public IEnumerable<TblBook> DisplayBook { get; set; }
public IList<TblUser> TblUser{ get; set; }
public async Task OnGetAsync()
{
TblBookStatus = await _context.TblBookStatus.ToListAsync();
TblBook = await _context.TblBook.ToListAsync();
TblUser = await _context.TblUser.ToListAsync();
if (TblBookStatus != null && TblBook != null){
DisplayBook = TblBook.Where(t => TblBookStatus.Any(ts => ts.BookId == t.BookId));
}
}
}
}
On the line DisplayBook = TblBook.Where(t => TblBookStatus.Any(ts => ts.BookId == t.BookId)); I display the reserved book via bookid, but not by user.
I want to display the reserved books of currently logged in user, not all users reserved books. So is there any possible way that I can implement this on the lambda statement above? I am imagining something like this, but my syntax is wrong.
DisplayBook = TblBook.Where(t => TblBookStatus.Any(ts => ts.BookId == t.BookId),a=> TblUser.Any(as=> as.UserId == a.UserId);
Thanks for the help!
{ EDIT - Answer was marked as correct since behavior was provided but didn't technically answer the question }
To answer the question: "How to add multiple arguments to lambda in C#", it is important to understand that in this case, the lambda expression is being converted to the delegate type, defined as a parameter to the linq methods such as Where() and Any(). The lamda must therefore match the "signature" of the delegate type which defines its parameters and return value. In most cases, linq methods expect a delegate called a Predicate, defined as delegate bool Predicate<in T>(T obj). The converted lambda must therefore, receive one parameter of type T, being the Enumerable element type, and return a single boolean.
In summary, you cannot add arguments to lambda expression that are to be converted to delegates, it is the delegate type that determines the parameters and return type. A "multiple argument" lambda may look like this, conforming to the signature of the delegate divide, two int in, one double out:
Func<int, int, double> divide = (x, y) => { return x / y; };
To provide the behavior you requested, an alternative approach you may like to consider:
(I don't have your models to test, so consider this pseudo, but looking at your query, you could try something like...)
var DisplayBook = (from book in TblBook
from bookStatus in TblBookStatus
from user in TblUser
where (book.BookId == bookStatus.BookId) && (user.UserId == currentReader) && (user.UserId == bookStatus.ReservedBy)
select book).FirstOrDefault();
Why not simply join the datasets by UserId?
DisplayBook = (from book in TblBook
join stat in TblBookStatus
on book.BookId equals stat.BookId
join usr in TblUser
on stat.UserId equals usr.UserId
where usr.UserId = <currentUserId>
Select book).ToList();
Try this :
//assumuning current userid is in below variable.
int CurrentUserId;// Fetch it from Session object if you store userid on login
DisplayBook = TblBook.Where(t => TblBookStatus.Any(ts => ts.BookId == t.BookId)
&& TblUser.Any(tb=>tb.UserId==CurrentUserId && tb.BookId==t.BookId));

NHibernate Like restriction on nested string properties

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();

AutoMapper throws exception when projecting to nullable enum

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));
});
}

Entity Framework ObjectQuery.Include()

I have an object with two objects as properties (User, PrimaryNode), both could potentially be null, see below:
public class Item
{
[Key]
public int ItemId { get; set; }
public string ItemName { get; set; }
public Node PrimaryNode { get; set; }
public User User { get; set; }
}
I'm using Entity Framework 6 to populate the Item object and using chained includes to populate the PrimaryNode and User objects within it.
When the first chained Include has a null object then the whole object returns as null, for example:
using (var db = new MyContext())
{
var item = db.Items.Include(i => i.User).Include(n => n.PrimaryNode).FirstOrDefault(i => i.ItemId == id);
}
If in the above example i.User is null then the item variable is null. Whats the best way of populating both the sub-objects in a way that if a sub-object is null then the parent object and the other sub-object will still be populated?
I don't think your issue is due to the Include calls. According with the documentation:
This extension method calls the Include(String) method of the
IQueryable source object, if such a method exists. If the source
IQueryable does not have a matching method, then this method does
nothing.
In other words is going to be translated to:
var item = db.Items.Include("User").Include("PrimaryNode").FirstOrDefault(i => i.ItemId == id);
My question is, are you sure you have an Item with that id properly related with existing rows in Users and PrimaryNodes tables in your DB?. When you call Include method at the end is going to be translated to a join, so if the FK of your relationship doesn't match with the PK that reference, your query should not return what you are expecting.
Anyways, if you want to try another variant to load related properties you can use Explicit Loading:
var item = db.Items.FirstOrDefault(i => i.ItemId == id);
context.Entry(item).Reference(p => p.PrimaryNode).Load();
context.Entry(item).Reference(p => p.User).Load();
I think it would be better if you use Lazy loading int his situation. Just make the User and PrimaryNode virtual:
public class Item
{
[Key]
public int ItemId { get; set; }
public string ItemName { get; set; }
public virtual Node PrimaryNode { get; set; }
public virtual User User { get; set; }
}
And then:
var db = new MyContext();
var item = db.Items.FirstOrDefault(i => i.ItemId == id);
As others have mentioned, I think your issue is not due to the Includes. However, I think the following method has value. It is functionally equivalent to what you are already doing with the chained includes, but I think it has several benefits including making the intention of the code clear to the user.
The includes can be placed in Extension methods:
using System.Data.Entity;
using System.Linq;
namespace Stackoverflow
{
public static class EntityExtensions
{
public static IQueryable<Item> IncludePrimaryNode(this IQueryable<Item> query)
{
// eager loading if this extension method is used
return query.Include(item => item.PrimaryNode);
}
public static IQueryable<Item> IncludeUser(this IQueryable<Item> query)
{
// eager loading if this extension method is used
return query.Include(item => item.User);
}
}
}
Then, you can use the extensions as follows:
using (var db = new MyContext())
{
var itemQuery = db.Items.IncludeUser();
itemQuery = itemQuery.IncludePrimaryNode();
var item = itemQuery.FirstOrDefault(i => i.Id == 1);
}
It's just another way of doing the same thing, but I like the clarity it adds to the code.

Categories

Resources