Linq to get a list of models per Make into DTO - entity-framework

I have a DTO class like this
public string Make { get; set; };
public List<string> Models { get; set; }
Then there is a table which contains a list of vehicles, with make and model columns.
My API endpoint accepts a list of strings (the Makes)
I need to return a list of the DTO class with each make and the list of models.
public async Task<ActionResult<List<MakeModelDTO>>> GetModelsByMakes([FromQuery] List<string> make_list)
{
return await _context.Vehicles.Where(x => x.Make.????).Select(x => x.Model).Distinct().ToListAsync();
}
I don't even want to show the code I've tried, because all versions turned out to be a mess.
I know this is suppose to be a very simple task, I just can't figure it out.

Firstly, In condition you can do this
Where(x => make_list.Contains(x.Make)
or
Where(x => make_list.Any(m => m == x.Make)
Secondly, the method is returning List<MakeModelDTO>, So you should adjust select result like below
.GroupBy(p => p.Make).Select(g =>
new MakeModelDTO { Make = g.Key, Models = g.Select(p => p.Model).ToList() }).ToListAsync();
FullCode
public async Task<ActionResult<List<MakeModelDTO>>> GetModelsByMakes([FromQuery] List<string> make_list)
{
return await _context.Vehicles.Where(x => make_list.Contains(x.Make)).GroupBy(p => p.Make).Select(g =>
new MakeModelDTO { Make = g.Key, Models = g.Select(p => p.Model).ToList() }).ToListAsync();
}

As #Fabio suggested you can filter the make like that. Adding to it convert to your DTO you need to group by that also.
You could try something like this
public async Task<ActionResult<List<MakeModelDTO>>> GetModelsByMakes([FromQuery] List<string> make_list)
{
var resultAsDto = await _context.Vehicles
.Where(vehicle => makeList.Contains(vehicle.Make))
.GroupBy(v=>v.Make)
.Select(g=> new YourDto
{
Make= g.Key -- As it grouped by make
Models = g.Select(v=>v.Model)
}).ToListAsync()
return resultAsDto
}

Related

Get dependent ids when querying principal

I'm trying to get just the ids for dependents if a principal is queried, every time the principal is queried.
My initial thought is to add it somehow in the OnModelCreating definitions, however that appears to be limited to filtering down larger sets of data, unless I'm missing something.
Something like this:
builder.Entity<ListingModel>()
.AlsoDoThis(
x => x.MenuIds.AddRange(
Menus.Where(y => y.ListingId == x.Id).Select(y => y.Id).ToList()
)
);
There is a need to not do this in code for each individual place I have a Select, since that functionality is normalized in some base classes. The base classes have a <TModel> passed in and don't inherently know what properties need to be handled this way.
I do have a workaround where I'm grabbing everything with an AutoInclude(), then filtering it out in the model definition with customer getter/setter to return a list of ids. But rather than being more performant (grabbing related FK ids at the DB level) it's transferring all of that data to the server and then programmatically selecting a list of ids, as far as I understand it.
private List<int> _topicsIds = new();
[NotMapped]
public List<int> TopicsIds
{
get { return Topics.Count > 0 ? Topics.Select(x => x.Id).ToList() : _topicsIds; }
set { _topicsIds = value; }
}
public List<TopicModel> Topics { get; set; } = new();
"Extra SQL that gets called with every select in a context" is (to my limited knowledge) almost what HasQueryFilter does, with a just slightly broader operation. I think this is the approach I'm looking for, just selecting more stuff instead of filtering stuff out.
You can project everything via Select
var result = ctx.ListingModels
.Select(lm => new // or to DTO
{
Id = lm.Id,
OtherProperty = lm.OtherProperty,
Ids = x.MenuIds.Select(m => m.Id).ToList()
})
.ToList();
To make more general solution we can use annotations and define how to project such entities.
During Model defining:
builder.Entity<TopicModel>()
.WithProjection(
x => x.MenuIds,
x => x.Menus.Where(y => y.ListingId == x.Id).Select(y => y.Id).ToList()
);
Then usage in common code:
public virtual List<TModel> GetList(List<int> ids)
{
var list = _context.Set<TModel>().Where(x => ids.Any(id => id == x.Id))
.ApplyCustomProjection(_context)
.ToList();
return list;
}
ApplyCustomProjection(_context) will find previously defined annotation and will apply custom projection.
And extensions implementation:
public static class ProjectionExtensions
{
public const string CustomProjectionAnnotation = "custom:member_projection";
public class ProjectionInfo
{
public ProjectionInfo(MemberInfo member, LambdaExpression expression)
{
Member = member;
Expression = expression;
}
public MemberInfo Member { get; }
public LambdaExpression Expression { get; }
}
public static bool IsUnderDotnetTool { get; }
= Process.GetCurrentProcess().ProcessName == "dotnet";
public static EntityTypeBuilder<TEntity> WithProjection<TEntity, TValue>(
this EntityTypeBuilder<TEntity> entity,
Expression<Func<TEntity, TValue>> propExpression,
Expression<Func<TEntity, TValue>> assignmentExpression)
where TEntity : class
{
// avoid registering non serializable annotations during migrations update
if (IsUnderDotnetTool)
return entity;
var annotation = entity.Metadata.FindAnnotation(CustomProjectionAnnotation);
var projections = annotation?.Value as List<ProjectionInfo> ?? new List<ProjectionInfo>();
if (propExpression.Body is not MemberExpression memberExpression)
throw new InvalidOperationException($"'{propExpression.Body}' is not member expression");
if (memberExpression.Expression is not ParameterExpression)
throw new InvalidOperationException($"'{memberExpression.Expression}' is not parameter expression. Only single nesting is allowed");
// removing duplicate
projections.RemoveAll(p => p.Member == memberExpression.Member);
projections.Add(new ProjectionInfo(memberExpression.Member, assignmentExpression));
return entity.HasAnnotation(CustomProjectionAnnotation, projections);
}
public static IQueryable<TEntity> ApplyCustomProjection<TEntity>(this IQueryable<TEntity> query, DbContext context)
where TEntity : class
{
var et = context.Model.FindEntityType(typeof(TEntity));
var projections = et?.FindAnnotation(CustomProjectionAnnotation)?.Value as List<ProjectionInfo>;
// nothing to do
if (projections == null || et == null)
return query;
var propertiesForProjection = et.GetProperties().Where(p =>
p.PropertyInfo != null && projections.All(pr => pr.Member != p.PropertyInfo))
.ToList();
var entityParam = Expression.Parameter(typeof(TEntity), "e");
var memberBinding = new MemberBinding[propertiesForProjection.Count + projections.Count];
for (int i = 0; i < propertiesForProjection.Count; i++)
{
var propertyInfo = propertiesForProjection[i].PropertyInfo!;
memberBinding[i] = Expression.Bind(propertyInfo, Expression.MakeMemberAccess(entityParam, propertyInfo));
}
for (int i = 0; i < projections.Count; i++)
{
var projection = projections[i];
var expression = projection.Expression.Body;
var assignExpression = ReplacingExpressionVisitor.Replace(projection.Expression.Parameters[0], entityParam, expression);
memberBinding[propertiesForProjection.Count + i] = Expression.Bind(projection.Member, assignExpression);
}
var memberInit = Expression.MemberInit(Expression.New(typeof(TEntity)), memberBinding);
var selectLambda = Expression.Lambda<Func<TEntity, TEntity>>(memberInit, entityParam);
var newQuery = query.Select(selectLambda);
return newQuery;
}
}

Delete multiple row with ids without foreach and postman test

I have a little problem. But I dont know why it doesnt work. And I dont know how to post all ids by postman.
I am using unit of work with generic repository. I want to send int[] ids to my controller. I dont want to send entity. I searched a lot it today. And I changed my code. But what is problem now?
This is my repostiroy:
public async Task DeleteRangeAsync(Expression<Func<T, bool>> predicate)
{
IQueryable<T> query = _dbSet.Where(predicate);
await Task.Run(() => { _dbSet.RemoveRange(query.AsNoTracking()); });
}
This is my KulturManager:
public async Task<IResult> HardDeleteRangeAsync(int[] ids)
{
await UnitOfWork.Kulturs.DeleteRangeAsync(c => ids.Contains(c.Id));
await UnitOfWork.SaveAsync();
return new Result(ResultStatus.Success, Messages.Info("Kultur", "HardDelete"));
}
And this is my KulturController:
[HttpDelete("{ids}")]
public async Task<IActionResult> HardDeleteRangeAsync(int[] ids)
{
var result = await _kulturManager.HardDeleteRangeAsync(ids);
return Ok(result.Message);
}
Thank you for help
You shouldn't fetch all the entities you want to delete. Instead create stub entities for RemoveRange. If you don't have a common base class, this requires reflection, but with a common entity base class you can do it like this:
public void DeleteRange<T>(int[] ids) where T: BaseEntity, new()
{
_dbSet.RemoveRange(ids.Select(i => new T() { Id = i }).ToList());
}
or if the method is in a generic class, the method would look like
public void DeleteRange(int[] ids)
{
_dbSet.RemoveRange(ids.Select(i => new T() { Id = i }).ToList());
}
And there's no reason to mark this as Async now since it doesn't do any database access.

Date comparison with Npgsql EF Core

I am trying to find records in a Postgresql table with a column of type Date using a simple equality. I am using DateTime properties (not noda)
I am sure that I am missing something very basic because I am not succeeding after trying many different ways.
The following code does not return any result even though the date is equal to Dates in the database:
var m = new Model {
dateprop = default(DateTime)
}
var result = await context.Model
.Where(a => a.dateprop == m.dateprop)
.ToListAsync()
In the database, there are plenty of records with such date, 0001-01-01, in the column.
Besides, I have tried:
.Where(a => a.dateprop == m.dateprop.Date)
.Where(a => a.dateprop.Date == m.dateprop.Date)
.Where(a => a.dateprop.Date == m.dateprop)
.Where(a => a.dateprop.Equals( m.dateprop)) and all the varieties above
Then, if I get the query that EF produces for .Where(a => a.dateprop.Date == m.dateprop.Date) and run it from a SQL client, it works and returns all the records.
What is the correct way to define the Date equality condition in the where clause?
Edit
I have edited the question since it was misleading, my apologies. Originally, the code showed as:
var m = new Model {
dateprop = DateTime.Parse("1970-01-01")
}
var result = await context.Model
.Where(a => a.dateprop == m.dateprop)
.ToListAsync()
The following runnable code sample works. I'd double-check my database to see exactly what's in there and whether it corresponds to default(DateTime), and if you're still convinced there's an issue, try tweaking the code below to make it appear.
class Program
{
static async Task Main(string[] args)
{
await using var ctx = new BlogContext();
await ctx.Database.EnsureDeletedAsync();
await ctx.Database.EnsureCreatedAsync();
var m = new Model
{
DateProperty = default
};
var result = await ctx.Model
.Where(a => a.DateProperty == m.DateProperty)
.ToListAsync();
Console.WriteLine($"Count: {result.Count}");
}
}
public class BlogContext : DbContext
{
public DbSet<Model> Model { get; set; }
static ILoggerFactory ContextLoggerFactory
=> LoggerFactory.Create(b => b.AddConsole().AddFilter("", LogLevel.Information));
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseNpgsql(#"Host=localhost;Username=test;Password=test")
.EnableSensitiveDataLogging()
.UseLoggerFactory(ContextLoggerFactory);
protected override void OnModelCreating(ModelBuilder modelBuilder)
=> modelBuilder.Entity<Model>().HasData(
new Model { Id = -1, DateProperty = default });
}
public class Model
{
public int Id { get; set; }
public DateTime DateProperty { get; set; }
}

Order By in Linq to Entities

I had sql table with two columns sizename and orderof . I want to select from that table all the sizenames but in ascending order of the orderof .Iam using EF6 and Linq to Entities
I had used the Query Like this .But its not working(sorting)
var sizedetails = (from size in enty.StyleSizes
where size.OurStyleID == ourstyleid
orderby size.Orderof
select new
{
size.SizeName
}).Distinct();
//var sizedetails = enty .StyleSizes.Where(u => u.OurStyleID == ourstyleid).Select(u => u.SizeName ).Distinct();
foreach (var sizedet in sizedetails)
{
dt.Columns.Add(sizedet.SizeName.Trim(), typeof(String));
}
I know this may be already asked. But none of the solution provided in those questions working for me
Since LINQ to Entities translates your query to SQL, ordering before Distinct has no effect. And the problem is that after Distinct you have no access to the property needed for ordering.
So you need an alternative way, which luckily is the GroupBy method - its similar to Distinct but allows you to access the properties of the elements sharing the same key. Thus you can order the result based on some aggregates (in your case looks like Min is the appropriate):
var sizedetails = from size in enty.StyleSizes
where size.OurStyleID == ourstyleid
group size by size.SizeName into sizeGroup
orderby sizeGroup.Min(size => size.Orderof)
select new
{
SizeName = sizeGroup.Key
};
I dint tried with DB but with in memory collection it gives be correct result .
here is my class .
class StyleSizes
{
public int Orderof { get; set; }
public string SizeName { get; set; }
public int OurStyleID { get; set; }
}
// logic to ue orderby
var list = new List<StyleSizes> { new StyleSizes { Orderof=2,SizeName="B",OurStyleID=1 },
new StyleSizes { Orderof=11,SizeName="C" ,OurStyleID=2},
new StyleSizes { Orderof=9,SizeName="D" ,OurStyleID=1},
new StyleSizes { Orderof=9,SizeName="D" ,OurStyleID=1},
new StyleSizes { Orderof=3,SizeName="E" ,OurStyleID=1},
new StyleSizes { Orderof=4,SizeName="F" ,OurStyleID=1}
};
var orderList = list.Where(x=>x.OurStyleID==1).OrderBy(x => x.Orderof).Select(c => new { c.SizeName }).Distinct();

Entity Framework 6 dbContext not saving changes

In our MVC 5 project our database context is instantiate in the AccountController like this
private CustomersContext _customersContext;
public CustomersContext CustContext
{
get
{
return _customersContext ?? new CustomersContext();
}
private set
{
_customersContext = value;
}
}
Each customer is referred by a number of sources. The routine below changes the UserId of the referral source to a new user.
var referralList = CustContext.Referrals.Where(d => d.UserId == membershipUser.Id);
foreach (Referral referral in referralList)
{
referral.UserId = newUser.Id;
}
Stepping trough the code I can see referral.UserId being updated. However
var result = await CustContext.SaveChangesAsync();
returns 0. The database is not updated.
CustomersContext looks like this
{
public partial class CustomersContext : IdentityDbContext<ApplicationUser>//, ICustomersContext
{
public CustomersContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static CustomersContext Create()
{
return new CustomersContext();
}
public virtual DbSet<ReferralSource> ReferralSources { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ApplicationUser>()
.HasMany(e => e.Referrals)
.WithRequired(e => e.User)
.HasForeignKey(e => e.UserId)
.WillCascadeOnDelete(false);
I don't see any sql emitted in SQL Profiler. Why doesn't the database context save changes?
Before calling var result = await CustContext.SaveChangesAsync(); you need to set the state of the entities that you want to be modified. Somthing like:
var referralList = CustContext.Referrals.Where(d => d.UserId == membershipUser.Id);
foreach (Referral referral in referralList)
{
referral.UserId = newUser.Id;
CustContext.Entry(referral).State = System.Data.Entity.EntityState.Modified;
}
var result = await CustContext.SaveChangesAsync();
The answer provided by #Issac did not solve my problem, but it did put me on the road to a solution. The error
An entity object cannot be referenced by multiple instances of IEntityChangeTracker.
suggested there were multiple instances of dbContext. I removed the context from the CTOR and instantiated the context within a using statement
using (CustomersContext customersContext = new CustomersContext())
{
var referralList = customersContext.Referrals.Where(d => d.UserId == membershipUser.Id);
foreach (Referral referral in referralList)
{
referral.UserId = newUser.Id;
}
var result = await customersContext.SaveChangesAsync();
}
and now all is tickety-boo