how to apply generic multiple include with generic repository? - linq

I need to make generic multiple include function as a service with generic repository.
But unfortunately, I get nothing !!
here is my attempt using aggregate linq.
public IQueryable<TEntityDTO> getRowsWithIncludeMultiple(int page = 0, params Expression<Func<TEntityDTO, object>>[] includes)
{
GridSetting gs = GetGrid();
IEnumerable<TEntity> getPage = _dbSet.Skip((page == 0 ? page : page - 1) * gs.ItemsPerPage).Take(gs.ItemsPerPage);
IQueryable<TEntityDTO> rows = _mapper.Map<IEnumerable<TEntityDTO>>(getPage).AsQueryable();
if (includes != null) { rows = includes.Aggregate(rows, (current, include) => current.Include(include)); }
// or
//foreach (var include in includes)
//{
// rows = rows.Include(include);
//}
return rows;
}
when I add debugging point I get that the includes has list of expression
and then here is how I use it
var xxx = _customerService.getRowsWithIncludeMultiple(page: 0, i => i.cityDTO, i => i.ageDTO);
the problem here I get customers without the included things (cityDTO & ageDTO)
let me include here models
public class CustomerDTO
{
public int Id { get; set; }
public string CustName { get; set; }
public string CustJobTitle { get; set; }
public string CustAge { get; set; }
public bool IsManager { get; set; }
// FKs
public int AgeId { get; set; }
public int CityId { get; set; }
public AgeDTO ageDTO { get; set; }
public CityDTO cityDTO { get; set; }
}
public class CityDTO
{
public int Id { get; set; }
public string CityName { get; set; }
public List<CustomerDTO> customerDTO { get; set; }
}
public class AgeDTO
{
public int Id { get; set; }
public int AgeName { get; set; }
public List<CustomerDTO> customerDTO { get; set; }
}
Update ... showing the whole service, usage, and injection
here is the whole generic repository service and how it looks like
public class Repository<TEntity, TEntityDTO> : IRepository<TEntity, TEntityDTO> where TEntity : class where TEntityDTO : class
{
protected readonly AppDbContext _context;
private readonly DbSet<TEntity> _dbSet;
private readonly IMapper _mapper;
public Repository(AppDbContext context, IMapper mapper)
{
_context = context;
_dbSet = context.Set<TEntity>();
_mapper = mapper;
}
// GENERIC CRUD ...
// and then here where i want to focus
public IQueryable<TEntityDTO> getRowsWithIncludeMultiple(int page = 0, params Expression<Func<TEntityDTO, object>>[] includes)
{
GridSetting gs = GetGrid();
IEnumerable<TEntity> getPage = _dbSet.Skip((page == 0 ? page : page - 1) * gs.ItemsPerPage).Take(gs.ItemsPerPage);
IQueryable<TEntityDTO> rows = _mapper.Map<IEnumerable<TEntityDTO>>(getPage).AsQueryable();
if (includes != null) { rows = includes.Aggregate(rows, (current, include) => current.Include(include)); }
// or
//foreach (var include in includes)
//{
// rows = rows.Include(include);
//}
return rows;
}
}
and then here is how customer service uses generic repo
public class CustomerService : Repository<Customer, CustomerDTO>, ICustomerService
{
public CustomerService(AppDbContext db, IMapper mapper) : base(db, mapper) { }
}
finally injection in Program.cs
builder.Services.AddScoped(typeof(IRepository<,>), typeof(Repository<,>));
builder.Services.AddScoped<ICustomerService, CustomerService>();

thanks #Svyatoslav Danyliv and thanks for all contributors your answer helped me a lot.
cannot apply Include to DTO object
so, I would to share what I have made so far.
instead of
public IQueryable<TEntityDTO> getRowsWithIncludeMultiple(int page = 0, params Expression<Func<TEntityDTO, object>>[] includes)
{
GridSetting gs = GetGrid();
IEnumerable<TEntity> getPage = _dbSet.Skip((page == 0 ? page : page - 1) * gs.ItemsPerPage).Take(gs.ItemsPerPage);
IQueryable<TEntityDTO> rows = _mapper.Map<IEnumerable<TEntityDTO>>(getPage).AsQueryable();
if (includes != null) { rows = includes.Aggregate(rows, (current, include) => current.Include(include)); }
return rows;
}
I applied include to the original entity not to DTO
public IEnumerable<TEntityDTO> IncludeMultiple(int page = 0, params Expression<Func<TEntity, object>>[] includes)
{
GridSetting gs = GetGrid();
IQueryable<TEntity> rows = _dbSet.Skip((page == 0 ? page : page - 1) * gs.ItemsPerPage).Take(gs.ItemsPerPage).AsQueryable();
if (includes != null)
{ rows = includes.Aggregate(rows, (current, include) => current.Include(include).AsQueryable().AsNoTracking()); }
return _mapper.Map<IEnumerable<TEntityDTO>>(rows).AsQueryable();
}
And here is some clarification to make the idea more understandable
public IEnumerable<TEntityDTO> IncludeMultiple(params Expression<Func<TEntity, object>>[] includes)
{
// get IQueryable of TEntity
IQueryable<TEntity> rows = _dbSet.AsQueryable();
// and here using `linq Aggregate` to append multiple include statement
if (includes != null)
{ rows = includes.Aggregate(rows, (current, include) => current.Include(include).AsQueryable().AsNoTracking()); }
// here I use auto mapper to map IEnumerable object to IEnumerable object as a result
return _mapper.Map<IEnumerable<TEntityDTO>>(rows).AsQueryable();
}
and finally the usage
_ServiceName.IncludeMultiple(i => i.city, i => i.age);

Related

How insert multiple value in Intermediate table through API

I use to add value from the VUEJS where write code like this.
<multiselect v-model="schoolTypeform.schoolTypeId" :options="SchoolTypes" :multiple="true" :close-on-select="false" :clear-on-select="false" :preserve-search="true" placeholder="Pick School Type" label="name" track-by="name" :preselect-first="true">
and the JS code for this is written like this:
async addSchool() {
this.isbtnLoading = true;
this.isException = false;
await this.axios.post(this.school, this.form).then(response => {
this.addSchoolType(response.data);
})
},
async addSchoolType(id) {
this.isbtnLoading = true;
this.isException = false;
this.schoolTypeform.shoolId = id;
await this.axios.post(this.apiBasedUrl + '/SchoolsSchoolType', this.schoolTypeform).then(response => {
this.isbtnLoading = false;
});
Now my ER structure is given like this:
School:(Table1)
public partial class Schools
{
public Guid ID { get; set; }
public string Name{ get; set; }
// Navigation
public ICollection<SchoolsSchoolType> SchoolsSchoolTypes { get; set; }
}
SchoolType:(Table2)
public class SchoolType
{
public Guid Id { get; set; }
public string Name { get; set; }
//Navigation
public ICollection<SchoolsSchoolType> SchoolsSchoolTypes { get; set; }
}
SchoolsSchoolType (It is Intermediate table): Here the relation between the above is many to many.
public class SchoolsSchoolType
{
public Guid Id { get; set; }
public Guid ShoolId { get; set; }
public Schools Schools { get; set; }
public Guid SchoolTypeId { get; set; }
public SchoolType SchoolType { get; set; }
}
Here is repository method write for single value input, but I want to add here multiple value in the intermediates or junction table.
public async Task<Guid> CreateSchoolsAsync(SchoolsCreateVm schoolsCreateVm)
{
if (_GpsContext != null)
{
var schoolsEntity = new Schools()
{
ID = Guid.NewGuid(),
Name = schoolsCreateVm.Name,
SchoolsSchoolTypes = new List<SchoolsSchoolType>()
};
var schoolType = new SchoolType();
schoolsEntity.SchoolsSchoolTypes = new List<SchoolsSchoolType>
{
new SchoolsSchoolType
{
ShoolId =schoolsEntity.ID,
SchoolTypeId =schoolType.Id
}
};
return schoolsEntity.ID;
}
return Guid.Empty
}
Controller code is written here:
[HttpPost]
public async Task<IActionResult> PostSchool([FromBody]SchoolsCreateVm schoolsCreateVm)
{
var result = await _schoolsRepository.CreateSchoolsAsync(schoolsCreateVm);
if (result != null)
{
return Ok(result);
}
return NotFound();
}
Here is viewmodel used by me:
public class SchoolsCreateVm
{
public string Name { get; set; }
public List<Guid> SchoolTypeId{ get; set; } // List type of for intermediate table
public SchoolsCreateVm()
{
SchoolTypeId = new List<Guid>();
}
How can insert many schooltype for a single school in the intermediates(many to many) relation table through the VUEJS multiple selects.
Finally I am able to find the solution...
public async Task<Guid> CreateSchoolsAsync(SchoolsCreateVm schoolsCreateVm)
{
if (_GpsContext != null)
{
var schoolId = Guid.NewGuid();
var schoolsEntity = new Schools()
{
ID = schoolId, // 1--[1,2,3]
Name = schoolsCreateVm.Name,
};
// Here the code in which we can enter in the multiple table and Intermediate table
var SchoolsSchoolTypeList = new List<SchoolsSchoolType>();
foreach(var item in schoolsCreateVm.SchoolTypeId)
{
SchoolsSchoolTypeList.Add(new SchoolsSchoolType
{
Id = Guid.NewGuid(),
ShoolId = schoolId,
SchoolTypeId = item,
});
}
await _GpsContext.School.AddAsync(schoolsEntity);
_GpsContext.SchoolsSchoolTypes.AddRange(SchoolsSchoolTypeList);//Enter here for intermediate table that is 'SchoolsSchoolTypes'
await _GpsContext.SaveChangesAsync();
return schoolsEntity.ID;
}
return Guid.Empty;
}

Dynamic Sort expression issue

Let say I have classes hierarchy like
public class A
{
public int AMember { get; set; }
}
public class B
{
public int BMember { get; set; }
public virtual A AasMember { get; set; }
}
public static class OrderByUtility
{
public static bool PropertyExists<T>(string propertyName)
{
return typeof(T).GetProperty(propertyName, BindingFlags.IgnoreCase |
BindingFlags.Public) != null;
}
}
From main class whenever I use this Utility
OrderByUtility.PropertyExists BClass>("BMember");
This works fine and returns TRUE. But whenever I use
OrderByUtility.PropertyExists BClass> ("AMember"); returns False
I want same PropertyExist function work for all Composed Object. Please suggest resolving this issue. Thanks
Here is a really naive implementation of it. You could make it recursive to keep checking nested types. It also keeps a cache so it doesn't do the expensive processing on each lookup.
public class A
{
public int AMember { get; set; }
}
public class B
{
public int BMember { get; set; }
public virtual A AasMember { get; set; }
}
public static class OrderByUtility
{
private static readonly Dictionary<Type, Dictionary<string, bool>> Seen =
new Dictionary<Type, Dictionary<string, bool>>();
public static bool PropertyExists<T>(string propertyName)
{
var type = typeof(T);
if (!Seen.TryGetValue(type, out var props))
{
props = new Dictionary<string, bool>();
Seen[type] = props;
}
if (props.ContainsKey(propertyName))
{
return props[propertyName];
}
var prop = GetPropertyByName(type, propertyName);
if (prop == null)
{
foreach (var p in type.GetProperties())
{
var propType = p.PropertyType;
if (!propType.IsClass && !propType.IsInterface) continue;
prop = GetPropertyByName(propType, propertyName);
if (prop != null)
{
break;
}
}
}
props[propertyName] = prop != null;
return props[propertyName];
}
private static PropertyInfo GetPropertyByName(Type t, string name)
{
return t.GetProperty(name, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
}
}
Usage:
bool exists = OrderByUtility.PropertyExists<B>("AMember");

Convert collections of database entities to collections of view models

I am working on a .NET Core Web API
So far I used to return anonymous types in my controllers but now I want to start using the full power of swagger with auto documentation of the return types.
Which lead me to start using view models.
But I am struggling with converting between the auto-generated database model classes
and the auto-generated swagger view model classes.
It works for a single instance (see GetPerson method in the controller below) but fails when I want to return lists.
So my questions:
How do I cast/convert collections/lists of objects between view models and database models
Is the code in the controller correct? Are there easier/shorter/better ways to do the conversion? (I read about using the implicit operator)
Error message I get:
Cannot implicitly convert type 'System.Linq.IQueryable' to 'System.Collections.Generic.List'. An explicit conversion exists (are you missing a cast?)
It gives me an InvalidCastException if I cast them explicitly like
List result = (List)_dbContext.Person....
there seems to be a problem with generics in the display of stackoverflow
Assume I used the generic lists with giving a type PersonView
My code looks like:
Database models
public partial class Person
{
public Person()
{
}
public int Id { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
public int? MainAdressId { get; set; }
public virtual Adress MainAdress { get; set; }
}
public partial class Adress
{
public Adress()
{
Person = new HashSet();
}
public int Id { get; set; }
public string CityName { get; set; }
public int CityPostalCode { get; set; }
public string StreetName { get; set; }
public string HouseNumber { get; set; }
public string FloorNumber { get; set; }
public string DoorNumber { get; set; }
public virtual ICollection Person { get; set; }
}
View models
public class City
{
public string Name { get; set; }
public int PostalCode { get; set; }
}
public class Street
{
public string Name { get; set; }
public string HouseNumber { get; set; }
public string FloorNumber { get; set; }
public string DoorNumber { get; set; }
}
public class AdressView
{
public Street Street { get; set; }
public City City { get; set; }
}
public class PersonView
{
public string FirstName { get; set; }
public string Lastname { get; set; }
public AdressView Adress { get; set; }
}
The controller class which is working for a single instance but not for lists
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Swashbuckle.SwaggerGen.Annotations;
using PersonExample.ModelsPersonDB;
using PersonExample.ModelsViewPerson;
namespace PersonExample.Controllers
{
[Route("api/[controller]")]
public class PersonViewTestController : Controller
{
private readonly PersonDBContext _dbContext;
private readonly ILogger _logger;
public PersonViewTestController(PersonDBContext dbContext, ILogger logger)
{
_dbContext = dbContext;
_logger = logger;
_logger.LogDebug("{0} > new instance created", GetType().Name);
}
[HttpGet("{id:int}", Name = "GetPerson")]
[ProducesResponseType(typeof(PersonView), 200)]
[SwaggerOperation("GetPerson")]
public virtual IActionResult GetPerson([FromRoute]int id)
{
PersonView result = _dbContext.Person
.Include(p => p.MainAdress)
.Where(p => p.Id == id)
.Select(p => new PersonView()
{
FirstName = p.Firstname,
Lastname = p.Lastname,
Adress = (p.MainAdress == null) ? null :
new AdressView()
{
Street = new Street()
{
Name = p.MainAdress.StreetName,
HouseNumber = p.MainAdress.HouseNumber,
FloorNumber = p.MainAdress.FloorNumber,
DoorNumber = p.MainAdress.DoorNumber
},
City = new City()
{
Name = p.MainAdress.CityName,
PostalCode = p.MainAdress.CityPostalCode
}
}
}
)
.FirstOrDefault();
return new ObjectResult(result);
}
[HttpGet(Name = "GetPersonList")]
[ProducesResponseType(typeof(List), 200)]
[SwaggerOperation("GetPersonList")]
public virtual IActionResult GetPersonList()
{
List result = _dbContext.Person
.Include(p => p.MainAdress)
.Select(p => new PersonView()
{
FirstName = p.Firstname,
Lastname = p.Lastname,
Adress = (p.MainAdress == null) ? null :
new AdressView()
{
Street = new Street()
{
Name = p.MainAdress.StreetName,
HouseNumber = p.MainAdress.HouseNumber,
FloorNumber = p.MainAdress.FloorNumber,
DoorNumber = p.MainAdress.DoorNumber
},
City = new City()
{
Name = p.MainAdress.CityName,
PostalCode = p.MainAdress.CityPostalCode
}
}
}
);
return new ObjectResult(result);
}
}
}
you can use AutoMapper https://github.com/AutoMapper/AutoMapper/wiki/Getting-started
here some examples: Simple Automapper Example
example with EF core and ASP.NET WebApi: https://github.com/chsakell/aspnet5-angular2-typescript
I missed the .ToList() at the end of the query.
The full controller know looks like:
[HttpGet(Name = "GetPersonList")]
[ProducesResponseType(typeof(List), 200)]
[SwaggerOperation("GetPersonList")]
public virtual IActionResult GetPersonList()
{
List result = _dbContext.Person
.Include(p => p.MainAdress)
.Select(p => new PersonView()
{
FirstName = p.Firstname,
Lastname = p.Lastname,
Adress = (p.MainAdress == null) ? null :
new AdressView()
{
Street = new Street()
{
Name = p.MainAdress.StreetName,
HouseNumber = p.MainAdress.HouseNumber,
FloorNumber = p.MainAdress.FloorNumber,
DoorNumber = p.MainAdress.DoorNumber
},
City = new City()
{
Name = p.MainAdress.CityName,
PostalCode = p.MainAdress.CityPostalCode
}
}
}
).ToList(); //missed that line
return new ObjectResult(result);
}

insert data in two class(table) in Entity Framwork by rerlation many to many

i create two class in my model and create relation many to many by Entity
in sql my classes is created tables Properly
when i try to insert data in this table get show error "Object reference not set to an instance of an object." my cod is:
public class News
{
public int ID { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public DateTime Date { get; set; }
public virtual Picture Picture { get; set; }
public virtual NewsType NewsType { get; set; }
public ICollection<Tag> Tag { get; set; }
public News(int id, string title, string content, DateTime date)
{
this.ID = id;
this.Title = title;
this.Content = content;
this.Date = date;
}
public News()
{
}
}
public class Tag
{
public int ID { get; set; }
public string Title { get; set; }
public ICollection<News> News { get; set; }
public Tag()
{
}
}
public class DatabaseContext : DbContext
{
public DatabaseContext()
: base("News")
{
}
static DatabaseContext()
{
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<DatabaseContext>());
}
public DbSet<News> newsInfo { get; set; }
public DbSet<Picture> pictures { get; set; }
public DbSet<NewsType> Types { get; set; }
public DbSet<Tag> Tags { get; set; }
}
[HttpPost]
public ActionResult AddNews(NewsViewModel newsInfo)
{
using (Models.DatabaseContext dbContext = new DatabaseContext())
{
ViewData["Type"] = new SelectList(dbContext.Types.ToList(), "Id", "Title");
}
if (!ModelState.IsValid)
{
return View();
}
else
{
Models.DatabaseContext dbContext = new Models.DatabaseContext();
Models.News news = new Models.News();
news.Title = newsInfo.Title;
news.Content = newsInfo.Content;
news.Date = DateTime.Now;
string newsinput = newsInfo.Tag.cleanTag();
string[] tags = new string[] { };
if (newsinput != null)
{
tags = newsinput.Split(',');
}
foreach (string item in tags)
{
Tag findTag = dbContext.Tags.Where(x => x.Title == item).FirstOrDefault();
if (findTag != null)
{
news.Tag.Add(findTag)
////////////////////////show error in this line
}
}
news.NewsType = dbContext.Types.Find(Convert.ToInt32(Request.Form["rdb"]));
dbContext.newsInfo.Add(news);
dbContext.SaveChanges();
return View();
}

fluentvalidation InclusiveBetween dynamically set range

Im using FluentValidation
I would like to do a range validation using:
InclusiveBetween
RuleFor(x => x.Height)
.InclusiveBetween(x=> x.min, x.max).
I want to be able to get the 'from' and 'to' values dynamically from the model..rather than being hardcoded in the validator
Is this possible?
Thanks
Well, there's nothing in FluentValidation for that.
But you could write your own extension method (and validator), something like that (fast shot, so you'll have to make this better, but you've got the idea).
//the extension method
public static class ValidationExtensions
{
public static IRuleBuilder<T, TProperty> InclusiveBetween<T, TProperty>(this IRuleBuilder<T, TProperty> ruleBuilder, Expression<Func<T, TProperty>> fromExpression, Expression<Func<T, TProperty>> toExpression)
{
var fromFunc = leftExpression.Compile();
var toFunc = rightExpression.Compile();
return ruleBuilder.SetValidator(new InclusiveBetweenPropertyValidator(fromFunc.CoerceToNonGeneric(), fromExpression.GetMember(), toFunc.CoerceToNonGeneric(), toExpression.GetMember()));
}
}
Then the Validator class
public class InclusiveBetweenPropertyValidator : PropertyValidator, IBetweenValidator, IPropertyValidator
{
public Func<object, object> FromFunc { get; set; }
public MemberInfo FromMemberInfo { get; set; }
public Func<object, object> ToFunc { get; set; }
public MemberInfo ToMemberInfo { get; set; }
public IComparable From { get; private set; }
public IComparable To { get; private set; }
public InclusiveBetweenPropertyValidator(Func<object, object> fromFunc, MemberInfo fromMember, Func<object, object> toFunc, MemberInfo toMember)
: base((() => Messages.inclusivebetween_error))
{
FromFunc = fromFunc;
FromMemberInfo = fromMember;
ToFunc = toFunc;
ToMemberInfo = toMember;
}
protected override bool IsValid(PropertyValidatorContext context)
{
var comparable = (IComparable)context.PropertyValue;
From = (IComparable)this.FromFunc(context.Instance);
To = (IComparable)this.ToFunc(context.Instance);
if (comparable == null || FluentValidation.Internal.Comparer.GetComparisonResult(comparable, From) >= 0 && FluentValidation.Internal.Comparer.GetComparisonResult(comparable, To) <= 0)
return true;
context.MessageFormatter.AppendArgument("From", string.Format("{0} ({1})", FromMemberInfo.Name, From)).AppendArgument("To", string.Format("{0} ({1})",ToMemberInfo.Name, To)).AppendArgument("Value", context.PropertyValue);
return false;
}
}
usage :
RuleFor(x => x.Height)
.InclusiveBetween(x=> x.min, x.max)
If you don't want to write an extension you could use the additional overload of the Predicate Validator - which also accepts an instance of the parent object - like this:
RuleFor(x => x.Height)
.Must((model, height) => height >= model.Min && height <= model.Max);
This is similar to Raphaƫl's answer, but is more of a case-by-case usage as opposed to a general usage extension.
RuleFor(x => x).Must(HeightValidation);
private static bool HeightValidation(Model m)
{
return m.Height >= m.min && m.Height <= m.max;
}
Let's imagine your Model as follow:
public class YourModel
{
public int Height { get; set; }
public int Min { get; set; }
public int Max { get; set; }
}
then Validation will be as follow:
public class YourModelValidation : AbstractValidator<YourModel>
{
public YourModelValidation(int min,int max)
{
RuleFor(x => x.Height).InclusiveBetween(min, max);
}
}
then Validation usage is :
var validation = new YourModelValidation(model.Min,model.Max).Validate(model);
as you can see dynamic parameters are passed in validation's Constructor.
you can pass model or dto as Constructor parameter

Resources