I want to add a post with category, my Add action in BlogController is like this :
private readonly IBlogPostRepository _blogPostRepository;
private readonly ICategoryRepository _categoryRepository;
public BlogController()
{
_blogPostRepository = new BlogPostRepository(new SiteContext());
_categoryRepository = new CategoryRepository(new SiteContext());
}
public BlogController(IBlogPostRepository blogPostRepository, ICategoryRepository categoryRepository)
{
_blogPostRepository = blogPostRepository;
_categoryRepository = categoryRepository;
}
public ActionResult Add()
{
ViewData["categoryList"] = _categoryRepository.GetAllCategory();
return View("Add");
}
[HttpPost]
public ActionResult Add(BlogPost blogPost)
{
if (ModelState.IsValid)
{
blogPost.PublishDate = DateTime.Now;
_blogPostRepository.AddPost(blogPost);
_blogPostRepository.Save();
return RedirectToAction("Add");
}
return new HttpNotFoundResult("An Error Accoured while requesting your order!");
}
My first question is that why I can't cast category list to SelectList in razor to select a category via DropDownList? My code in view is like this:
<div>
#Html.LabelFor(b => b.Category)
#Html.DropDownList("Category", ViewData["categoryList"] as SelectList)
#Html.ValidationMessageFor(b => b.Category)
</div>
My second question is: how can I add category in Add action with POST request?
To use a collection for a dropdown, it has to be of type IEnumerable<SelectListItem>, which SelectList is. You can't take an arbitrary collection of objects and just cast it to a SelectList.
I like to use an extension method for creating an IEnumerable<SelectListItem>, check out this:
public static class SelectListExtensions
{
public static IEnumerable<SelectListItem> ToSelectList<T>(this IEnumerable<T> source)
{
return ToSelectList(source, null);
}
public static IEnumerable<SelectListItem> ToSelectList<T>(this IEnumerable<T> source, string defaultOption)
{
return ToSelectList(source, value => value, value => value.ToString(), null);
}
public static IEnumerable<SelectListItem> ToSelectList<T, TDataValue>(
this IEnumerable<T> source,
Func<T, TDataValue> dataValueSelector,
Func<T, string> dataTextSelector)
{
return ToSelectList(source, (value, index) => dataValueSelector(value), dataTextSelector, null);
}
public static IEnumerable<SelectListItem> ToSelectList<T, TDataValue>(
this IEnumerable<T> source,
Func<T, int, TDataValue> dataValueSelector,
Func<T, string> dataTextSelector)
{
return ToSelectList(source, dataValueSelector, dataTextSelector, null);
}
public static IEnumerable<SelectListItem> ToSelectList<T, TDataValue>(
this IEnumerable<T> source,
Func<T, TDataValue> dataValueSelector,
Func<T, string> dataTextSelector,
string defaultOption)
{
return ToSelectList(source, (value, index) => dataValueSelector(value), dataTextSelector, defaultOption);
}
public static IEnumerable<SelectListItem> ToSelectList<T, TDataValue>(
this IEnumerable<T> source,
Func<T, int, TDataValue> dataValueSelector,
Func<T, string> dataTextSelector,
string defaultOption)
{
var list = source.Select((item, index) => new SelectListItem
{
Value = dataValueSelector(item, index).ToString(),
Text = dataTextSelector(item),
}).ToList();
if (defaultOption == null) return list;
list.Insert(0, new SelectListItem { Text = defaultOption, Value = "-1" });
return list;
}
}
You can then use this extension method in your controller:
public ActionResult Add()
{
ViewData["categoryList"] = _categoryRepository.GetAllCategory()
.ToSelectList(c => c.Id, c => c.Name);
return View("Add");
}
Assuming that your Category has the properties Id and Name...
And then use it in your view:
#Html.DropDownList("Category", ViewData["categoryList"] as IEnumerable<SelectListItem>)
On a side note, I would recommend that you use models to pass to your (strongly typed) views, as opposed to using ViewData/ViewBag. It saves you a lot of trouble the day you're going to refactor something and it also gives you good tooling help (intellisense).
Related
I want to convert One predicate Expression(Expression<Func<Item, bool>>) to another Predicate Expression (Expression<Func<ItemEntity, bool>>) but after converting I am not able to query through LINQ.
I Already try this and this approach but nothing work properly
can anyone tells me how to do it properly, My approach for this problems.
using System;
using System.Linq;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading.Tasks;
using System.Reflection;
public class ItemEntity {
public int ItemId {set;get;}
public int ItemParentId {set;get;}
public int ItemName {set;get;}
}
public class Item {
public int Id{ set;get;}
public int ParentId{set;get;}
public int Name {set;get;}
}
public class Program
{
public static Item Convert(ItemEntity itemChild)
{
return new Item()
{
Id = itemChild.ItemId,
ParentId = itemChild.ItemParentId,
Name = itemChild.ItemName
};
}
public async Task<IList<ItemEntity>> SelectAsync(Expression<Func<ItemEntity, bool>> predicate)
{
// using Microsoft.EntityFrameworkCore;
// private readonly DbContext _context; // Injected globally by using Service.AddScoped<ItemContext>();
// return await _context.Set<ItemEntity>().AsNoTracking().Where(predicate).ToListAsync();
return await Task.Run(() => new List<ItemEntity>()); // actually return the result with matching predicate
}
public async Task<List<Item>> GetItems(Expression<Func<Item, bool>> expression)
{
MethodInfo convertMethod = ((Func<ItemEntity, Item>)Convert).Method;
var p = Expression.Parameter(typeof(ItemEntity));
var converted = Expression.Lambda<Func<ItemEntity, bool>>(
Expression.Invoke(expression, Expression.Convert(p, typeof(Item), convertMethod)), p);
IList<ItemEntity> res = await SelectAsync(converted);
var t = res.Select(x => Convert(x)).ToList();
return t;
}
public static void Main()
{
Program pr = new Program();
Func<Expression<Func<Item, bool>>, Task<List<Item>>> getItem = pr.GetItems;
var res = getItem.Invoke(x => x.Id.Equals(1));
Console.WriteLine("Hello World");
}
}
but I am getting error
The LINQ expression 'DbSet()\r\n .Where(i => ((Item)i).Id.Equals(__Id_0))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
I don't able to understand it properly, as per my understanding I am using ToList() for client evaluation, but and also provide method to convert ItemEntity to Item.
I any other way to create fresh Expression Tree based on ItemEntity and then query on DBSet?
Any Help is appreciated
version used:
dot-net 5.0
Microsoft.EntityFrameworkCore 5.0.6
EntityFramework 6.4.4
Database SQL Server
Try the following approach. Main idea to use filter exactly on the projected DTO before materialization.
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading.Tasks;
public class ItemEntity
{
public int ItemId { set; get; }
public int ItemParentId { set; get; }
public int ItemName { set; get; }
}
public class Item
{
public int Id { set; get; }
public int ParentId { set; get; }
public int Name { set; get; }
}
public class Program
{
public static Expression<Func<ItemEntity, Item>> ToItem()
{
return itemChild => new Item
{
Id = itemChild.ItemId,
ParentId = itemChild.ItemParentId,
Name = itemChild.ItemName
};
}
public Task<IList<Item>> SelectAsync(Expression<Func<Item, bool>> predicate)
{
return _context.Set<ItemEntity>()
.AsNoTracking()
.Select(ToItem())
.Where(predicate)
.ToListAsync();
//return Task.Run(() => new List<ItemEntity>().AsQueryable().Select(ToItem()).Where(predicate).ToList());
}
public async Task<IList<Item>> GetItems(Expression<Func<Item, bool>> expression)
{
var res = await SelectAsync(expression);
return res;
}
public static void Main()
{
var pr = new Program();
var res = pr.GetItems(x => x.Id == 1);
Console.WriteLine("Hello World");
}
}
I'm running ASP WebAPI 2 and successfully installed Swashbuckle. I am trying to figure out how one defines what the default schema values are?
For example, on the Swagger live demo site they changed the default value of pet to "doggie". They also defined the allowable values for status. (Live Demo)
I managed to get this working by following what's on this link:
https://github.com/domaindrivendev/Swashbuckle/issues/69#issuecomment-53953785
In short this is what needs to be done:
Create the classes SwaggerDefaultValue and AddDefaultValues as described in the link. Some changes that I did:
public class SwaggerDefaultValue : Attribute
{
public string Name { get; set; }
public string Value { get; set; }
public SwaggerDefaultValue(string value)
{
this.Value = value;
}
public SwaggerDefaultValue(string name, string value) : this(value)
{
this.Name = name;
}
}
public class AddDefaultValues : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
IDictionary<string, object> parameterValuePairs =
GetParameterValuePairs(apiDescription.ActionDescriptor);
foreach (var param in operation.parameters)
{
var parameterValuePair = parameterValuePairs.FirstOrDefault(p => p.Key.IndexOf(param.name, StringComparison.InvariantCultureIgnoreCase) >= 0);
param.#default = parameterValuePair.Value;
}
}
private IDictionary<string, object> GetParameterValuePairs(HttpActionDescriptor actionDescriptor)
{
IDictionary<string, object> parameterValuePairs = new Dictionary<string, object>();
foreach (SwaggerDefaultValue defaultValue in actionDescriptor.GetCustomAttributes<SwaggerDefaultValue>())
{
parameterValuePairs.Add(defaultValue.Name, defaultValue.Value);
}
foreach (var parameter in actionDescriptor.GetParameters())
{
if (!parameter.ParameterType.IsPrimitive)
{
foreach (PropertyInfo property in parameter.ParameterType.GetProperties())
{
var defaultValue = GetDefaultValue(property);
if (defaultValue != null)
{
parameterValuePairs.Add(property.Name, defaultValue);
}
}
}
}
return parameterValuePairs;
}
private static object GetDefaultValue(PropertyInfo property)
{
var customAttribute = property.GetCustomAttributes<SwaggerDefaultValue>().FirstOrDefault();
if (customAttribute != null)
{
return customAttribute.Value;
}
return null;
}
}
Edit your SwaggerConfig and add the AddDefaultValues class to the OperationFilters:
GlobalConfiguration.Configuration
.EnableSwagger(c => {
...
c.OperationFilter<AddDefaultValues>()
...
});
Now for the parameters I want default values I just add the following:
public IHttpActionResult Put([FromBody]Pet pet)
{
...
return Ok();
}
public class Pet {
[SwaggerDefaultValue("doggie")]
public string Name { get; set; }
[SwaggerDefaultValue("available")]
public string Status;
...
}
Well the code of vgaspar.trivix did not work completly for me, the default values did not get set for the schema. Also i got an NullPointerException. I managed to get it working as intended by editing the Apply method and manipulated the schemaRegistry like this:
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
if (operation.parameters == null)
return;
IDictionary<string, object> parameterValuePairs =
GetParameterValuePairs(apiDescription.ActionDescriptor);
foreach (var param in operation.parameters)
{
if (param.schema != null && param.schema.#ref != null)
{
string schemaName = param.schema.#ref.Split('/').LastOrDefault();
if (schemaRegistry.Definitions.ContainsKey(schemaName))
foreach (var props in schemaRegistry.Definitions[schemaName].properties)
{
if (parameterValuePairs.ContainsKey(props.Key))
props.Value.#default = parameterValuePairs[props.Key];
}
}
var parameterValuePair = parameterValuePairs.FirstOrDefault(p => p.Key.IndexOf(param.name, StringComparison.InvariantCultureIgnoreCase) >= 0);
param.#default = parameterValuePair.Value;
}
}
An example Model Schema can be defined by implementing ISchemaFilter and registering it using the following:
httpConfig
.EnableSwagger(c =>
{
c.SchemaFilter<AddSchemaExamples>()
});
An example implementation is provided here:
public class AddSchemaExamples : ISchemaFilter
{
public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type)
{
if (type == typeof(Product))
{
schema.example = new Product
{
Id = 123,
Type = ProductType.Book,
Description = "Treasure Island",
UnitPrice = 10.0M
};
}
}
}
Source: https://github.com/domaindrivendev/Swashbuckle/issues/162
I know this thread is quite old, but I wanted to share my solution which creates a custom constructor just for the Swagger example schema.
In my model:
/// <summary>
/// Supply a custom constructor for Swagger where you can apply defaults to control the example schema.
/// The constructor must have one parameter of type System.Reflection.ParameterInfo[].
/// Note: Setting a property to null will prevent it from showing in the Swagger example.
/// </summary>System.Reflection.ParameterInfo[].
/// </summary>
public class SwaggerConstructor : Attribute { }
In SwaggerConfig.cs:
c.SchemaFilter<ApplySchemaVendorExtensions>();
The schema extension:
public class ApplySchemaVendorExtensions : ISchemaFilter
{
public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type)
{
ConstructorInfo constructor = type.GetConstructors().FirstOrDefault(c => c.GetCustomAttribute<SwaggerConstructor>() != null);
if (constructor != null)
{
schema.example = constructor.Invoke(new object[] { constructor.GetParameters() });
}
}
}
Usage:
[SwaggerConstructor]
public MyClass(System.Reflection.ParameterInfo[] decoy) : base()
{
MyProperty = false;
}
Stumbled across this just now, you can also set the tag in the XML documentation, in one of my models, I have this defined
/// <summary>
/// Note content
/// </summary>
/// <example>Any text for a note.</example>
public string Note { get; set; }
which ends up looking like this in the swagger documentation when selecting "Try It Now"
Hope that helps someone!
Using .NET 5 with Swashbuckle.AspNetCore 5.6.3, the only way I could get this to work efficiently is this:
public class ExampleDocFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
string ToCamelCase(string name) => char.ToLowerInvariant(name[0]) + name.Substring(1);
if (schema.Properties == null) return;
var setProperties = context.Type.GetProperties().ToList().Where(f => f.GetCustomAttribute<DefaultValueAttribute>() != null).Where(f => schema.Properties.Any(n => n.Key.Equals(ToCamelCase(f.Name)))).ToDictionary(f => f, f => f.GetCustomAttribute<DefaultValueAttribute>());
foreach (var prop in setProperties) schema.Properties[ToCamelCase(prop.Key.Name)].Example = OpenApiAnyFactory.CreateFor(schema.Properties[ToCamelCase(prop.Key.Name)], prop.Value.Value);
}
}
To use this - in your startup.cs:
services.AddSwaggerGen(swagger => {
...
swagger.SchemaFilter<ExampleDocFilter>();
});
I know this is very simple but new to Mvc. I will like to populate a dropdownbox with data
from the GetDescription method such that the dropdown shows the Description and when selected the code is passed as the value.
How can I write the html piece?
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public static List<Populate> GetDescription()
{
List<Populate> myInfor = new List<Populate>()
{
new Populate{Id=1,Description="Lagos", Code="lag"},
new Populate{Id=2,Description="Ibadan", Code="lba"},
new Populate{Id=3,Description="Akure", Code="Aku"},
new Populate{Id=4,Description="Ejigbo", Code="Eji"},
new Populate{Id=5,Description="Oshogbo", Code="Osh"}
};
return myInfor;
}
}
public class Populate
{
public int Id { get; set; }
public string Description { get; set; }
public string Code { get; set; }
}
Here is the html
*********************
#model PopulateDropDownList.Models.Populate
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
#Html.DropDownList(Model.id, Model.Description, "--Select One--")
</p>
You may also do it like that.
Modify a bit your code:
public static class Helper
{
public static List<SelectListItem> GetDescription()
{
List<Populate> myInfor = new List<Populate>()
{
new Populate{Id=1,Description="Lagos", Code="lag"},
new Populate{Id=2,Description="Ibadan", Code="lba"},
new Populate{Id=3,Description="Akure", Code="Aku"},
new Populate{Id=4,Description="Ejigbo", Code="Eji"},
new Populate{Id=5,Description="Oshogbo", Code="Osh"}
};
var result = new List<SelectListItem>();
var items = from n in myInfor
select new SelectListItem
{
Text = n.Description,
Value = n.Id.toString()
};
foreach (var item in items)
result.Add(item);
return result;
}
}
Than in your View:
#using YourProject.Helper
#Html.DropDownList("AnyTextAsID", Helper.GetDescription())
You could do this in your view:
#{
Dictionary<string, string> populate = new Dictionary<string, string>();
populate.Add("Lagos", "lag");
populate.Add("Ibadan", "lba");
populate.Add("Akure", "aku");
populate.Add("Ejigbo", "eji");
populate.Add("Oshogbo", "osh");
}
#Html.DropDownList("myinfo", new SelectList(populate.ToList(), "Value", "Key", "Lagos"), new { id = "Populate" })
inspired by this answer I'm trying to map a property on a model class to an expression based on the actual entity.
These are the two classes involved:
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Id { get; set; }
public DateTime? BirthDate { get; set; }
public int CustomerTypeId { get; set; }
}
public class CustomerModel
{
...
public bool HasEvenId { get; set; }
}
An example of a possible expression I'd like to convert is:
Expression<Func<CustomerModel, bool>> from = model => model.HasEvenId;
Expression<Func<Customer, bool>> to = entity => ((entity.Id % 2) == 0);
The problem is that I have to expose an OData endpoint via ASP.NET WebAPI but I need to make some operations on the entities before I can them, hence the need of a model class and the need to translate the expression based on the model that I could receive as an OData query in an expression based on the entity, that I would use to query EF4.
This is where I got so far:
private static readonly Dictionary<Expression, Expression> Mappings = GetMappings();
private static Dictionary<Expression, Expression> GetMappings()
{
var mappings = new Dictionary<Expression, Expression>();
var mapping = GetMappingFor((CustomerModel model) => model.HasEvenId, (Customer customer) => (customer.Id%2) == 0);
mappings.Add(mapping.Item1, mapping.Item2);
return mappings;
}
private static Tuple<Expression, Expression> GetMappingFor<TFrom, TTo, TValue>(Expression<Func<TFrom, TValue>> fromExpression, Expression<Func<TTo, TValue>> toExpression)
{
MemberExpression fromMemberExpression = (MemberExpression) fromExpression.Body;
return Tuple.Create<Expression, Expression>(fromMemberExpression, toExpression);
}
public static Expression<Func<TTo, bool>> Translate<TFrom, TTo>(Expression<Func<TFrom, bool>> expression, Dictionary<Expression, Expression> mappings = null)
{
if (expression == null)
return null;
string parameterName = expression.Parameters[0].Name;
parameterName = string.IsNullOrWhiteSpace(parameterName) ? "p" : parameterName;
var param = Expression.Parameter(typeof(TTo), parameterName);
var subst = new Dictionary<Expression, Expression> { { expression.Parameters[0], param } };
ParameterChangeVisitor parameterChange = new ParameterChangeVisitor(parameterName);
if (mappings != null)
foreach (var mapp in mappings)
subst.Add(mapp.Key, parameterChange.Visit(mapp.Value));
var visitor = new TypeChangeVisitor(typeof(TFrom), typeof(TTo), subst);
return Expression.Lambda<Func<TTo, bool>>(visitor.Visit(expression.Body), param);
}
public IQueryable<CustomerModel> Get()
{
var filterExtractor = new ODataFilterExtractor<CustomerModel>();
Expression<Func<CustomerModel, bool>> expression = filterExtractor.Extract(Request);
Expression<Func<Customer, bool>> translatedExpression = Translate<CustomerModel, Customer>(expression, Mappings);
IQueryable<Customer> query = _context.Customers;
if (translatedExpression != null)
query = query.Where(translatedExpression);
var finalQuery = from item in query.AsEnumerable()
select new CustomerModel()
{
FirstName = item.FirstName,
LastName = item.LastName,
Id = item.Id,
BirthDate = item.BirthDate,
CustomerTypeId = item.CustomerTypeId,
HasEvenId = (item.Id % 2 ) == 0
};
return finalQuery.AsQueryable();
}
where:
ODataFilterExtractor is a class that extract the $filter expression from the RequestMessage we receive;
ParameterChangeVisitor just changes all the ParameterExpression to a new one having the provided string as parameter name;
In addition, I changed the VisitMember method of the answer linked above in this way:
protected override Expression VisitMember(MemberExpression node)
{
// if we see x.Name on the old type, substitute for new type
if (node.Member.DeclaringType == _from)
{
MemberInfo toMember = _to.GetMember(node.Member.Name, node.Member.MemberType, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).SingleOrDefault();
if (toMember != null)
{
return Expression.MakeMemberAccess(Visit(node.Expression), toMember);
}
else
{
if (_substitutions.Select(kvp => kvp.Key).OfType<MemberExpression>().Any(me => me.Member.Equals(node.Member)))
{
MemberExpression key = _substitutions.Select(kvp => kvp.Key).OfType<MemberExpression>().Single(me => me.Member.Equals(node.Member));
Expression value = _substitutions[key];
// What to return here?
return Expression.Invoke(value);
}
}
}
return base.VisitMember(node);
}
Thanks for you help.
I took the liberty of modifying your code just a hair but this does the trick,
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Id { get; set; }
public DateTime? BirthDate { get; set; }
public int CustomerTypeId { get; set; }
}
public class CustomerModel
{
public string FullName { get; set; }
public bool HasEvenId { get; set; }
}
sealed class AToBConverter<TA, TB> : ExpressionVisitor
{
private readonly Dictionary<ParameterExpression, ParameterExpression> _parameters = new Dictionary<ParameterExpression, ParameterExpression>();
private readonly Dictionary<MemberInfo, LambdaExpression> _mappings;
protected override Expression VisitParameter(ParameterExpression node)
{
if (node.Type == typeof(TA))
{
ParameterExpression parameter;
if (!this._parameters.TryGetValue(node, out parameter))
{
this._parameters.Add(node, parameter = Expression.Parameter(typeof(TB), node.Name));
}
return parameter;
}
return node;
}
protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression == null || node.Expression.Type != typeof(TA))
{
return base.VisitMember(node);
}
Expression expression = this.Visit(node.Expression);
if (expression.Type != typeof(TB))
{
throw new Exception("Whoops");
}
LambdaExpression lambdaExpression;
if (this._mappings.TryGetValue(node.Member, out lambdaExpression))
{
return new SimpleExpressionReplacer(lambdaExpression.Parameters.Single(), expression).Visit(lambdaExpression.Body);
}
return Expression.Property(
expression,
node.Member.Name
);
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
return Expression.Lambda(
this.Visit(node.Body),
node.Parameters.Select(this.Visit).Cast<ParameterExpression>()
);
}
public AToBConverter(Dictionary<MemberInfo, LambdaExpression> mappings)
{
this._mappings = mappings;
}
}
sealed class SimpleExpressionReplacer : ExpressionVisitor
{
private readonly Expression _replacement;
private readonly Expression _toFind;
public override Expression Visit(Expression node)
{
return node == this._toFind ? this._replacement : base.Visit(node);
}
public SimpleExpressionReplacer(Expression toFind, Expression replacement)
{
this._toFind = toFind;
this._replacement = replacement;
}
}
class Program
{
private static Dictionary<MemberInfo, LambdaExpression> GetMappings()
{
var mappings = new Dictionary<MemberInfo, LambdaExpression>();
var mapping = GetMappingFor(model => model.HasEvenId, customer => (customer.Id % 2) == 0);
mappings.Add(mapping.Item1, mapping.Item2);
mapping = GetMappingFor(model => model.FullName, customer => customer.FirstName + " " + customer.LastName);
mappings.Add(mapping.Item1, mapping.Item2);
return mappings;
}
private static Tuple<MemberInfo, LambdaExpression> GetMappingFor<TValue>(Expression<Func<CustomerModel, TValue>> fromExpression, Expression<Func<Customer, TValue>> toExpression)
{
return Tuple.Create(((MemberExpression)fromExpression.Body).Member, (LambdaExpression)toExpression);
}
static void Main()
{
Expression<Func<CustomerModel, bool>> source = model => model.HasEvenId && model.FullName == "John Smith";
Expression<Func<Customer, bool>> desiredResult = model => (model.Id % 2) == 0 && (model.FirstName + " " + model.LastName) == "John Smith";
Expression output = new AToBConverter<CustomerModel, Customer>(GetMappings()).Visit(source);
Console.WriteLine("The two expressions do {0}match.", desiredResult.ToString() == output.ToString() ? null : "not ");
Console.ReadLine();
}
}
Another solution would be to use AutoMapper to map complex types and modify the resulting expression query with an ExpressionTransformer before it gets executed. I will try to explain with a complete sample:
Model classes
Some POCO classes just for holding data.
public enum CostUnitType
{
None = 0,
StockUnit = 1,
MachineUnit = 2,
MaintenanceUnit = 3
}
public class CostUnit
{
public string CostUnitId { get; set; }
public string WorkplaceId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public CostUnitType Type { get; set; }
public override string ToString()
{
return CostUnitId + " " + Name + " " + Type;
}
}
public class StockUnit
{
public string Id { get; set; }
public string Name { get; set; }
}
public class MachineUnit
{
public string Id { get; set; }
public string Name { get; set; }
}
public class MaintenanceUnit
{
public string Id { get; set; }
public string Name { get; set; }
}
The ExpressionTransformer class
Most of the time, using the Mapper static class is fine, but sometimes you need to map the same types with different configurations, so you need to explicitly use an IMappingEngine like this:
var configuration = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers);
var engine = new MappingEngine(configuration);
The way to create a MappingEngine is not obvious at all. I had to dig in the source code to find how it was done.
public static class ExpressionTransformer
{
private static readonly MappingEngine Mapper;
/// <summary>
/// Initializes the <see cref="ExpressionTransformer"/> class.
/// Creates an instance of AutoMapper. Initializes mappings.
/// </summary>
static ExpressionTransformer()
{
ConfigurationStore configurationStore = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers);
// Create mapping
// Maps Id from StockUnit to CostUnitId from CostUnit
configurationStore.CreateMap<StockUnit, CostUnit>()
.ForMember(m => m.CostUnitId, opt => opt.MapFrom(src => src.Id));
// Maps Id from MachineUnit to CostUnitId from CostUnit
configurationStore.CreateMap<MachineUnit, CostUnit>()
.ForMember(m => m.CostUnitId, opt => opt.MapFrom(src => src.Id));
// Maps Id from MaintenanceUnit to CostUnitId from CostUnit
configurationStore.CreateMap<MaintenanceUnit, CostUnit>()
.ForMember(m => m.CostUnitId, opt => opt.MapFrom(src => src.Id));
// Create instance of AutoMapper
Mapper = new MappingEngine(configurationStore);
}
public static Expression<Func<TDestination, bool>> Tranform<TSource, TDestination>(Expression<Func<TSource, bool>> sourceExpression)
{
// Resolve mappings by AutoMapper for given types.
var map = Mapper.ConfigurationProvider.FindTypeMapFor(typeof(TSource), typeof(TDestination));
if (map == null)
{
throw new AutoMapperMappingException(string.Format("No Mapping found for {0} --> {1}.", typeof(TSource).Name, typeof(TDestination).Name));
}
// Transform from TSource to TDestination with specified mappings
var visitor = new ParameterTypeVisitor<TSource, TDestination>(sourceExpression, map.GetPropertyMaps());
var expression = visitor.Transform();
return expression;
}
private class ParameterTypeVisitor<TSource, TDestination> : ExpressionVisitor
{
private readonly Dictionary<string, ParameterExpression> _parameters;
private readonly Expression<Func<TSource, bool>> _expression;
private readonly IEnumerable<PropertyMap> _maps;
public ParameterTypeVisitor(Expression<Func<TSource, bool>> expression, IEnumerable<PropertyMap> maps)
{
_parameters = expression.Parameters
.ToDictionary(p => p.Name, p => Expression.Parameter(typeof(TDestination), p.Name));
_expression = expression;
_maps = maps;
}
public Expression<Func<TDestination, bool>> Transform()
{
return (Expression<Func<TDestination, bool>>) Visit(_expression);
}
protected override Expression VisitMember(MemberExpression node)
{
if (node.Member.DeclaringType == typeof(TSource))
{
var memberName = node.Member.Name;
var member = _maps.FirstOrDefault(p => typeof(TSource) == node.Expression.Type
&& p.SourceMember.Name == memberName);
if (member != null)
{
// Return Property from TDestination
var expression = Visit(node.Expression);
return Expression.MakeMemberAccess(expression, member.DestinationProperty.MemberInfo);
}
}
return base.VisitMember(node);
}
protected override Expression VisitParameter(ParameterExpression node)
{
var parameter = _parameters[node.Name];
return parameter;
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
var expression = Visit(node.Body);
return Expression.Lambda(expression, _parameters.Select(x => x.Value));
}
}
}
Usage
To Convert an expression we just need to call Transform Method of ExpressionTransformer class
Expression<Func<StockUnit, bool>> stockQuery = unit => unit.Id == "0815" && unit.Name == "ABC";
// Call Transform<TSource, TDestination> method.
Expression<Func<CostUnit, bool>> costUnitQuery = ExpressionTransformer.Tranform<StockUnit, CostUnit>(stockQuery);
Result
I have a collection named as MenuItemCollection and this derived form List< MenuItem >
There is a Singleton Instance of MenuItemCollection and if I simplify the fields of MenuItem:
public class MenuItem
{
int Id {set;get;}
string Title {set;get;}
MenuItemCollection ChildMenus {set;get;}
}
I need to use a filter method on this collection. For example, I'd like to filter the collection for one menu's Id.
Here is a sample MenuItemCollection:
1-Home
2-User Menu
4-Update Info
5-Delete Account
3-News
6-Archived News
As you can see there some Child menus such as number 4 or number 6
I normally use below to filter:
public List<MenuItem> Filter(MenuItemFilterArgs args)
{
List<MenuItem> Result = new List<MenuItem>();
IQueryable<MenuItem> QueryableTemp = this.AsQueryable();
return (from item in QueryableTemp
orderby item.Ordering descending
select item).ToList<MenuItem>();
}
And calling this method as:
var FilteredMenus = MenuItemCollection.GetInstance.Filter(new MenuItemFilterArgs { Id = 5 });
Since number 5 is in an inner collection under number 2 menuitem, the result returns as 0. It cannot be found.
How is it possible to run the filter recursively through inner MenuItemCollections? Could you write a code sample?
PS: If you must know why I'm using a singleton instance; my idea was
to retrieve the menus from database and keep it as an object for
easier and faster usage on run-time.
Any help would be highly appreciated.
Thanks in advance
Here is a working example. The ExtensionMethods.Map method is what you need.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace WindowsFormsApplication1
{
static class Program
{
[STAThread]
static void Main()
{
var menuItems1 = MenuItemCollection.Instance.Filter(null);
var menuItems2 = MenuItemCollection.Instance.Filter(new MenuItemCriteria { Id = 5 });
var menuItems3 = MenuItemCollection.Instance.Filter(new MenuItemCriteria { Title = "News" });
var menuItems4 = MenuItemCollection.Instance.Filter(new MenuItemCriteria { Title = "News", IsTrash = true });
}
}
public class MenuItemCollection : List<MenuItem>
{
public static readonly MenuItemCollection Instance;
static MenuItemCollection()
{
Instance = GetMenuList();
}
static MenuItemCollection GetMenuList()
{
return new MenuItemCollection {
new MenuItem {Id = 1, Title = "Home"},
new MenuItem {Id = 2, Title = "User Menu", ChildMenus = new MenuItemCollection {
new MenuItem { Id = 4, Title = "Update Info"},
new MenuItem { Id = 5, Title = "Delete"}
}},
new MenuItem {Id = 3, Title = "News", ChildMenus = new MenuItemCollection {
new MenuItem { Id = 6, Title = "Archived News"},
new MenuItem { Id = 6, Title = "Trashy News", IsTrash = true}
}},
};
}
public List<MenuItem> Filter(MenuItemCriteria criteria)
{
var expression = PredicateBuilder.True<MenuItem>();
if(criteria != null)
{
if (criteria.Id.HasValue)
{
expression = expression.And(menuItem => menuItem.Id == criteria.Id);
}
if (!string.IsNullOrEmpty(criteria.Title))
{
expression = expression.And(menuItem => menuItem.Title.Contains(criteria.Title));
}
if (criteria.IsTrash.HasValue)
{
expression = expression.And(menuItem => menuItem.IsTrash == criteria.IsTrash);
}
}
Func<MenuItem, bool> searchCriteria = expression.Compile();
Func<MenuItem, IEnumerable<MenuItem>> childrenSelector = x => x.ChildMenus;
return this.Map(searchCriteria, childrenSelector).ToList();
}
}
public class MenuItemCriteria
{
public int? Id { set; get; }
public string Title { set; get; }
public bool? IsTrash { set; get; }
}
public class MenuItem
{
public int Id { set; get; }
public string Title { set; get; }
public bool IsTrash { set; get; }
public MenuItemCollection ChildMenus { set; get; }
}
public static class ExtensionMethods
{
public static IEnumerable<T> Map<T>(this IEnumerable<T> source,
Func<T, bool> selector = null,
Func<T, IEnumerable<T>> childrenSelector = null)
{
if (source == null) return new List<T>();
if (selector == null)
{
// create a default selector that selects all items
selector = x => true;
}
var list = source.Where(selector);
if (childrenSelector != null)
{
foreach (var item in source)
{
list = list.Concat(childrenSelector(item).Map(selector, childrenSelector));
}
}
return list;
}
}
public static class PredicateBuilder
{
public static Expression<Func<T, bool>> True<T>() { return f => true; }
public static Expression<Func<T, bool>> False<T>() { return f => false; }
public static Expression<Func<T, bool>> Or<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.OrElse(expr1.Body, invokedExpr), expr1.Parameters);
}
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);
}
}
}