I have a model class with two properties of different types - SourceData, DestinationData. These properties holds instances of similar classes (in different namespaces) that I need to compare manually and display differences.
I would like to create generic extension method IsSame for this problem, so I could write simply:
// SourceData: x => ..., DestinatinData: y => ...
var nameIsSame = model.IsSame(x => x.Name, y => y.Name);
var ageIsSame = model.IsSame(x => x.Age, y => y.Age);
...
I prepared sample with this scenario but I can't write the method IsSame that could be called simply like above. I have to specify all type for generic method call.
public interface ISource
{ }
public interface IDestination
{ }
public class SourcePerson : ISource
{
public string Name { get; set; }
public int Age { get; set; }
}
public class DestinationPerson : IDestination
{
public string Name { get; set; }
public int Age { get; set; }
}
public abstract class DetailViewModel<TSource, TDestination>
where TSource : ISource
where TDestination : IDestination
{
public TSource SourceData { get; set; }
public TDestination DestinationData { get; set; }
}
public class PersonDetailViewModel : DetailViewModel<SourcePerson, DestinationPerson>
{ }
Now I have model class of type PersonDetailViewModel that implements abstract class DetailViewModel<>. I thought that following implementation of method ExtensionMethods.IsSame should be called simply by giving instance of PersonDetailViewModel that might be recognized because of constraints.
public static class ExtensionMethods
{
public static bool IsSame<TModel, TSource, TDestination, TValue>(this TModel model, Func<TSource, TValue> sourceProperty, Func<TDestination, TValue> destinationProperty)
where TModel : DetailViewModel<TSource, TDestination>
where TSource : ISource
where TDestination : IDestination
{
if (model.SourceData == null) {
return (model.DestinationData == null);
}
if (model.DestinationData == null)
return false;
return Equals(sourceProperty(model.SourceData), destinationProperty(model.DestinationData));
}
}
However when I try to call the extension method I have to specify all the types even though the compiler should know then all.
class Program
{
static void Main(string[] args)
{
var model = new PersonDetailViewModel
{
SourceData = new SourcePerson { Name = "Karel Gott", Age = 72 },
DestinationData = new DestinationPerson { Name = "Karel Engles", Age = 72 }
};
Console.WriteLine(model);
Console.WriteLine();
//var nameIsSame = model.IsSame(x => x.Name, y => y.Name); // doesn't work :-(
var nameIsSame = model.IsSame<PersonDetailViewModel, SourcePerson, DestinationPerson, string>(x => x.Name, y => y.Name);
Console.WriteLine("Name is same: " + nameIsSame);
//var ageIsSame = model.IsSame(x => x.Age, y => y.Age); // doesn't work :-(
var ageIsSame = model.IsSame<PersonDetailViewModel, SourcePerson, DestinationPerson, int>(x => x.Age, y => y.Age);
Console.WriteLine("Age is same: " + ageIsSame);
Console.WriteLine();
Console.WriteLine("Press any key to exit ...");
Console.ReadKey(true);
}
}
Notice for all: Please don't write me, that I should compare equality differently. I used here method IsSame just for simplicity. The method works well and the code inside should do something else. I just need to call it without explicitly defined types. In my opinion the compiler should know them all from constrains.
In this scenario I used classes PersonDetailViewModel, SourcePerson, DestinationPerson ... but in my application there are plenty of classes like this. The TSource and TDestination doesnt have same code inside, and the properties should have different names. Imagine that I want to compare properties in these classes:
public class SourceCompany : ISource {
public int CompanyId { get; set; }
public string NameTradeRegister { get; set; }
public string AddressStreet { get; set; }
public string Town { get; set; }
}
public class DestinationCompany : IDestination {
public int ID { get; set; }
public string Name { get; set; }
public string StreetName { get; set; }
public string City { get; set; }
}
var idIsSame = model.IsSame(x => CompanyId, y => y.ID);
Ok, I found the solution. The extension method should avoid TModel and instead it can declare DetailViewModel.
public static class ExtensionMethods
{
public static bool IsSame<TSource, TDestination, TValue>(this DetailViewModel<TSource, TDestination> model, Func<TSource, TValue> sourceProperty, Func<TDestination, TValue> destinationProperty)
where TSource : ISource
where TDestination : IDestination
{
if (model.SourceData == null) {
return (model.DestinationData == null);
}
if (model.DestinationData == null)
return false;
return Equals(sourceProperty(model.SourceData), destinationProperty(model.DestinationData));
}
}
Related
I have a generic method, I give it any object of type T and a list of properties and it will return the object with the properties defined by the list set to null
Here's my code
class Program
{
static void Main(string[] args)
{
var orderDto = new OrderDto();
orderDto.Nominal = "1";
orderDto.OrderId = "2";
orderDto.Type = "3";
var clean = FieldCleaner.Clean(orderDto, "OrderId");
}
}
public class FieldCleaner
{
public static T Clean<T>(T dto, params string[] properties) // I want in compilation time, have autocompletion that tell user the value of properties can only be a property name of the type T
{
var propertyInfos = dto.GetType().GetProperties();
foreach (var propertyInfo in propertyInfos)
{
foreach (var property in properties)
{
if (propertyInfo.Name == property)
{
propertyInfo.SetValue(dto, null);
}
}
}
return dto;
}
}
public class OrderDto
{
public string OrderId { get; set; }
public string Nominal { get; set; }
public string Type { get; set; }
}
My question is in the comment above in the code. I don't like the type string[], I want something like a keyof T in typescript
Im using last C# version with last .NET core
To paraphrase a bit:
// I want an auto-completion list in Visual Studio to tell the programmer what properties of T are available
The problem is the type T could be anything. You have to be able to use this code in a completely different assembly, where neither Visual Studio nor the compiler know about your T at compile time.
I won't say it's impossible. Visual Studio is very extensible, especially now we have Roslyn. But that is what you'll have to do: a custom Visual Studio extension using Roslyn to analyze the code and provide the completion list. This isn't built in to the platform.
While the following answer does what you want, there really isn't a point because it's just unnecessary. If you have to specify the property, you might as well just set it directly.
Here is probably as close as you can get. I don't believe you can use params because that would require all TProp to be the same type.
In visual studio when you get to the period, you'll get intellisense:
using System;
using System.Linq.Expressions;
using System.Reflection;
public class Program
{
public static void Main()
{
var orderDto = new OrderDto
{
Id = 1,
Name = "Name",
CreatedOn = DateTime.UtcNow,
CompletedOn = DateTime.UtcNow.AddMinutes(30),
Misc = Guid.NewGuid()
};
Console.WriteLine($"{orderDto.Id} {orderDto.Name} {orderDto.CreatedOn} {orderDto.CompletedOn} {orderDto.Misc}");
orderDto.DefaultFor(x => x.Id, x => x.Name, x => x.CreatedOn, x => x.CompletedOn);
Console.WriteLine($"{orderDto.Id} {orderDto.Name} {orderDto.CreatedOn} {orderDto.CompletedOn} {orderDto.Misc}");
}
}
public static class ObjectExtensions
{
public static void DefaultFor<TObject, TProp1, TProp2, TProp3, TProp4>(this TObject instance,
Expression<Func<TObject, TProp1>> selector1,
Expression<Func<TObject, TProp2>> selector2,
Expression<Func<TObject, TProp3>> selector3,
Expression<Func<TObject, TProp4>> selector4)
where TObject : class
{
DefaultFor(instance, selector1, selector2, selector3);
DefaultFor(instance, selector4);
}
public static void DefaultFor<TObject, TProp1, TProp2, TProp3>(this TObject instance,
Expression<Func<TObject, TProp1>> selector1,
Expression<Func<TObject, TProp2>> selector2,
Expression<Func<TObject, TProp3>> selector3)
where TObject : class
{
DefaultFor(instance, selector1, selector2);
DefaultFor(instance, selector3);
}
public static void DefaultFor<TObject, TProp1, TProp2>(this TObject instance,
Expression<Func<TObject, TProp1>> selector1,
Expression<Func<TObject, TProp2>> selector2)
where TObject : class
{
DefaultFor(instance, selector1);
DefaultFor(instance, selector2);
}
public static void DefaultFor<TObject, TProp>(this TObject instance, Expression<Func<TObject, TProp>> selector)
where TObject : class
{
if (instance == null)
throw new ArgumentNullException();
var memberExpression = (MemberExpression)selector.Body;
var property = (PropertyInfo)memberExpression.Member;
property.SetValue(instance, default(TProp));
}
}
public class OrderDto
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime? CompletedOn { get; set; }
public Guid Misc { get; set; }
}
DotNetFiddle Example
Output:
1 Name 3/29/2019 5:14:06 AM 3/29/2019 5:44:06 AM 3800be41-7fe1-42da-ada5-4fe33ac04a84
0 1/1/0001 12:00:00 AM 3800be41-7fe1-42da-ada5-4fe33ac04a84
I am having trouble with generics. Using examples from here, I want to pass a property for use in EF Core. I have a feeling I am overcomplicating things. Note that I would like to stick with lamba expressions instead of passing something like nameof (which I cannot use in 4.0).
class Foo
{
public string A { get; set; } // some unique string in DbSet for entity Person
public string B { get; set; } // some unique string in DbSet for entity Company
public int GetId<TEntity>(string match, Expression<Func<TEntity, string>> fieldToExamine, Expression<Func<TEntity, int>> fieldWithId, System.Data.Entity.DbContext context)
where TEntity : class
{
int retval = -1;
if (!string.IsNullOrEmpty(match))
{
var expr = (MemberExpression)fieldToExamine.Body;
var prop = (PropertyInfo)expr.Member;
var expr2 = (MemberExpression)fieldWithId.Body;
var idField = (PropertyInfo)expr2.Member;
// this works because everything is explicit
var entity = context.Set<Person>().SingleOrDefault(p => p.PersonName.Equals(match));
// ... but I want that to be generic
// how to reference the property to be evaluated?
//var entity = context.Set<TEntity>().SingleOrDefault(p => ...); // <- HELP?!
if (entity != null)
{
retval = (int)entity.GetType().GetProperty(idField.Name).GetValue(entity, null);
}
}
return retval;
}
}
static void Main(string[] args)
{
// .. omitted database stuff ..
// (for Person) what I want is to match the Name field (here: p.Name) of the Person entity to the Foo property value (here: f.A) and return the database id stored in p.PersonId
int x = f.GetId<Person>(f.A, p => p.PersonName, p => p.PersonId, context);
// same for Company, but different values and fields
int y = f.GetId<Company>(f.B, p => p.CompanyName, p => p.CompanyId, context);
}
Person class and Company class will need to implement either abstract class or interface.
public abstract class BaseEntity
{
public abstract int Id { get; set; }
public abstract string Name { get; set; }
}
public class Person : BaseEntity
{
public int PersonId { get; set; }
public override string Name { get; set; }
public override int Id
{
get { return PersonId; }
set { PersonId = value; }
}
}
public class Company : BaseEntity
{
public int CompanyId { get; set; }
public override string Name { get; set; }
public override int Id
{
get { return CompanyId; }
set { CompanyId = value; }
}
}
Then you can pass p.Name and p.CompanyId.
var entity = context.Set<TEntity>().SingleOrDefault(p => p.Name.Equals(match));
I get the exception message:
The binary operator NotEqual is not defined for the types 'NotificationArea' and 'System.Object'
Source enum:
public enum NotificationArea
{
One,
Two,
Three
}
Destination class:
public class EnumValue
{
public EnumValue()
{
}
public EnumValue(Enum tEnum)
{
Id = Convert.ToInt32(tEnum);
Name = GetEnumValue(tEnum, tEnum.GetType());
}
public int Id { get; set; }
public string Name { get; set; }
public string GetEnumValue(Enum tEnum, Type type)
{
MemberInfo member = type.GetMember(tEnum.ToString())[0];
if (member.GetCustomAttribute<DisplayAttribute>() != null)
{
DisplayAttribute attribute = member.GetCustomAttribute<DisplayAttribute>();
return attribute.Name;
}
return tEnum.ToString();
}
}
Conversion class:
public class EnumConverter : IMemberValueResolver<Notification,
NotificationModel, NotificationArea, EnumValue>
{
public EnumValue Resolve(Notification source, NotificationModel destination,
NotificationArea sourceMember, EnumValue destMember, ResolutionContext context)
{
var model = source.Area.ToDisplay();
return model;
}
}
ToDisplay Extension:
public static EnumValue ToDisplay(this Enum value)
{
var display = new EnumValue(value);
return display;
}
Implementation:
// Query
var notifications = await _db.Notifications
.OrderByDescending(x => x.Timestamp)
.ProjectTo<NotificationModel>(_mapperConfig)
.ToListAsync(token);
// Mapping
CreateMap<Notification, NotificationModel>()
.ForMember(dest => dest.Area, opt => opt.ResolveUsing(src => src.Area));
Source and Destination Models:
public class Notification
{
public int Id {get;set;}
public NotificationArea Area { get; set; }
}
public class NotificationModel
{
public int Id {get;set;}
public EnumValue Area {get;set;}
}
I know I'm doing something wrong... obviously... I just don't know exactly where it's at. I feel Like I have A and C but am missing B.
How to Generic implement of the following query
double sum=context.Employees.Where(e=>(e.Id==12 && e.Sl==100)).Sum(e=>e.Salary);
i am using a repository system.
i want something like this
T total=GetTotalSalary((e.Id==12 && e.Sl==100),e.Salary);
Just pass the variables as param to the function and use them in linq
public double GetTotalSalary(int empId, double empSalary)
{
return context.Employees.Where(e=>(e.Id==empId && e.Sl==empSalary)).Sum(e=>e.Salary);
}
Without knowing if 12 and 100 is hardcoded, this could be a good starting point:
Updated to reflect comments:
public abstract class Contract
{
public decimal Salery { get; set; }
}
public class EmployeePermanent : Contract {
}
public class Employee : Contract
{
public int Id { get; set; }
public int Sl { get; set; }
public void Test()
{
new List<Employee>().AsQueryable().GetTotalSalary(x => x.Id == 12 && x.Sl == 100);
}
}
public static class QueryExtensions
{
public static decimal GetTotalSalary<T>(this IQueryable<T> queryable, Expression<Func<T, bool>> expression) where T : Contract
{
return queryable.Where(expression).Sum(arg => arg.Salery);
}
}
Updated example to show more general usage.
I have an entity to allow user-provided localization:
public class ResourceValue
{
public int ResourceValueId { get; set; }
public string EnglishValue { get; set; }
public string FrenchValue { get; set; }
public string SpanishValue { get; set; }
etc...
}
Used on many other entities like this:
public class SomeEntity
{
public int Id { get; set; }
public virtual ResourceValue Name { get; set; }
public virtual ResourceValue ShortDescription { get; set; }
public virtual ResourceValue LongDescription { get; set; }
etc...
}
I would like to do something like this:
return context.SomeEntities.OrderBy(x => x.Name);
And have that work as if I had done this:
return context.SomeEntities.OrderBy(x => x.Name.FrenchValue);
based on the CurrentUICulture being "fr-CA".
I have been trying some things based on Marc Gravell's answer here: https://stackoverflow.com/a/1231941 but haven't been able to get quite what I want.
Update - This is pretty close, but I would rather have it be named just "OrderBy" so the end-coder can use it without special consideration:
public static IOrderedQueryable<TSource> OrderBy<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector)
{
return ApplyLocalizedOrder(source, keySelector, "OrderBy");
}
public static IOrderedQueryable<TSource> ApplyLocalizedOrder<TSource, TKey>(IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector, string methodName)
{
ParameterExpression arg = keySelector.Parameters[0];
Expression expr = Expression.PropertyOrField(keySelector.Body, GetCurrentCulture());
LambdaExpression lambda = Expression.Lambda<Func<TSource, string>>(expr, arg);
return (IOrderedQueryable<TSource>)typeof(Queryable).GetMethods().Single(
method => method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2
&& method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(TSource), expr.Type)
.Invoke(null, new object[] { source, lambda });
}
While creating lambda expressions dynamically is cool you can achieve your result in a much simpler way just by creating a method that will apply the sort on top of your query. The method would just look like this:
private static IQueryable<SomeEntity> OrderByName(IQueryable<SomeEntity> source, string culture)
{
if (culture == "fr-CA")
{
return source.OrderBy(x => x.Name.FrenchValue);
}
return source.OrderBy(x => x.Name.EnglishValue);
}
And then you would just use it as follows:
OrderByName(context.SomeEntities, "en-US")
Here is the entire example:
public class MyCtx1 : DbContext
{
public DbSet<SomeEntity> SomeEntities { get; set; }
public DbSet<ResourceValue> ResourceValues { get; set; }
}
public class SomeEntity
{
public int Id { get; set; }
public virtual ResourceValue Name { get; set; }
}
public class ResourceValue
{
public int ResourceValueId { get; set; }
public string EnglishValue { get; set; }
public string FrenchValue { get; set; }
}
class Program
{
private static IQueryable<SomeEntity> OrderByName(IQueryable<SomeEntity> source, string culture)
{
if (culture == "fr-CA")
{
return source.OrderBy(x => x.Name.FrenchValue);
}
return source.OrderBy(x => x.Name.EnglishValue);
}
static void Main(string[] args)
{
using (var context = new MyCtx1())
{
if (!context.SomeEntities.Any())
{
context.SomeEntities.Add(
new SomeEntity()
{
Name = new ResourceValue()
{
EnglishValue = "abc - en",
FrenchValue = "xyz - fr"
}
});
context.SomeEntities.Add(
new SomeEntity()
{
Name = new ResourceValue()
{
EnglishValue = "xyz - en",
FrenchValue = "abc - fr"
}
});
context.SaveChanges();
}
Console.WriteLine("Ordered by english name");
DisplayResults(OrderByName(context.SomeEntities, "en-US"));
Console.WriteLine("Ordered by french name");
DisplayResults(OrderByName(context.SomeEntities, "fr-CA"));
}
}
private static void DisplayResults(IQueryable<SomeEntity> q)
{
foreach (var e in q)
{
Console.WriteLine(e.Id);
}
}
And the result:
Ordered by english name
1
2
Ordered by french name
2
1
Press any key to continue . . .
context.SomeEntities.Select(v => v.Name.FrenchName).OrderBy(x => x);
But even better than that in your get for name, return the current culture or a certain culture or whatever your code is calling for, no reason to do it in the Linq query when your class could do it. when it done in the class it is better anyway because then anywhere you call the code it is returning the correct culture.