How can I make Dapper.NET map unmapped columns to a dictionary? - c#

I have tables that will have some known, some unknown columns, by allowing some dynamic SQL for the select. I'm looking to have any columns that don't have a matching property to be added to a dictionary. Based on another question on How can I make Dapper.NET throw when result set has unmapped columns?, but instead of throwing an error I'd like to just map those columns to a dictionary that will be on the class.
public SqlMapper.IMemberMap GetMember( string columnName ) {
var fallbackMappers = new List<SqlMapper.ITypeMap>();
fallbackMappers.Add( _defaultTypeMap );
var fallbackTypeMapper = new FallbackTypeMapper(fallbackMappers);
var member = fallbackTypeMapper.GetMember(columnName);
if(member == null ) {
throw new Exception();
}
return member;
}
//...
public static async Task<IEnumerable<T>> QueryAsyncOtherProps<T>(
this IDbConnection cnn,
string sql,
object param = null,
IDbTransaction transaction = null,
int? commandTimeout = default,
CommandType? commandType = default
)
where T : BaseSimpleType {
lock ( _lock ) {
if ( TypesThatHaveMapper.ContainsKey( typeof( T ) ) == false ) {
SqlMapper.SetTypeMap( typeof( T ), new NullTypeMapToOtherProperties<T>() );
TypesThatHaveMapper.Add( typeof( T ), null );
}
}
return await cnn.QueryAsync<T>( sql, param, transaction, commandTimeout, commandType );
}
My BaseSimpleType just contains a dictionary property called OtherProperties, so this only would get called on types that have that field and the name is consistent. Is there a different way to try to do this with Dapper?

I've got the same requirement, I wanted to map all missing fields of a records into a IDictionary<string, object> field. I've searched for enough long that I decided to implement it myself.
I've created a Dapper Extension class. It contains only a new method : .QueryWithExtraFields() and pass as an argument an Action<T, Dictionary> function that will be called for every row, the function will receive the class T instance and a IDictionary<string,object> of every field not mapped.
With this Action<> argument you can control to either set it inside your class or do something else with unmapped fields.
Expected Dapper Return Class
private class ReturnTestClass
{
public int Id { get; set; }
public string Name { get; set; }
public IDictionary<string,object> ExtraFields { get; set; }
}
Usage
var results = dbConnection.QueryWithExtraFields<ReturnTestClass>(
sql: "select 1 id, 'Test' as Name, 2 as FieldNumber2, 'other field' as FieldNumber3",
unmappedFieldsAction: (o, unmappedFields) => o.ExtraFields= unmappedFields); //Here is where you place the dictionary inside you type `T` class
DapperExtension.cs class code:
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Reflection;
using Dapper;
namespace Dapper {
public static class DapperExtension {
public static IReadOnlyList<T> QueryWithExtraFields<T>(
this DbConnection connection,
string sql,
Action<T, IDictionary<string, object>> unmappedFieldsAction,
object param = null,
DbTransaction transaction = null,
int? commandTimeout = null) where T : new()
{
Dictionary<string, PropertyInfo> propertySetters = typeof(T)
.GetProperties().Where(p => p.CanRead && p.CanWrite)
.ToDictionary(p => p.Name.ToLowerInvariant(), p => p);
return connection.Query(sql: sql, param: param, transaction: transaction, commandTimeout: commandTimeout.GetValueOrDefault(_defaultCommandTimeout))
.Select(row =>
{
IDictionary<string, object> rowDict = (IDictionary<string, object>) row;
T instance = new T();
rowDict.Where(o => propertySetters.ContainsKey(o.Key.ToLowerInvariant()))
.ToList().ForEach(o => propertySetters[o.Key.ToLowerInvariant()].SetValue(instance, o.Value));
unmappedFieldsAction(instance,
rowDict.Where(o => !propertySetters.ContainsKey(o.Key.ToLowerInvariant()))
.ToDictionary(k => k.Key,
v => v.Value));
return instance;
})
.ToArray();
}
}
}

Related

Implementing an SQL wrapper with entity mapping

I was trying to implement a wrapper in C# for SQL Server.
The normal workflow without wrapper is fetching the data into a datatable using direct SQL query and then mapping the columns by names into entities.
But as a wrapper is better to accept a mapping function which describes which column maps to which fields of an enumerable.
So, something like this :
public class UserInfo
{
public string FirstName{ get; set; }
public string LastName{ get; set; }
}
enumerableList = dbManager.Execute("** sql query **", /* some method to specify mapping */);
The enumerable will then contain the result from the database, mapped by the execute method. But I am unsure how to specify the mapping?
Even if I do then how to deal with the different data types for each column in the mapping?
If I correct understand, you want something like this:
public static List<T> ReadRows<T>(this SqlHelper sql, string query, SqlParameter[]
parameters, Func<SqlDataReader, T> projection)
{
var command = GetSqlCommand(query, CommandType.StoredProcedure, parameters);
return sql.ExecuteReader(command, reader => reader.Select(projection).ToList());
}
And use like:
var members = _unitOfWork.SqlHelper.ReadRows("spGetMembersByUserCompanies", parameters, _memberProjection);
readonly Func<SqlDataReader, MemberVm> _memberProjection = (r) => new MemberVm
{
InvitationId = r.Get<int?>("InvitationId"),
UserName = r.Get<string>("UserName"),
RoleName = r.Get<string>("RoleName"),
InvitationStatus = (InvitationStatus)r.Get<int>("InvitationStatus"),
LogoUrl = r.Get<string>("LogoUrl")
};
It is a piece of my code. I hope it is start to resolve your problem.
Implementing such a wrapper from bare bones is not that easy. But it is possible. There is an ADO wrapper library in Github : ADOWrapper
The implementation is pretty straightforward.
Short Answer
How to specify mapping between columns? - Use Func
How to deal with the different data types? You can write an extension
Long Answer
Make a generic method that takes as input the query and a Func Delegate (and optional third parameter to pass query parameters as dictionary)
public ICollection<T> Execute<T>(string query, Func<IDataReader, T> map, IDictionary<string, object> parameters = null)
{
ICollection<T> collection = new List<T>();
using (SqlConnection connection = CreateConnection())
{
connection.Open();
using (SqlCommand command = CreateCommand(connection, query, parameters))
{
using (IDataReader reader = await command.ExecuteReader())
{
while(reader.Read())
{
collection.Add(map.Invoke(reader));
}
}
}
connection.Close();
}
return collection;
}
Implementation of AddParameter AND CreateCommand:
private void AddParameter(IDbCommand command, string parameter, object value)
{
IDbDataParameter param = command.CreateParameter();
param.ParameterName = parameter;
param.Value = value;
command.Parameters.Add(param);
}
private SqlCommand CreateCommand(SqlConnection connection, string query,
IDictionary<string, object> parameters = null)
{
SqlCommand command = connection.CreateCommand();
command.CommandText = query;
if(parameters != null && parameters.Count > 0)
{
foreach(KeyValuePair<string, object> parameter in parameters)
{
AddParameter(command, parameter.Key, parameter.Value);
}
}
return command;
}
You can call the method like this :
public class UserInfo
{
public string FirstName{ get; set; }
public string LastName{ get; set; }
}
var enumerableList = manager.Execute("** query **",
(reader) =>
{
return new UserInfo()
{
FirstName = reader.Get<string>("FirstName"),
LastName = reader.Get<string>("LastName "),
};
})
The Get method makes it easy to manage different data types being fetched from column. But it is not an inbuilt method. So you need to write an extension for Data Reader:
public static class DataReaderExtension
{
public static T Get<T>(this IDataReader reader, string column) where T : IComparable
{
try
{
int index = reader.GetOrdinal(column);
if (!reader.IsDBNull(index))
{
return (T)reader[index];
}
}
catch (IndexOutOfRangeException) { throw new Exception($"Column, '{column}' not found."); }
return default(T);
}
public static IEnumerable<string> GetColumns(this IDataReader reader)
{
IEnumerable<string> columns = new List<string>();
if (reader != null && reader.FieldCount > 0)
{
columns = Enumerable.Range(0, reader.FieldCount)
.Select(index => reader.GetName(index))
.ToList();
}
return columns;
}
}

Get custom object from lambda expression

I have this method:
public static SiteSettingEntity
GetSettings<SiteSettingEntity>(string siteId,
Expression<Func<SiteSettingEntity, object>> properties)
{
// This method returns a SiteSettingEntity
// filled with the values selected in my expression
}
If want to invoke:
var _siteSetting = SiteSettingService.GetSettings<SiteSettingEntity>(SiteID,
s => new { s.BillCycleType, s.InvoiceComment,
s.AllowInvoiceDetailApproval, s.JobMinimumHours });
So this returns an object with the properties filled that was selected by my lambda expression.
My question is: how I do to return a custom object dynamically same using my expression?
Taking an example: when if you want to use .Select(s => new{ filed1 = data.FieldX}), you can just use the property .filed1 in return object.
Thanks!
If you are trying to return a dynamic object you can do something like this simple example of using ExpandoObject:
public class DuckFactory
{
public ExpandoObject GetDuck()
{
dynamic duck = new ExpandoObject();
duck.Name = "Fauntleroy";
return duck;
}
}
And then call it like:
dynamic duck = new DuckFactory().GetDuck();
// Check the property exists before using it
if (((IDictionary<string, Object>)duck).ContainsKey("Name"))
{
Console.WriteLine(duck.Name); // Prints Fauntleroy
}
else
{
Console.WriteLine("Poor duck doesn't have a name.");
}
Just remember you won't get the benefits of the object being strongly typed.
This is my original method:
public static T GetSettings<T>(string siteId, Expression<Func<SiteSettingEntity, object>> properties)
{
string query = $"SELECT TOP 1 {DbTool.GetSqlFields(properties)} FROM {SiteSettingEntity.TABLE_NAME} (NOLOCK) WHERE Sites.SiteID = #SiteID";
var parameters = new Dictionary<string, object>
{
{"SiteID", siteId},
};
var _data = DbTool.SqlExec<T>(PowerDetailContext.GetConnectionString(siteId), CommandType.Text, query, parameters);
return _data != null ? _data.FirstOrDefault() : default(T);
}
Ang the when I made the call I have this:
var _siteSetting = SiteSettingService.GetSettings<SiteSettingEntity>(SiteID, s => new { s.BillCycleType, s.InvoiceComment, s.AllowInvoiceDetailApproval, s.JobMinimumHours });
the var _siteSetting I want just have the properties SELECTED between new{} when I called. If I try use _siteSetting.OtherProperty, the "OtherProperty" won't available in the code.
After reading it again I suppose you are just trying to return SiteSettingEntity from GetSettings method.
public static SiteSettingEntity GetSettings<T>(string siteId, Expression<Func<SiteSettingEntity, object>> properties)
{
string query = $"SELECT TOP 1 {DbTool.GetSqlFields(properties)} FROM {SiteSettingEntity.TABLE_NAME} (NOLOCK) WHERE Sites.SiteID = #SiteID";
var parameters = new Dictionary<string, object>
{
{"SiteID", siteId},
};
var _data = DbTool.SqlExec<SiteSettingEntity>(PowerDetailContext.GetConnectionString(siteId), CommandType.Text, query, parameters);
return _data != null ? _data.FirstOrDefault() : default(SiteSettingEntity);
}

EF6: Use reference/lookup data with IQueryable

I want to use a pre-loaded lookup data from a list within a query. I need the query to return as IQueryable because it is used in a grid & being paged (not included here). I need the Labels to load from the lookup to avoid joins (more are in the actual code).
I know I could do a ToList() and post-process it, but I need the IQueryable. Here is the code:
// Run in intialization other code...
var contactLookups = new ContactsDbContext();
List<StatusType> _statusTypes = contactLookups.StatusTypes.ToList();
public IQueryable GetQuery()
{
var contactsCtx = new ContactsDbContext();
IQueryable query = contactsCtx.Select(contact => new
{
Id = contact.Id,
FullName = contact.FirstName + " " + contact.LastName,
CompanyName = contact.CompanyName,
Status = _statusTypes.FirstOrDefault(l => contact.StatusId == l.Id).Label
});
return query;
}
The code above throws an error because EF/LINQ can't form the in-memory list into SQL (for obvious reasons) - unless something is added/changed.
I want to somehow tell EF to apply the lookup after the SQL or something to that effect. I had read about doing something similar with EF Helper code and Expressions, but I can't find that article anymore.
Note, I'm flexible on the lookup itself. The only non-negotiable is the "contact.StatusId" (int), but the rest of the structure "_statusTypes.FirstOrDefault(l => contact.StatusId == l.Id)", like the List type, is open.
Any help is appreciated.
You can wrap EF's query into your own intercepting implementation of IQueryable, in which you can inject values of in-memory lookups prior to returning objects to application.
It may sound complex, but actually is not that hard to implement. The following needs to be done:
Mark the Status property in your entity as non-mapped (using Ignore() with Fluent API or [NotMapped] attribute on the property).
Write InterceptingQueryable<T> (let's name it so) implementation of IOrderedQueryable<T>, which wraps an IQueryable<T> object from the EF (returned by the Select method in your example).
Write InterceptingQueryProvider<T> implementation of IQueryProvider<T>, which in turn wraps query provider obtained from the EF.
Write InterceptingEnumerator<T> implementation of IEnumerator<T>, which relays to enumerator object returned by the EF. This enumerator will inject the value of Status property (can easily be generalized to populate any lookup property this way), right after it performs MoveNext, so that object returned by Current is fully populated.
The above chain is connected as follows:
InterceptingQueryable relays to EF's query object, passed in the constructor.
InterceptingQueryable.Provider property returns InterceptingQueryProvider.
InterceptingQueryable.GetEnumerator method returns InterceptingEnumerator.
InterceptingQueryProvider.CreateQuery methods obtain query object from the EF query provider, then return it wrapped in another instance of InterceptingQueryable.
InterceptingQueryProvider.Execute invokes Execute on the EF query provider, then in the case it gets an entity which is subject to lookup injection, it injects the lookup values in the same way as InterceptingEnumerator does (extract a method for reuse).
UPDATE
Here is the code:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
namespace Examples.Queryables
{
public class InterceptingQueryable<T> : IOrderedQueryable<T>
{
private readonly Action<T> _interceptor;
private readonly IQueryable<T> _underlyingQuery;
private InterceptingQueryProvider _provider;
public InterceptingQueryable(Action<T> interceptor, IQueryable<T> underlyingQuery)
{
_interceptor = interceptor;
_underlyingQuery = underlyingQuery;
_provider = null;
}
public IEnumerator<T> GetEnumerator()
{
return new InterceptingEnumerator(_interceptor, _underlyingQuery.GetEnumerator());
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public Expression Expression
{
get { return _underlyingQuery.Expression; }
}
public Type ElementType
{
get { return _underlyingQuery.ElementType; }
}
public IQueryProvider Provider
{
get
{
if ( _provider == null )
{
_provider = new InterceptingQueryProvider(_interceptor, _underlyingQuery.Provider);
}
return _provider;
}
}
private class InterceptingQueryProvider : IQueryProvider
{
private readonly Action<T> _interceptor;
private readonly IQueryProvider _underlyingQueryProvider;
public InterceptingQueryProvider(Action<T> interceptor, IQueryProvider underlyingQueryProvider)
{
_interceptor = interceptor;
_underlyingQueryProvider = underlyingQueryProvider;
}
public IQueryable CreateQuery(Expression expression)
{
throw new NotImplementedException();
}
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
var query = _underlyingQueryProvider.CreateQuery<TElement>(expression);
if ( typeof(T).IsAssignableFrom(typeof(TElement)) )
{
return new InterceptingQueryable<TElement>((Action<TElement>)(object)_interceptor, query);
}
else
{
return query;
}
}
public object Execute(Expression expression)
{
throw new NotImplementedException();
}
public TResult Execute<TResult>(Expression expression)
{
var result = _underlyingQueryProvider.Execute<TResult>(expression);
if ( result is T )
{
_interceptor((T)(object)result);
}
return result;
}
}
private class InterceptingEnumerator : IEnumerator<T>
{
private readonly Action<T> _interceptor;
private readonly IEnumerator<T> _underlyingEnumerator;
private bool _hasCurrent;
public InterceptingEnumerator(Action<T> interceptor, IEnumerator<T> underlyingEnumerator)
{
_interceptor = interceptor;
_underlyingEnumerator = underlyingEnumerator;
_hasCurrent = false;
}
public void Dispose()
{
_underlyingEnumerator.Dispose();
}
public bool MoveNext()
{
_hasCurrent = _underlyingEnumerator.MoveNext();
if ( _hasCurrent )
{
_interceptor(_underlyingEnumerator.Current);
}
return _hasCurrent;
}
public void Reset()
{
_underlyingEnumerator.Reset();
}
public T Current
{
get
{
return _underlyingEnumerator.Current;
}
}
object IEnumerator.Current
{
get { return Current; }
}
}
}
public static class QueryableExtensions
{
public static IOrderedQueryable<T> InterceptWith<T>(this IQueryable<T> query, Action<T> interceptor)
{
return new InterceptingQueryable<T>(interceptor, query);
}
}
}
And here is test case/example. First, we should not forget to add non-mapped Status property to Contact entity:
public partial class Contact
{
[NotMapped]
public StatusType Status { get; set; }
}
Then, we can use the interceptor mechanism as follows:
var contactLookups = contactsCtx.StatusTypes.ToList();
Action<Contact> interceptor = contact => {
contact.Status = contactLookups.FirstOrDefault(l => contact.StatusId == l.Id);
};
// note that we add InterceptWith(...) to entity set
var someContacts =
from c in contactsCtx.Contacts.InterceptWith(interceptor)
where c.FullName.StartsWith("Jo")
orderby c.FullName, c.CompanyName
select c;
Console.WriteLine("--- SOME CONTACTS ---");
foreach ( var c in someContacts )
{
Console.WriteLine(
"{0}: {1}, {2}, {3}={4}",
c.Id, c.FullName, c.CompanyName, c.StatusId, c.Status.Name);
}
which prints:
--- SOME CONTACTS ---
1: John Smith, Acme Corp, 3=Status Three
3: Jon Snow, The Wall, 2=Status Two
and the query gets translated into:
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[FullName] AS [FullName],
[Extent1].[CompanyName] AS [CompanyName],
[Extent1].[StatusId] AS [StatusId]
FROM [dbo].[Contacts] AS [Extent1]
WHERE [Extent1].[FullName] LIKE 'Jo%'
ORDER BY [Extent1].[FullName] ASC, [Extent1].[CompanyName] ASC
I'm not sure what is the benefit of avoiding the joins compared to the obvious drawback of not being able to process the whole query at the database side, but what you are asking for can be achieved by doing as much as possible (filtering, ordering, grouping, projection) with Linq to Entities, then turn it into IEnumerable and do the rest with Linq To Objects. You can always use Enumerable.AsQueryable to switch to IQueryable implementation over IEnumerable. Something like this
public IQueryable GetQuery()
{
var db = new ContactsDbContext();
var query = db.Contacts.Select(contact => new
{
Id = contact.Id,
FullName = contact.FirstName + " " + contact.LastName,
CompanyName = contact.CompanyName,
StatusId = contact.StatusId
})
.AsEnumerable()
.Select(contact => new
{
Id = contact.Id,
FullName = contact.FullName,
CompanyName = contact.CompanyName,
Status = _statusTypes.FirstOrDefault(l => contact.StatusId == l.Id).Label
})
.AsQueryable();
return query;
}

How to write a Custom DynamicObject class that supports object initializers

In the documentation for DynamicObject, there is an example of a DynamicDictionary that allows you to work with a dictionary as if it's a class with properties.
Here is the class (modified slightly for brevity):
public class DynamicDictionary : DynamicObject
{
Dictionary<string, object> _dictionary = new Dictionary<string, object>();
public int Count
{
get { return _dictionary.Count; }
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
string name = binder.Name.ToLower();
return _dictionary.TryGetValue(name, out result);
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
_dictionary[binder.Name.ToLower()] = value;
return true;
}
}
What I'd like to do is modify the class, so that I can do the following:
public class Test
{
public Test()
{
var result = Enumerable.Range(1, 5).Select(i => new DynamicDictionary
{
Id = i,
Foo = "Foo",
Bar = 2
});
}
}
Questions
Is this possible?
If yes, how?
DynamicObject provides TryCreateInstance(), which is meant for situations like this, but it's not usable from C#.
I see some ways around this:
Create a dynamic factory class. When you call its Create() method with named argumets, it passes it to the dictionary:
class DynamicDictionaryFactory : DynamicObject
{
public override bool TryInvokeMember(
InvokeMemberBinder binder, object[] args, out object result)
{
if (binder.Name == "Create")
{
// use binder.CallInfo.ArgumentNames and args
// to create the dynamic dictionary
result = …;
return true;
}
return base.TryInvokeMember(binder, args, out result);
}
}
…
dynamic factory = new DynamicDictionaryFactory();
dynamic dict = factory.Create(Id: 42);
Use non-dynamic collection initializer. This means having the property names as strings in the code:
// has to implement IEnumerable, so that collection initializer works
class DynamicDictionary
: DynamicObject, IEnumerable<KeyValuePair<string, object>>
{
public void Add(string name, object value)
{
m_dictionary.Add(name, value);
}
// IEnumerable implmentation and actual DynamicDictionary code here
}
…
dynamic dict = new DynamicDictionary { { "Id", 42 } };
Probably the closest to what you asked for would be to use nested object initializer. That is, the class will have a dynamic property (say, Values), whose properties can be set using object initializer:
class DynamicDictionary : DynamicObject
{
private readonly IDictionary<string, object> m_expandoObject =
new ExpandoObject();
public dynamic Values
{
get { return m_expandoObject; }
}
// DynamicDictionary implementation that uses m_expandoObject here
}
…
dynamic dict = new DynamicDictionary { Values = { Id = 42 } };
Using the open source ImpromptuInterface (via nuget) it has a Builder Syntax. that lets you do something close to the initialization syntax. Specifically after including ImpromptuInterface.Dynamic you could do
var result = Enumerable.Range(1, 5).Select(i => Build<DynamicDictionary>.NewObject
(
Id: i,
Foo: "Foo",
Bar: 2
));
There are other options too listed on that syntax page if you drop the <DynamicDictionary> it will use an ImpromptuDictionary which is essentially the same thing. And you can look at the source for the build syntax too.
It turns out, I was able to solve my problem by using Linq's built-in ToDictionary() method.
Example:
public Test()
{
var data = Enumerable.Range(1, 5).Select(i => new
{
Id = i,
Foo = "Foo",
Bar = 2
});
var result = data
.Select(d => d.GetType().GetProperties()
.Select(p => new { Name = p.Name, Value = p.GetValue(pd, null) })
.ToDictionary(
pair => pair.Name,
pair => pair.Value == null ? string.Empty : pair.Value.ToString()));
}
When I see a pattern of embedded language like this, I tend to use extension methods, so I could write the following code to achieve my goal:
public Test()
{
var data = Enumerable.Range(1, 5).Select(i => new
{
Id = i,
Foo = "Foo",
Bar = 2
}.AsDynamicDictionary());
}
Then I would define the extension method with the code to convert any object (including anonymously typed ones) to DynamicDictionary:
public static class DynamicDictionaryExtensions
{
public static DynamicDictionary AsDynamicDictionary(this object data)
{
if (data == null) throw new ArgumentNullException("data");
return new DynamicDictionary(
data.GetType().GetProperties()
.Where(p => p. && p.CanRead)
.Select(p => new {Name: p.Name, Value: p.GetValue(data, null)})
.ToDictionary(p => p.Name, p => p.Value)
);
}
}
You would have to implement a constructor in DynamicDictionary to receive a IDictionary<string, object>, but that's a piece of cake.

Creating a dynamic sort method for linq to entities [duplicate]

I found an example in the VS2008 Examples for Dynamic LINQ that allows you to use a SQL-like string (e.g. OrderBy("Name, Age DESC")) for ordering. Unfortunately, the method included only works on IQueryable<T>. Is there any way to get this functionality on IEnumerable<T>?
Just stumbled into this oldie...
To do this without the dynamic LINQ library, you just need the code as below. This covers most common scenarios including nested properties.
To get it working with IEnumerable<T> you could add some wrapper methods that go via AsQueryable - but the code below is the core Expression logic needed.
public static IOrderedQueryable<T> OrderBy<T>(
this IQueryable<T> source,
string property)
{
return ApplyOrder<T>(source, property, "OrderBy");
}
public static IOrderedQueryable<T> OrderByDescending<T>(
this IQueryable<T> source,
string property)
{
return ApplyOrder<T>(source, property, "OrderByDescending");
}
public static IOrderedQueryable<T> ThenBy<T>(
this IOrderedQueryable<T> source,
string property)
{
return ApplyOrder<T>(source, property, "ThenBy");
}
public static IOrderedQueryable<T> ThenByDescending<T>(
this IOrderedQueryable<T> source,
string property)
{
return ApplyOrder<T>(source, property, "ThenByDescending");
}
static IOrderedQueryable<T> ApplyOrder<T>(
IQueryable<T> source,
string property,
string methodName)
{
string[] props = property.Split('.');
Type type = typeof(T);
ParameterExpression arg = Expression.Parameter(type, "x");
Expression expr = arg;
foreach(string prop in props) {
// use reflection (not ComponentModel) to mirror LINQ
PropertyInfo pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);
object result = typeof(Queryable).GetMethods().Single(
method => method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2
&& method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), type)
.Invoke(null, new object[] {source, lambda});
return (IOrderedQueryable<T>)result;
}
Edit: it gets more fun if you want to mix that with dynamic - although note that dynamic only applies to LINQ-to-Objects (expression-trees for ORMs etc can't really represent dynamic queries - MemberExpression doesn't support it). But here's a way to do it with LINQ-to-Objects. Note that the choice of Hashtable is due to favorable locking semantics:
using Microsoft.CSharp.RuntimeBinder;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Runtime.CompilerServices;
static class Program
{
private static class AccessorCache
{
private static readonly Hashtable accessors = new Hashtable();
private static readonly Hashtable callSites = new Hashtable();
private static CallSite<Func<CallSite, object, object>> GetCallSiteLocked(
string name)
{
var callSite = (CallSite<Func<CallSite, object, object>>)callSites[name];
if(callSite == null)
{
callSites[name] = callSite = CallSite<Func<CallSite, object, object>>
.Create(Binder.GetMember(
CSharpBinderFlags.None,
name,
typeof(AccessorCache),
new CSharpArgumentInfo[] {
CSharpArgumentInfo.Create(
CSharpArgumentInfoFlags.None,
null)
}));
}
return callSite;
}
internal static Func<dynamic,object> GetAccessor(string name)
{
Func<dynamic, object> accessor = (Func<dynamic, object>)accessors[name];
if (accessor == null)
{
lock (accessors )
{
accessor = (Func<dynamic, object>)accessors[name];
if (accessor == null)
{
if(name.IndexOf('.') >= 0) {
string[] props = name.Split('.');
CallSite<Func<CallSite, object, object>>[] arr
= Array.ConvertAll(props, GetCallSiteLocked);
accessor = target =>
{
object val = (object)target;
for (int i = 0; i < arr.Length; i++)
{
var cs = arr[i];
val = cs.Target(cs, val);
}
return val;
};
} else {
var callSite = GetCallSiteLocked(name);
accessor = target =>
{
return callSite.Target(callSite, (object)target);
};
}
accessors[name] = accessor;
}
}
}
return accessor;
}
}
public static IOrderedEnumerable<dynamic> OrderBy(
this IEnumerable<dynamic> source,
string property)
{
return Enumerable.OrderBy<dynamic, object>(
source,
AccessorCache.GetAccessor(property),
Comparer<object>.Default);
}
public static IOrderedEnumerable<dynamic> OrderByDescending(
this IEnumerable<dynamic> source,
string property)
{
return Enumerable.OrderByDescending<dynamic, object>(
source,
AccessorCache.GetAccessor(property),
Comparer<object>.Default);
}
public static IOrderedEnumerable<dynamic> ThenBy(
this IOrderedEnumerable<dynamic> source,
string property)
{
return Enumerable.ThenBy<dynamic, object>(
source,
AccessorCache.GetAccessor(property),
Comparer<object>.Default);
}
public static IOrderedEnumerable<dynamic> ThenByDescending(
this IOrderedEnumerable<dynamic> source,
string property)
{
return Enumerable.ThenByDescending<dynamic, object>(
source,
AccessorCache.GetAccessor(property),
Comparer<object>.Default);
}
static void Main()
{
dynamic a = new ExpandoObject(),
b = new ExpandoObject(),
c = new ExpandoObject();
a.X = "abc";
b.X = "ghi";
c.X = "def";
dynamic[] data = new[] {
new { Y = a },
new { Y = b },
new { Y = c }
};
var ordered = data.OrderByDescending("Y.X").ToArray();
foreach (var obj in ordered)
{
Console.WriteLine(obj.Y.X);
}
}
}
Too easy without any complication:
Add using System.Linq.Dynamic; at the top.
Use vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();
Edit: to save some time, the System.Linq.Dynamic.Core (System.Linq.Dynamic is deprecated) assembly is not part of the framework, but can be installed from nuget: System.Linq.Dynamic.Core
Just stumbled across this question.
Using Marc's ApplyOrder implementation from above, I slapped together an Extension method that handles SQL-like strings like:
list.OrderBy("MyProperty DESC, MyOtherProperty ASC");
Details can be found here: http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html
I guess it would work to use reflection to get whatever property you want to sort on:
IEnumerable<T> myEnumerables
var query=from enumerable in myenumerables
where some criteria
orderby GetPropertyValue(enumerable,"SomeProperty")
select enumerable
private static object GetPropertyValue(object obj, string property)
{
System.Reflection.PropertyInfo propertyInfo=obj.GetType().GetProperty(property);
return propertyInfo.GetValue(obj, null);
}
Note that using reflection is considerably slower than accessing the property directly, so the performance would have to be investigated.
Just building on what others have said. I found that the following works quite well.
public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> input, string queryString)
{
if (string.IsNullOrEmpty(queryString))
return input;
int i = 0;
foreach (string propname in queryString.Split(','))
{
var subContent = propname.Split('|');
if (Convert.ToInt32(subContent[1].Trim()) == 0)
{
if (i == 0)
input = input.OrderBy(x => GetPropertyValue(x, subContent[0].Trim()));
else
input = ((IOrderedEnumerable<T>)input).ThenBy(x => GetPropertyValue(x, subContent[0].Trim()));
}
else
{
if (i == 0)
input = input.OrderByDescending(x => GetPropertyValue(x, subContent[0].Trim()));
else
input = ((IOrderedEnumerable<T>)input).ThenByDescending(x => GetPropertyValue(x, subContent[0].Trim()));
}
i++;
}
return input;
}
I was trying to do this but having problems with Kjetil Watnedal's solution because I don't use the inline linq syntax - I prefer method-style syntax. My specific problem was in trying to do dynamic sorting using a custom IComparer.
My solution ended up like this:
Given an IQueryable query like so:
List<DATA__Security__Team> teams = TeamManager.GetTeams();
var query = teams.Where(team => team.ID < 10).AsQueryable();
And given a run-time sort field argument:
string SortField; // Set at run-time to "Name"
The dynamic OrderBy looks like so:
query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField));
And that's using a little helper method called GetReflectedPropertyValue():
public static string GetReflectedPropertyValue(this object subject, string field)
{
object reflectedValue = subject.GetType().GetProperty(field).GetValue(subject, null);
return reflectedValue != null ? reflectedValue.ToString() : "";
}
One last thing - I mentioned that I wanted the OrderBy to use custom IComparer - because I wanted to do Natural sorting.
To do that, I just alter the OrderBy to:
query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField), new NaturalSortComparer<string>());
See this post for the code for NaturalSortComparer().
I've stumble this question looking for Linq multiple orderby clauses
and maybe this was what the author was looking for
Here's how to do that:
var query = pets.OrderBy(pet => pet.Name).ThenByDescending(pet => pet.Age);
Use dynamic linq
just add using System.Linq.Dynamic;
And use it like this to order all your columns:
string sortTypeStr = "ASC"; // or DESC
string SortColumnName = "Age"; // Your column name
query = query.OrderBy($"{SortColumnName} {sortTypeStr}");
After a lot of searching this worked for me:
public static IEnumerable<TEntity> OrderBy<TEntity>(this IEnumerable<TEntity> source,
string orderByProperty, bool desc)
{
string command = desc ? "OrderByDescending" : "OrderBy";
var type = typeof(TEntity);
var property = type.GetProperty(orderByProperty);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var orderByExpression = Expression.Lambda(propertyAccess, parameter);
var resultExpression = Expression.Call(typeof(Queryable), command,
new[] { type, property.PropertyType },
source.AsQueryable().Expression,
Expression.Quote(orderByExpression));
return source.AsQueryable().Provider.CreateQuery<TEntity>(resultExpression);
}
First Install Dynamic
Tools --> NuGet Package Manager --> Package Manager Console
install-package System.Linq.Dynamic
Add Namespace using System.Linq.Dynamic;
Now you can use OrderBy("Name, Age DESC")
You could add it:
public static IEnumerable<T> OrderBy( this IEnumerable<T> input, string queryString) {
//parse the string into property names
//Use reflection to get and sort by properties
//something like
foreach( string propname in queryString.Split(','))
input.OrderBy( x => GetPropertyValue( x, propname ) );
// I used Kjetil Watnedal's reflection example
}
The GetPropertyValue function is from Kjetil Watnedal's answer
The issue would be why? Any such sort would throw exceptions at run-time, rather than compile time (like D2VIANT's answer).
If you're dealing with Linq to Sql and the orderby is an expression tree it will be converted into SQL for execution anyway.
Here's something else I found interesting.
If your source is a DataTable, you can use dynamic sorting without using Dynamic Linq
DataTable orders = dataSet.Tables["SalesOrderHeader"];
EnumerableRowCollection<DataRow> query = from order in orders.AsEnumerable()
orderby order.Field<DateTime>("OrderDate")
select order;
DataView view = query.AsDataView();
bindingSource1.DataSource = view;
reference: http://msdn.microsoft.com/en-us/library/bb669083.aspx (Using DataSetExtensions)
Here is one more way to do it by converting it to a DataView:
DataTable contacts = dataSet.Tables["Contact"];
DataView view = contacts.AsDataView();
view.Sort = "LastName desc, FirstName asc";
bindingSource1.DataSource = view;
dataGridView1.AutoResizeColumns();
You can convert the IEnumerable to IQueryable.
items = items.AsQueryable().OrderBy("Name ASC");
An alternate solution uses the following class/interface. It's not truly dynamic, but it works.
public interface IID
{
int ID
{
get; set;
}
}
public static class Utils
{
public static int GetID<T>(ObjectQuery<T> items) where T:EntityObject, IID
{
if (items.Count() == 0) return 1;
return items.OrderByDescending(u => u.ID).FirstOrDefault().ID + 1;
}
}
Thanks to Maarten (Query a collection using PropertyInfo object in LINQ) I got this solution:
myList.OrderByDescending(x => myPropertyInfo.GetValue(x, null)).ToList();
In my case I was working on a "ColumnHeaderMouseClick" (WindowsForm) so just found the specific Column pressed and its correspondent PropertyInfo:
foreach (PropertyInfo column in (new Process()).GetType().GetProperties())
{
if (column.Name == dgvProcessList.Columns[e.ColumnIndex].Name)
{}
}
OR
PropertyInfo column = (new Process()).GetType().GetProperties().Where(x => x.Name == dgvProcessList.Columns[e.ColumnIndex].Name).First();
(be sure to have your column Names matching the object Properties)
Cheers
You can use this:
public List<Book> Books(string orderField, bool desc, int skip, int take)
{
var propertyInfo = typeof(Book).GetProperty(orderField);
return _context.Books
.Where(...)
.OrderBy(p => !desc ? propertyInfo.GetValue(p, null) : 0)
.ThenByDescending(p => desc ? propertyInfo.GetValue(p, null) : 0)
.Skip(skip)
.Take(take)
.ToList();
}
This answer is a response to the comments that need an example for the solution provided by #John Sheehan - Runscope
Please provide an example for the rest of us.
in DAL (Data Access Layer),
The IEnumerable version:
public IEnumerable<Order> GetOrders()
{
// i use Dapper to return IEnumerable<T> using Query<T>
//.. do stuff
return orders // IEnumerable<Order>
}
The IQueryable version
public IQueryable<Order> GetOrdersAsQuerable()
{
IEnumerable<Order> qry= GetOrders();
// use the built-in extension method AsQueryable in System.Linq namespace
return qry.AsQueryable();
}
Now you can use the IQueryable version to bind, for example GridView in Asp.net and benefit for sorting (you can't sort using IEnumerable version)
I used Dapper as ORM and build IQueryable version and utilized sorting in GridView in asp.net so easy.
You can define a dictionary from string to Func<> like this :
Dictionary<string, Func<Item, object>> SortParameters = new Dictionary<string, Func<Item, object>>()
{
{"Rank", x => x.Rank}
};
And use it like this :
yourList.OrderBy(SortParameters["Rank"]);
In this case you can dynamically sort by string.
you can do it like this for multiple order by
IOrderedEnumerable<JToken> sort;
if (query.OrderBys[0].IsDESC)
{
sort = jarry.OrderByDescending(r => (string)r[query.OrderBys[0].Key]);
}
else
{
sort = jarry.OrderBy(r =>
(string) r[query.OrderBys[0].Key]);
}
foreach (var item in query.OrderBys.Skip(1))
{
if (item.IsDESC)
{
sort = sort.ThenByDescending(r => (string)r[item.Key]);
}
else
{
sort = sort.ThenBy(r => (string)r[item.Key]);
}
}
Convert List to IEnumerable or Iquerable, add using System.LINQ.Dynamic namespace, then u can mention the property names in comma seperated string to OrderBy Method which comes by default from System.LINQ.Dynamic.
I am able to do this with the code below. No need write long and complex code.
protected void sort_array(string field_name, string asc_desc)
{
objArrayList= Sort(objArrayList, field_name, asc_desc);
}
protected List<ArrayType> Sort(List<ArrayType> input, string property, string asc_desc)
{
if (asc_desc == "ASC")
{
return input.OrderBy(p => p.GetType()
.GetProperty(property)
.GetValue(p, null)).ToList();
}
else
{
return input.OrderByDescending(p => p.GetType()
.GetProperty(property)
.GetValue(p, null)).ToList();
}
}
If you are using Specification (such as Ardalis Specification)
using Microsoft.EntityFrameworkCore;
namespace TestExtensions;
public static class IQueryableExtensions
{
public static void ApplyOrder<T>(ISpecificationBuilder<T> query, string propertyName, bool ascendingOrder)
{
if (ascendingOrder)
query.OrderBy(T => EF.Property<object>(T!, propertyName));
else
query.OrderByDescending(T => EF.Property<object>(T!, propertyName));
}
}
With Net6 and EF
.AsQueryable().OrderBy((ColumnOrder.Column, ColumnOrder.Dir));
var result1 = lst.OrderBy(a=>a.Name);// for ascending order.
var result1 = lst.OrderByDescending(a=>a.Name);// for desc order.

Categories

Resources