Creating helper method that can be used in LINQ (EF) - c#

Scenario:
I Have a table FooTable when column Foo is varchar(8) NOT NULL and the info in this column is like:
Foo
-----------
13940-00
13940-01
13940-02
13941-00
13941-01
Where the numeric part after the hyphen (-) always have two digits.
Problem:
I'm using Ado.net Entity Framework, and I created a class with 2 static methods to help get first and second part of the number:
public class HelperFoo
{
public static string Prefix(string value) { /* code here */ }
public static string Suffix(string value) { /* code here */ }
}
So now I can do something like this:
context.FooTable.Where(w => HelperFoo.Prefix(w.Foo) == "13940");
But, as you probably already know, this command line throws a NotSupportedException. It's because LINQ don't recognize HelperFoo.Prefix, so it can't convert the expression in SQL.
I can write a block of code in SQL that do the same that my methods of HelperFoo so I can create the SQL to my methos.
Question
Can I create something (class, method, or other) that makes LINQ knows my method when I executed the LINQ code?
EDITED
PS: I need something generic that works like a method or SQL function because I need to get this Prefix and Suffix in scenarios like Select, OrderBy, GroupBy and many others.

You could try creating your own IQueryable filters for the FooTable, a bit like this:
public static class Filters
{
public static IQueryable<FooTable> WithPrefix(this IQueryable<FooTable> item, string prefix)
{
return item.Where(i => i.Foo.StartsWith(prefix));
// note that this should be the same code you have in the
// Prefix() method inside HelperFoo...
}
}
Which you can use like this:
context.FooTable.WithPrefix("13940");
UPDATE: Sadly the second option here does not work:
Another option would be to have the helper methods not return a value but a Predicate<> for FooTable:
public class HelperFoo
{
public static Func<FooTable, bool> Prefix(string value)
{
return (i) => i.Foo.Substring(0, 5) == value;
}
public static Func<FooTable, bool> Suffix(string value) { /* code here */ }
}
And use it like this:
context.FooTable.Where(HelperFoo.Prefix("13940"));
Caveat: I'm not entirely sure the second method would not give you the same problem though.

With the the plethera of awnsers and you stating it needs to be more generic and you need the prefix and suffix avaliable to the Select, OrderBy, & GroupBy keywords, you should have the prefix and suffix in two different fields.
Foo Table
----------
Prefix | Suffix
----------------
10245 | 05
With that, you can query them individually to get what you want:
var resultSet = Db.Foo.Where(x => x.Suffix == "05").OrderBy(x => x.Prefix);
With this you can easily add a read-only property to get a formatted value:
public [partial] class Foo {
//Your other code
public string FormattedValue {
get { return Prefix + "-" + Suffix; }
}
}

You can use .StartsWith to check the prefix:
string prefix = "13940";
var result = context.FooTable.Where(w => w.Foo.StartsWith(prefix + "-"));
and .EndsWith to check the suffix:
string suffix = "02";
var result = context.FooTable.Where(w => w.Foo.EndsWith("-" + suffix));

You could create custom getters in your Foo class:
public class Foo
{
//your code
[NotMapped]
public string Prefix { get { return /*whatever*/; }
}
I think this should work.

You cannot filter your database selection based on a custom function - as you say, it cannot convert this to SQL.
For this specific problem, I could propose you use the StartsWith function, which does work on SQL server
context.FooTable.Where(w => w.Foo.StartsWith("13940"));

Use Microsoft.Linq.Translations.
It would look something like this:
partial class FooTable
{
private static readonly CompiledExpression<FooTable,string> prefixExpression
= DefaultTranslationOf<FooTable>.Property(e => e.Prefix).Is(e => e.Foo.Substring(0, 5));
public string Prefix
{
get { return prefixExpression.Evaluate(this); }
}
}
And queried like:
context.FooTable.Where(w => w.Prefix == "13940").WithTranslations();
Nuget gallery page
Documentation
EDIT: This solution works in Select, GroupBy, OrderBy.

Related

Get string path properties from object path clearly in C#

i need to have this result :
ProgrammeEtude.Description
So, i have done something like that and it work
modelMember = $"{nameof(Gabarit.ProgrammeEtude)}.{nameof(Gabarit.ProgrammeEtude.Description)}";
But it's ugly and if we have more than one class to reach, it will not be clean. So, i would like to know if it's possible to create a function to get the fullName property without the first class clearly. Only by calling a function
// Like that
modelMember = typeof(ProgrammeEtude).GetPropertyFullName(nameof(ProgrammeEtude.Description));
// Or like that
modelMember = GetPropertyFullName(ProgrammeEtude.Description);
Thank you!
Final solution help By Ecoron :
public void Test1()
{
var result = NameOf<Gabarit>(x => x.ProgrammeEtude.Description);
}
public static string NameOf<T>(Expression<Func<T, object>> selector)
{
return string.Join(".", selector.ToString().Split('.').Skip(1));
}
You can do it in runtime:
public class SomeClass
{
public SomeClass Other;
}
public class Tests
{
[Test]
public void Test1()
{
var result = NameOf<SomeClass>(x => x.Other.Other.Other);
}
public static string NameOf<T>(Expression<Func<T,object>> selector)
{
const string joinWith = ".";
return nameof(T) + joinWith + string.Join(joinWith, selector.ToString().Split('.').Skip(1));
}
}
Result: SomeClass.Other.Other.Other
You can play with this function to get desired result - with/out namespaces/indexes/separation select just start or end or skip something, etc.
Be aware that this working great only if you don't use some funky variables/enums inside which accessed by dot. For more correct version you should traverse expression yourself, but in this example Im just kinda lazy to write this all, and better to use simple approach.

Reuse expression to select single

I have Property and PropertyCompliance entities that look something like this...
public class Property{
public virtual ICollection<PropertyCompliance> ComplianceRecords {get;set;}
}
public class PropertyCompliance{
public virtual Property {get;set;}
public DateTime ComplianceDate {get;set;}
public ComplianceRating ComplianceValue {get;set;} //just an enum
}
In a number of places I need to find the PropertyCompliance row closest to a particular date.
var complianceRating = property.ComplianceRecords.OrderBy(cr=>cr.Date)
.Where(cr=>cr.ComplianceDate< checkDate).FirstOrDefault();
I know I could use an expression as such to filter the ComplianceRecords:
var complianceRating = property.ComplianceRecords.Where(SomeExpression)
.OrderBy(cr=>cr.ComplianceDate).FirstOrDefault()
However this isn't really reducing the amount of repetition as all it's doing is replacing the Where() statement.
Is there a way to apply the expression to the Property to allow this filtering to occur within another expression? Something like:
private static Expression<Func<Property, ComplianceRating>> PropertyComplianceForDate(DateTime checkDate)
{
return p => p.ComplianceRatings
.OrderByDescending(cr => cr.Date)
.First(cr => cr.Date <= checkDate).ComplianceRating;
}
public Expression<Func<Property, bool>> PropertyIsCompliant(DateTime checkDate)
{
return (p) => PropertyComplianceForDate(checkDate) == ComplianceRating.Compliant;
}
With "PropertyComplianceForDate" being an expression that could be translated to SQL to allow the PropertyIsCompliant expression to be used in SQL also.
You could declare an expression at class level and reuse it.
Alternatively you could make a method on your table or an extension method. Extension method (inside a static class) would look something like:
public static PropertyCompliance PropertyIsCompliant(this IEnumerable<PropertyCompliance> complianceRecords, DateTime checkDate) {
return complianceRatings
.OrderByDescending(cr => cr.Date)
.First(cr => cr.Date <= checkDate);
}

how to turn a string into a linq expression?

Similar: Convert a string to Linq.Expressions or use a string as Selector?
A similar one of that one: Passing a Linq expression as a string?
Another question with the same answer: How to create dynamic lambda based Linq expression from a string in C#?
Reason for asking something which has so many similar questions:
The accepted answer in those similar questions is unacceptable in that they all reference a library from 4 years ago (granted that it was written by code master Scott Gu) written for an old framework (.net 3.5) , and does not provide anything but a link as an answer.
There is a way to do this in code without including a whole library.
Here is some sample code for this situation:
public static void getDynamic<T>(int startingId) where T : class
{
string classType = typeof(T).ToString();
string classTypeId = classType + "Id";
using (var repo = new Repository<T>())
{
Build<T>(
repo.getList(),
b => b.classTypeId //doesn't compile, this is the heart of the issue
//How can a string be used in this fashion to access a property in b?
)
}
}
public void Build<T>(
List<T> items,
Func<T, int> value) where T : class
{
var Values = new List<Item>();
Values = items.Select(f => new Item()
{
Id = value(f)
}).ToList();
}
public class Item
{
public int Id { get; set; }
}
Note that this is not looking to turn an entire string into an expression such as
query = "x => x.id == somevalue";
But instead is trying to only use the string as the access
query = x => x.STRING;
Here's an expression tree attempt. I still don't know if this would work with Entity framework, but I figure it is worth a try.
Func<T, int> MakeGetter<T>(string propertyName)
{
ParameterExpression input = Expression.Parameter(typeof(T));
var expr = Expression.Property(input, typeof(T).GetProperty(propertyName));
return Expression.Lambda<Func<T, int>>(expr, input).Compile();
}
Call it like this:
Build<T>(repo.getList(), MakeGetter<T>(classTypeId))
If you can use an Expression<Func<T,int>> in place of a just a Func, then just remove the call to Compile (and change the signature of MakeGetter).
Edit:
In the comments, TravisJ asked how he could use it like this: w => "text" + w.classTypeId
There's several ways to do this, but for readability I would recommend introducing a local variable first, like this:
var getId = MakeGetter<T>(classTypeId);
return w => "text" + getId(w);
The main point is that the getter is just a function, and you can use it exactly like you normally would. Read Func<T,int> like this: int DoSomething(T instance)
Here is an extension method for you with my testing code (linqPad):
class test
{
public string sam { get; set; }
public string notsam {get; set; }
}
void Main()
{
var z = new test { sam = "sam", notsam = "alex" };
z.Dump();
z.GetPropertyByString("notsam").Dump();
z.SetPropertyByString("sam","john");
z.Dump();
}
static class Nice
{
public static void SetPropertyByString(this object x, string p,object value)
{
x.GetType().GetProperty(p).SetValue(x,value,null);
}
public static object GetPropertyByString(this object x,string p)
{
return x.GetType().GetProperty(p).GetValue(x,null);
}
}
results:
I haven't tried this, and not sure if it would work, but could you use something like:
b => b.GetType().GetProperty(classTypeId).GetValue(b, null);

Exposing a protected collection for LINQ queries

I have a class which contains a generic dictionary:
protected Dictionary<K,T> Data { get; set;}
I wish to allow clients of this class to perform LINQ queries without having to return the Dictionary. I had look at AsQueryable() but that doesn't seems to do what I wish.
An example of the code I would like to write would be:
typeRepo.Query().Where( x => x.name == "wire")
It seems like the dictionary is holding a model (T) by its key (K). The code you'll need is:
public IQueryable<T> Query
{
get { return Data.Values.AsQueryable(); }
}
Just add a public property like that:
public IEnumerable<KeyValuePair<K,T>> Query
{
get { return Data.AsEnumerable(); }
}
and it should work like you expect.

How to code this generic type C# snippet?

Simplified repeated code for each entity type is
public IList<entity1> GetEntity1(.. query params ..)
{
IQueryable<entity1> query = context.entity1;
query = from refDataType in query
where refDataType.Id > 0
select refDataType;
.
. plus more changes to query same for each entity
.
return query.ToList();
}
I wanted to create a generic function that creates the query, but not sure how to go about it?
ie in the following snippet, How do I code for ReturnAGenericQuery?
public IList<entity1> GetEntity1(.. query params ..)
{
IQueryable<entity1> query = context.entity1;
query = ReturnAGenericQuery of type entity1
return query.ToList();
}
public IList<entity2> GetEntity2(.. query params ..)
{
IQueryable<entity2> query = context.entity2;
query = ReturnAGenericQuery of type entity2
return query.ToList();
}
private IQueryable<T> ReturnAGenericQuery<T> ()
{
return IQueryable of entity1 or entity2
}
Your example is a little vague, but it looks like you need something along the lines of:
private IQueryable<T> ReturnAGenericQuery<T>(IQueryable<T> source)
where T : SomeBaseTypeForAllYourEntities
{
IQueryable<T> result =
from refDataType in source
where refDataType.Id > 0
select refDataType;
// Other stuff here
return result;
}
public IList<Entity1> GetEntity1( ... )
{
return ReturnAGenericQuery(context.entity1).ToList();
}
The reason you need the 'where T :' clause is because T needs to be a type that has a property 'Id' for your LINQ where-clause to work ... so you'd need to derive Entity1 and Entity2 from a base class that defines that property. If you need any other properties for the 'other stuff' these will need to be added to the base class too.
Addendum:
If context.entity1 (whatever collection that refers to) is not an IQueryable<entity1>, then you may need to use context.entity1.AsQueryable() instead.
Originally my query was wrong, it was supposed to query from refDataType in source rather than in result... duh.
Provided you have the right kind of inheritance structure (see below), this compiles fine.
public class SomeBaseTypeForAllYourEntities
{
public int Id { get; set; }
}
sealed public class Entity1 : SomeBaseTypeForAllYourEntities
{
... other properties, etc. ...
}
You need an interface.
private IQueryable<T, R> ReturnAGenericQuery<T> (T entity) where T : IQueryable, IHasRefDataType
{
return from DataType refDataType in entity
where refDataType.Id > 0
select refDataType;
}
struct DataType
{
public int Id;
}
public interface IHasRefDataType
{
DataType refDataType;
}

Categories