MVC model require all fields - c#

I have an MVC application with EF6. Is there a way to automatically set all properties of a model to [Required]? Some of our models are large with all required fields. Any way to save lines of code or make this cleaner?
public class Employee{
[Required]
public string Name { get; set; }
[Required]
public string Address 1 { get; set; }
[Required]
public string Address 2 { get; set; }
[Required]
public int SSN { get; set; }
[Required]
public double PayRate { get; set; }
[Required]
public int PayType { get; set; }
[Required]
public string JobTitle { get; set; }
[Required]
public bool FullTime { get; set; }
[Required]
public string Sex { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Name { get; set; }
}
Thanks

You can MakeStringColumnsRequired or MakeAllNullableColumnsRequired by means of Fluent API and "dynamic" Expressions:
protected void MakeStringColumnsRequired<T>(DbModelBuilder modelBuilder)
where T : class
{
var type = typeof(T);
foreach (var prop in type.GetProperties().Where(x => x.PropertyType == typeof(string)))
{
var argParam = Expression.Parameter(type, "x");
var nameProperty = Expression.Property(argParam, prop.Name);
var lambda = Expression.Lambda<Func<T, string>>(nameProperty, argParam);
modelBuilder.Entity<T>().Property(lambda).IsRequired();
}
}
protected void MakeAllNullableColumnsRequired<T>(DbModelBuilder modelBuilder)
where T : class
{
var type = typeof(T);
foreach (var prop in type.GetProperties()
.Where(x => Nullable.GetUnderlyingType(x.PropertyType) != null || x.PropertyType == typeof(string))
)
{
var argParam = Expression.Parameter(type, "x");
var nameProperty = Expression.Property(argParam, prop.Name);
var funcType = typeof(Func<,>);
funcType = funcType.MakeGenericType(typeof(T), prop.PropertyType);
var lambdaMethod = typeof(Expression).GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(x => x.Name == "Lambda" && x.IsGenericMethod).First();
lambdaMethod = lambdaMethod.MakeGenericMethod(funcType);
var lambda = lambdaMethod.Invoke(null, new object[] { nameProperty, new ParameterExpression[] { argParam } });
var entity = modelBuilder.Entity<T>();
var entityPropertyMethods = entity.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Where(x => x.Name == "Property" && !x.IsGenericMethod).ToList();
var entityPropertyMethod = entityPropertyMethods
.Where(x => x.GetParameters().First().ParameterType.GetGenericArguments().First().GetGenericArguments().Last() == prop.PropertyType)
.FirstOrDefault();
if(entityPropertyMethod == null)
{
entityPropertyMethod = entity.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Where(x => x.Name == "Property" && x.IsGenericMethod).Last();
entityPropertyMethod = entityPropertyMethod.MakeGenericMethod(Nullable.GetUnderlyingType(prop.PropertyType));
}
var property = entityPropertyMethod.Invoke(entity, new object[] { lambda });
var isRequired = property.GetType().GetMethod("IsRequired");
isRequired.Invoke(property, null);
}
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
#if StringColumnsRequired
MakeStringColumnsRequired<Employee>(modelBuilder);
#else
MakeAllNullableColumnsRequired<Employee>(modelBuilder)
#endif
}

Related

Mapping classes with reflection

I have 2 classes User and UserDto both have a Phones property, all the other properties I can map perfectly.
But the problem occurs with the Phones list of the User class, since being null, I cannot identify what type of list it is.
What I need is to be able to and create a list based on its type using reflection.
var list = new List<PhoneDto>();
list.Add(new PhoneDto { Code = 1, Number = 11111111 });
list.Add(new PhoneDto { Code = 2, Number = 11111112 });
list.Add(new PhoneDto { Code = 3, Number = 11111113 });
list.Add(new PhoneDto { Code = 4, Number = 11111114 });
list.Add(new PhoneDto { Code = 5, Number = 11111115 });
var userDto=new UserDto
{
Age=18,
IsData=true,
CreationDate=DateTime.Now,
Phones =list
};
var myMapper = new MyMapper();
var user= myMapper.Map<User>(userDto);
public class User
{
public string Name { get; set; }
public DateTime CreationDate { get; set; }
public int Age { get; set; }
public bool IsData { get; set; }
public ICollection.Phone. Phones { get; set; }
}
public class UserDto
{
public string Name { get; set; }
public DateTime CreationDate { get; set; }
public int Age { get; set; }
public bool IsData { get; set; }
public List.PhoneDto. Phones { get; set; }
}
namespace CustomMapping;
public class MyMapper
{
public T Map<T>(object sourceObject)
{
if (sourceObject == null) throw new Exception("Source object is null");
var instance = Activator.CreateInstance(typeof(T));
if (instance == null) throw new Exception("Can not create de typeof(T) Instance");
var sourceType = sourceObject.GetType();
var targetType = instance.GetType();
PropertyInfo[] sourceProperties = sourceType.GetProperties(BindingFlags.Instance | BindingFlags.Public);
PropertyInfo[] targetProperties = targetType.GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (var sourceProperty in sourceProperties)
{
if (sourceProperty == null) continue;
var targetProperty = targetProperties.FirstOrDefault(p => p.Name.ToLower() == sourceProperty.Name.ToLower() && p.CanWrite);
if (targetProperty == null) continue;
var sourceValue = sourceProperty?.GetValue(sourceObject, null);
if (sourceProperty?.PropertyType == targetProperty.PropertyType)
{
instance?.GetType()?.GetProperty(sourceProperty.Name)?.SetValue(instance, sourceValue);
continue;
}
var isEnumerable = IsEnumerable(targetProperty);
if (isEnumerable)
{
//---------Here I need to map the lists---------
}
}
return (T)instance;
}
private bool IsEnumerable(PropertyInfo propertyInfo)
{
return (propertyInfo.PropertyType.GetInterfaces().Any(p => p.Name == "IEnumerable`1") && propertyInfo.PropertyType.FullName.IndexOf("System.String") < 0);
}
}
I would appreciate any help.

How to usea depth level of nested EF Core list, child?

I'm using EF Core and I have one list. I have a level field in Response and I want incremental levels for each include. Example below. How can I do it ?
https://codebeautify.org/jsonviewer/cb75d8f7
if my include finder code is;
public async Task<IEnumerable<Category>> GetTreeAsync(Expression<Func<Category, bool>>
predicate = null, IList<string> includes = null,
Func<IQueryable<Category>, IOrderedQueryable<Category>> orderBy = null, bool
disableTracking = true)
{
IQueryable<Category> query = Context.Set<Category>().Where(x => !x.IsDelete);
if (disableTracking) query = query.AsNoTracking();
if (includes != null)
query = query.Include(a => a.SubCategories.Where(s => !s.IsDelete))
.ThenInclude(a => a.SubCategories.Where(s => !s.IsDelete))
.ThenInclude(a => a.SubCategories.Where(s => !s.IsDelete))
.ThenInclude(a => a.SubCategories.Where(s => !s.IsDelete))
.ThenInclude(a => a.SubCategories.Where(s => !s.IsDelete));
if (predicate != null) query = query.Where(predicate);
if (orderBy != null) query = orderBy(query);
string sql = query.ToQueryString();
return await query.ToListAsync();
}
Response Model;
public class CategoryTreeResponse
{
public int Key { get; set; } //id
public string Title { get; set; } //name
public int Level { get; set; } //1-2-3-4-....
public int? CommissionRate { get; set; }
public int TermDay { get; set; }
public bool IsDelete { get; set; }
public virtual ICollection<CategoryTreeResponse> Children { get; set; } //submenu
}
I use CQRS

How to write an expression tree for selecting inside of SelectMany?

Consider the following Person class
// Person.cs
public class Person
{
public Guid Id { get; set; }
public string Name { get; set; }
public string FamilyName { get; set; }
public float Age { get; set; }
public DateTimeOffset BithDate { get; set; }
public IEnumerable<Address> Addresses { get; set; }
public Person()
{
Addresses = new List<Address>();
}
}
// Address.cs
public class Address
{
public string Country { get; set; }
public string City { get; set; }
public string MainStreet { get; set; }
public string Info { get; set; }
public string No { get; set; }
}
Now, I have the snippet code below
var list = PeopleDataGenerator.GetPeople()
.SelectMany(x => x.Addresses)
.Select(x => x.City)
;
How can I do this part .SelectMany(x => x.Addresses) .Select(x => x.City) with Expression Tree?
Unclear if you want it for IEnumerable or IQueryable... For IEnumerable:
Given:
/// <summary>
/// IEnumerable<TResult> Enumerable.SelectMany<TSource, TResult>(IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector)
/// </summary>
public static readonly MethodInfo SelectMany1 = (from x in typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
where x.Name == nameof(Enumerable.SelectMany)
let args = x.GetGenericArguments()
where args.Length == 2
let pars = x.GetParameters()
where pars.Length == 2 &&
pars[0].ParameterType == typeof(IEnumerable<>).MakeGenericType(args[0]) &&
pars[1].ParameterType == typeof(Func<,>).MakeGenericType(args[0], typeof(IEnumerable<>).MakeGenericType(args[1]))
select x).Single();
/// <summary>
/// IEnumerable<TResult> Enumerable.Select<TSource, TResult>(IEnumerable<TSource> source, Func<TSource, TResult> selector)
/// </summary>
public static readonly MethodInfo Select1 = (from x in typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
where x.Name == nameof(Enumerable.Select)
let args = x.GetGenericArguments()
where args.Length == 2
let pars = x.GetParameters()
where pars.Length == 2 &&
pars[0].ParameterType == typeof(IEnumerable<>).MakeGenericType(args[0]) &&
pars[1].ParameterType == typeof(Func<,>).MakeGenericType(args[0], args[1])
select x).Single();
(I have a gist full of these definitions)
You can
// SelectMany
var par1 = Expression.Parameter(typeof(Person));
var sub1 = Expression.Property(par1, nameof(Person.Addresses));
var lambda1 = Expression.Lambda<Func<Person, IEnumerable<Address>>>(sub1, par1);
var selectMany = Expression.Call(SelectMany1.MakeGenericMethod(typeof(Person), typeof(Address)), par0, lambda1);
// Select
var par2 = Expression.Parameter(typeof(Address));
var sub2 = Expression.Property(par2, nameof(Address.City));
var lambda2 = Expression.Lambda<Func<Address, string>>(sub2, par2);
var select = Expression.Call(Select1.MakeGenericMethod(typeof(Address), typeof(string)), selectMany, lambda2);
// persons => Select(SelectMany(persons))
var par0 = Expression.Parameter(typeof(IEnumerable<>).MakeGenericType(typeof(Person)));
var lambda0 = Expression.Lambda<Func<IEnumerable<Person>, IEnumerable<string>>>(select, par0);
var compiled = lambda0.Compile();
If you want the SelectMany+Select call it is in the select variable. If you want a compilable expression that uses the SelectMany+Select, it is in lambda0 and in compiled.

LINQ to SQL select property name by string on projection

How can I achieve the projection on the last select? I need the property defined by the string prop.Name to be selected into the SeriesProjection object.
public override IQueryable<SeriesProjection> FilterOn(string column)
{
//Get metadata class property by defined Attributes and parameter column
var prop = typeof(CommunicationMetaData)
.GetProperties()
.Single(p => p.GetCustomAttribute<FilterableAttribute>().ReferenceProperty == column);
var attr = ((FilterableAttribute)prop.GetCustomAttribute(typeof(FilterableAttribute)));
var param = Expression.Parameter(typeof(Communication));
Expression conversion = Expression.Convert(Expression.Property(param, attr.ReferenceProperty), typeof(int));
var condition = Expression.Lambda<Func<Communication, int>>(conversion, param); // for LINQ to SQl/Entities skip Compile() call
var result = DbQuery.Include(prop.Name)
//.GroupBy(c => c.GetType().GetProperty(attr.ReferenceProperty))
.GroupBy(condition)
.OrderByDescending(g => g.Count())
.Select(group => new SeriesProjection()
{
Count = group.Count(),
Id = group.Key,
//set this navigation property dynamically
Name = group.FirstOrDefault().GetType().GetProperty(prop.Name)
});
return result;
}
For the GroupBy I used the fk property name that's always an int on the Communication entity, but for the select I can't figure out the expression.
[EDIT]
System.Data.Entity.Infrastructure.DbQuery<Communication> DbQuery;
---
[MetadataType(typeof(CommunicationMetaData))]
public partial class Communication
{
public int CommunicationId { get; set; }
public Nullable<int> TopicId { get; set; }
public int CreateById { get; set; }
public virtual Employee CreateByEmployee { get; set; }
public virtual Topic Topic { get; set; }
}
---
public class CommunicationMetaData
{
[Filterable("By Employee", nameof(Communication.CreateById))]
public Employee CreateByEmployee { get; set; }
[Filterable("By Topic", nameof(Communication.TopicId))]
public Topic Topic { get; set; }
}
---
[AttributeUsage(AttributeTargets.Property)]
public class FilterableAttribute : System.Attribute
{
public FilterableAttribute(string friendlyName, string referenceProperty)
{
FriendlyName = friendlyName;
ReferenceProperty = referenceProperty;
}
public string FriendlyName { get; set; }
public string ReferenceProperty { get; set; }
}
---
public class SeriesProjection
{
public int Count { get; set; }
public int Id { get; set; }
public object Name { get; set; }
}
Without some expression helper library, you have to build the whole selector expression manually.
The input of the selector will be a parameter of type IGrouping<int, Communication>, the result type - SeriesProjection, and the body will be MemberInit expression:
var projectionParameter = Expression.Parameter(typeof(IGrouping<int, Communication>), "group");
var projectionType = typeof(SeriesProjection);
var projectionBody = Expression.MemberInit(
// new SeriesProjection
Expression.New(projectionType),
// {
// Count = group.Count(),
Expression.Bind(
projectionType.GetProperty(nameof(SeriesProjection.Count)),
Expression.Call(typeof(Enumerable), "Count", new[] { typeof(Communication) }, projectionParameter)),
// Id = group.Key
Expression.Bind(
projectionType.GetProperty(nameof(SeriesProjection.Id)),
Expression.Property(projectionParameter, "Key")),
// Name = group.FirstOrDefault().Property
Expression.Bind(
projectionType.GetProperty(nameof(SeriesProjection.Name)),
Expression.Property(
Expression.Call(typeof(Enumerable), "FirstOrDefault", new[] { typeof(Communication) }, projectionParameter),
prop.Name))
// }
);
var projectionSelector = Expression.Lambda<Func<IGrouping<int, Communication>, SeriesProjection>>(projectionBody, projectionParameter);
and then of course use simply:
var result = DbQuery.Include(prop.Name)
.GroupBy(condition)
.OrderByDescending(g => g.Count())
.Select(projectionSelector);

Dynamic Selector Linq To Entities

I have a dynamic selector expression that produces anonymous type. It's working fine in linq to objects, but in linq to entities, it throws:
Attempt 1
NotSupportedException
Only parameterless constructors and initializers are supported in LINQ to Entities.
Expression<Func<User, T>> DynamicSelect<T>(T obj, ParameterExpression userParam)
{
var newExpression = Expression.New(
typeof(T).GetConstructor(typeof(T).GenericTypeArguments),
userParam,
Expression.Constant("X"),
Expression.Constant("Y")
);
return Expression.Lambda<Func<User, T>>(newExpression, userParam);
}
var userParam = Expression.Parameter(typeof(User), "u");
var obj = new { User = new User(), Address = string.Empty, Fax = string.Empty };
var arr = context.Set<T>()
.Select(DynamicSelect(obj, userParam))
.ToArray();
Attempt 2, If I create a custom type, it's working, but I don't want to, because I want to reuse this helper method without creating additional custom type for each entity, I want to be able to pass the type based on consumer.
public class Container
{
public User User { get; set; }
public string Address { get; set; }
public string Fax { get; set; }
}
Expression<Func<User, T>> DynamicSelect<T>(T obj, ParameterExpression userParam)
{
var initExpression = Expression.MemberInit(
Expression.New(typeof(T)),
Expression.Bind(typeof(T).GetProperty("User"), userParam),
Expression.Bind(typeof(T).GetProperty("Address"), Expression.Constant("X")),
Expression.Bind(typeof(T).GetProperty("Fax"), Expression.Constant("Y"))
);
return Expression.Lambda<Func<User, T>>(initExpression, userParam);
}
var userParam = Expression.Parameter(typeof(User), "u");
var arr = context.Set<T>()
.Select(DynamicSelect<Container>(null, userParam))
.ToArray();
Attempt 3, I also tried using Tuple<User, string, string>, but it's not supported too.
NotSupportedException
LINQ to Entities does not recognize the method
'System.Tuple`3[User,System.String,System.String]
Create[User,String,String](User, System.String, System.String)'
method, and this method cannot be translated into a store expression.
Expression<Func<User, T>> DynamicSelect<T>(T obj, ParameterExpression userParam)
{
var createExpression = Expression.Call(
typeof(Tuple),
"Create",
typeof(T).GenericTypeArguments,
userParam,
Expression.Constant("X"),
Expression.Constant("Y"));
return Expression.Lambda<Func<User, T>>(createExpression, userParam);
}
var userParam = Expression.Parameter(typeof(User), "u");
var arr = context.Set<User>()
.Select(DynamicSelect<Tuple<User, string, string>>(null, userParam))
.ToArray();
Please help.
update
I was trying to reuse this helper method in any consumer (User, Customer, Associate, etc) without having specific implementation to each consumer.
The class structure look like.
public class User
{
public int Id { get; set; }
public string UserName { get; set; }
public virtual ICollection<Contact> Contacts { get; set; }
}
public class Customer
{
public int Id { get; set; }
public string CompanyName { get; set; }
public virtual ICollection<Contact> Contacts { get; set; }
}
public class Contact
{
public int Id { get; set; }
public string Type { get; set; }
public string Content { get; set; }
}
public class UserDto
{
public int Id { get; set; }
public string UserName { get; set; }
public ContactDto Contact { get; set; }
}
public class CustomerDto
{
public int Id { get; set; }
public string CompanyName { get; set; }
public ContactDto Contact { get; set; }
}
public class ContactDto
{
public string Email { get; set; }
public string Address { get; set; }
public string Fax { get; set; }
// other contact informations
}
And I have many contacts that could be different for each consumer.
var users = context.Set<User>()
.Select(x => new UserDto
{
Id = x.Id,
UserName = x.UserName,
Contact = new ContactDto
{
Email = x.Contacts.Where(c => c.Type == "Email").Select(c => c.Value).FirstOrDefault()
}
})
.ToArray();
var customers = context.Set<Customer>()
.Select(x => new CustomerDto
{
Id = x.Id,
CompanyName = x.CompanyName,
Contact = new ContactDto
{
Address = x.Contacts.Where(c => c.Type == "Address").Select(c => c.Value).FirstOrDefault(),
Fax = x.Contacts.Where(c => c.Type == "Fax").Select(c => c.Value).FirstOrDefault(),
}
})
.ToArray();
And have refactored the x.Contacts.Where(c => c.Type == "Address").Select(c => c.Value).FirstOrDefault() into expression, but I can't use it directly inside the method like:
var users = context.Set<User>()
.Select(x => new UserDto
{
Id = x.Id,
UserName = x.UserName,
Contact = new ContactDto
{
Email = GetContactExpression("Email").Compile()(x)
}
})
.ToArray();
It will throw error because Invoke method is not supported in linq to expression, so that I need to refactored the whole Select expression, but I need to make it generic (User has UserName, but Customer has CompanyName, and any other information) and probably passing the contact type(s) too after this get solved. The expected result at the moment would be something lile:
var obj = new { User = new User(), Email = "" };
var users = context.Set<User>()
.Select(x => DynamicSelect(obj))
.Select(x => new UserDto
{
Id = x.User.Id,
UserName = x.User.UserName,
Contact = new ContactDto
{
Email = x.Email
}
})
.ToArray();
When I compare your expression with the one created by the compiler for something like u => new { User = u, Address = "X", Fax = "Y" }, the difference is that the latter has filled Members.
I don't quite understand what is the reason for Members to exist at all, but I would try to set it, my guess is that it will fix your problem. The code might look something like:
var constructor = typeof(T).GetConstructor(typeof(T).GenericTypeArguments);
var newExpression = Expression.New(
constructor,
new Expression[] { userParam, Expression.Constant("X"), Expression.Constant("Y") },
constructor.GetParameters().Select(p => typeof(T).GetProperty(p.Name)));

Categories

Resources