Get selected column from iqueryable - c#

How can I return fieldList from an IQueryable object?
// fieldList="Code,Name";
var result = from Activity in query
select new
{
Code = Activity.Code,
Name = Activity.Name,
StatusCode = Activity.ClaimStatus.Name
};

DTO
public class CustomDto
{
public string Code { get; set; }
public string Name { get; set; }
public string StatusCode { get; set; }
}
Convert To Dto
var result = items.AsQueryable().Select(x => new CustomDto()
{
Code = x.Code,
Name = x.Name,
StatusCode = x.ClaimStatus
}).ToList();

Related

Create query by dynamically pass the GroupBy() and create new class in Select() using Expression tree

I`m having simple method which builds IQueryable and returns it.
public IQueryable<ClassDTO> ReportByNestedProperty()
{
IQueryable<Class> query = this.dbSet;
IQueryable<ClassDTO> groupedQuery =
from opportunity in query
group new
{
ItemGroup = opportunity.OpportunityStage.Name,
EstimatedRevenue = opportunity.EstimatedRevenue,
CostOfLead = opportunity.CostOfLead
}
by new
{
opportunity.OpportunityStage.Name,
opportunity.OpportunityStage.Id
}
into item
select new ClassDTO()
{
ItemGroup = string.IsNullOrEmpty(item.Key.Name) ? "[Not Assigned]" : item.Key.Name,
Count = item.Select(z => z.ItemGroup.Name).Count(), // int
Commission = item.Sum(z => z.EstimatedRevenue), // decimal
Cost = item.Sum(z => z.CostOfLead), // decimal?
};
return groupedQuery;
}
This is fine. The thing i need is to create method with same return type, but groupby by different prperties dynamically. So from the above code I want to have 3 dynamic parts which will be passed as params:
ItemGroup = opportunity.OpportunityStage.Name
and
by new
{
opportunity.OpportunityStage.Name,
opportunity.OpportunityStage.Id
}
So the new method should be like this
public IQueryable<ClassDTO> ReportByNestedProperty(string firstNestedGroupByProperty, string secondNestedGroupByProperty)
{
// TODO: ExpressionTree
}
And call it like this:
ReportByNestedProperty("OpportunityStage.Name","OpportunityStage.Id")
ReportByNestedProperty("OtherNestedProperty.Name","OtherNestedProperty.Id")
ReportByNestedProperty("OpportunityStage.Name","OpportunityStage.Price")
So the main thing is to create expressions with these two selects:
opportunity.OpportunityStage.Name,
opportunity.OpportunityStage.Id
I have tried toe create the select expressions, groupby, the creation of Anonomoys classes and the DTO Class but I just cant get it right.
EDIT:
Here are the classes involved:
public class ClassDTO
{
public ClassDTO() { }
[Key]
public string ItemGroup { get; set; }
public decimal Commission { get; set; }
public decimal? Cost { get; set; }
public int Count { get; set; }
}
Class obj is a pretty big one so i`m posting just part of it
public partial class Class
{
public Class() { }
[Key]
public Guid Id { get; set; }
public Guid? OpportunityStageId { get; set; }
[ForeignKey(nameof(OpportunityStageId))]
[InverseProperty(nameof(Entities.OpportunityStage.Class))]
public virtual OpportunityStage OpportunityStage { get; set; }
}
public partial class OpportunityStage
{
public OpportunityStage()
{
this.Classes = new HashSet<Class>();
}
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
[InverseProperty(nameof(Class.OpportunityStage))]
public virtual ICollection<TruckingCompanyOpportunity> Classes{ get; set; }
}
I have simplified your Grouping query and introduced private class IdName which should replace anonymous class usage:
class IdName
{
public int Id { get; set; }
public string Name { get; set; } = null!;
}
static Expression MakePropPath(Expression objExpression, string path)
{
return path.Split('.').Aggregate(objExpression, Expression.PropertyOrField);
}
IQueryable<ClassDTO> ReportByNestedProperty(IQueryable<Class> query, string nameProperty, string idProperty)
{
// Let compiler to do half of the work
Expression<Func<Class, string, int, IdName>> keySelectorTemplate = (opportunity, name, id) =>
new IdName { Name = name, Id = id };
var param = keySelectorTemplate.Parameters[0];
// generating expressions from prop path
var nameExpr = MakePropPath(param, nameProperty);
var idExpr = MakePropPath(param, idProperty);
var body = keySelectorTemplate.Body;
// substitute parameters
body = ReplacingExpressionVisitor.Replace(keySelectorTemplate.Parameters[1], nameExpr, body);
body = ReplacingExpressionVisitor.Replace(keySelectorTemplate.Parameters[2], idExpr, body);
var keySelectorLambda = Expression.Lambda<Func<Class, IdName>>(body, param);
// finalize query
IQueryable<ClassDTO> groupedQuery = query
.GroupBy(keySelectorLambda)
.Select(item => new ClassDTO()
{
ItemGroup = string.IsNullOrEmpty(item.Key.Name) ? "[Not Assigned]" : item.Key.Name,
Count = item.Count(x => x.Name), // int
Commission = item.Sum(x => x.EstimatedRevenue), // decimal
Cost = item.Sum(x => x.CostOfLead), // decimal?
});
return groupedQuery;
}

How to convert data from mongodb document to List

I Create a web-site and I have a problem. When I'm tring to get datas from mongodb and convert it to list, I have an error "Cannot apply indexing with [] to an expression of type 'CategoryModel'"
this is classes
public class CategoryModel
{
[BsonId]
public ObjectId id { get; set; }
[BsonElement("title")]
[JsonProperty("title")]
public string Name { get; set; }
[BsonElement("slug")]
public string slug { get; set; }
[BsonElement("__v")]
public int __v { get; set; }
}
public class ProductsModel
{
[BsonId]
public ObjectId id { get; set; }
[BsonElement("title")]
public string Name { get; set; }
[BsonElement("desc")]
public string Desc { get; set; }
[BsonElement("price")]
public int price { get; set; }
[BsonElement("category")]
public CategoryModel category { get; set; }
[BsonElement("image")]
public string ImageURL { get; set; }
}
this is my conntroller
public class ProductsController:Controller
{
private readonly IConfiguration _configuration;
List<CategoryModel> categoriesList = new List<CategoryModel>();
List<ProductsModel> productsList = new List<ProductsModel>();
[HttpGet("products")]
public async Task<IActionResult> Product()
{
var client = new MongoClient("mongodb://localhost:27017/");
var database = client.GetDatabase("cmscart");
var collection = database.GetCollection<CategoryModel>("categories");
var result = await collection.Find(new BsonDocument()).ToListAsync();
foreach (var item in result)
{
categoriesList.Add(new CategoryModel() { Name = (string)item["[title]"] }); //here I have an error
}
//products
var client2 = new MongoClient("mongodb://localhost:27017");
var database2 = client2.GetDatabase("cmscart");
var collection2 = database2.GetCollection<ProductsModel>("products");
var result2 = await collection2.Find(new BsonDocument()).ToListAsync();
foreach (var item2 in result2)
{
productsList.Add(new ProductsModel() { Name = (string)item2["title"], Desc = (string)item2["desc"], price = (int)item2["price"], ImageURL = (string)item2["image"] }); // and here I have an error
}
return View(categoryProduct);
}
}
I found a lot of solutions but I don't understand why this error is appear, because if this trick do with SQL then I don't have this error
You should be able to use the deserialized object directly:
var client = new MongoClient("mongodb://localhost:27017/");
var database = client.GetDatabase("cmscart");
var collection = database.GetCollection<CategoryModel>("categories");
List<CategoryModel> categoriesList = await collection.Find(new BsonDocument()).ToListAsync();
//products
var collection2 = database2.GetCollection<ProductsModel>("products");
List<ProductsModel> products = await collection2.Find(new BsonDocument()).ToListAsync();
Also, don't use class properties for local variables, declare everything in the innermost scope possible (in general). Ideally the MongoClient or Database would be injected into the class constructor too. You don't want to be instantiating them in an action method and you definitely don't want configuration values in there.

Get and Add/Update multilevel embedded/nested MongoDB document using C#

How can i make filter and add/update third, fourth level child of a mongodb document using C#.
I can add/Update till second level but not further. Please provide me a solution or any kind reference from where can get help. Is there any other way to do it except builders..Elemmatch.
Here is my classes and code:
namespace CrudWithMultilvelNestedDoc
{
public class Channel
{
[BsonId]
[BsonRepresentation(BsonType.String)]
public string Id { get; set; }
public string Name { get; set; }
public Episode[] Episodes { get; set; }
}
public class Episode
{
[BsonId]
[BsonRepresentation(BsonType.String)]
public string Id { get; set; }
public string Name { get; set; }
public Track[] Tracks { get; set; }
}
public class Track
{
[BsonId]
[BsonRepresentation(BsonType.String)]
public string Id { get; set; }
public string Name { get; set; }
public Like[] Likes { get; set; }
}
public class Like
{
[BsonId]
[BsonRepresentation(BsonType.String)]
public string Id { get; set; }
public string Name { get; set; }
}
}
//First Method
//var filter = Builders<Channel>.Filter.And(Builders<Channel>
// .Filter.Where(x => x.Id == "5e4606e6ae7b090688671416"), // OR &
// Builders<Channel>.Filter.ElemMatch(e => e.Episodes, Builders<Episode>
// .Filter.Eq(e => e.Id, "5e460851d29c1b3df4d27b7d")));
//Second Method
//var filter = Builders<Channel>.Filter.Eq(e => e.Id, "5e4606e6ae7b090688671416")
// & Builders<Channel>.Filter.ElemMatch(e => e.Episodes, Builders<Episode>.Filter.Eq(e => e.Id, "5e46071d385a672b0cea0f86"));
//Third Method
var filter = channelFilter.ElemMatch(e => e.Episodes, episodeFilter.ElemMatch(e=> e.Tracks, trackFilter.Eq(e => e.Id, "5e460dbe2bc5e70c9cfeac21")));
var data = collection.Find(filter);
//Update Filter
var update = Builders<Channel>.Update.Push("Episodes[-1].Tracks[-1].Likes", like);
var result = collection.UpdateOne(filter, update);
//Data
{"_id":"5e4606e6ae7b090688671416","Name":"Channel 1","Episodes":[{"_id":"5e46071d385a672b0cea0f86","Name":"Episode 1","Tracks":[{"_id":"5e460dbe2bc5e70c9cfeac21","Name":"Trak 1","Likes":[]},{"_id":"5e4612d60747a2121870c815","Name":"Trak 2","Likes":[]}]},{"_id":"5e460851d29c1b3df4d27b7d","Name":"Episode 2","Tracks":[{"_id":"5e460e307ca6843758ce814e","Name":"Trak 1","Likes":[]}]}]}
As per documentation:
The positional $ operator cannot be used for queries which traverse more than one array
So using -1 is not a way forward here. The approach you should take is the $ positional filtered operator.
There's no strongly-typed representation in C# so your code can look like below:
var filter = Builders<Channel>.Filter.Eq(x => x.Id, "5e4606e6ae7b090688671416");
var like = new Like() {Name = "new like", Id = "1"};
var episodeId = "5e46071d385a672b0cea0f86";
var trackId = "5e460dbe2bc5e70c9cfeac21";
var update = Builders<Channel>.Update.Push("Episodes.$[e].Tracks.$[t].Likes", like);
var arrayFilters = new List<ArrayFilterDefinition>();
ArrayFilterDefinition<BsonDocument> episodesFilter = new BsonDocument("e._id", new BsonDocument("$eq", episodeId));
ArrayFilterDefinition<BsonDocument> tracksFilter = new BsonDocument("t._id", new BsonDocument("$eq", trackId));
arrayFilters.Add(episodesFilter);
arrayFilters.Add(tracksFilter);
var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters };
var result = mongoDBCollection.UpdateOne(filter, update, updateOptions);

Empty data on List in LinQ Statement

I got this LinQ statement
var daysList = new List<int>(new int[30]);
var model_pdv = db_pdv.Pdv.GroupBy(x => new { Pdv = x.Clave_PDV, Nombre_Pdv = x.Nombre_Pdv})
.Select(x => new DishVM()
{
Clave_PDV = x.Key.Pdv,
Nombre_Pdv = x.Key.Nombre_Pdv,
Days = daysList,
Data = x
}).ToList();
However i dont know why my "Data" List inside my LinQ gets empty values the first time but then i saves the LinQ as it should
This is my DishVm Class:
public class DishVM
{
public string Clave_PDV { get; set; }
public string Nombre_Pdv { get; set; }
public IEnumerable<Pdv> Data { get; set; }
}
And my Pdv class:
public class Pdv
{
public string Clave_PDV { get; set; }
public string Nombre_Pdv { get; set; }
}
How can i avoid the empty Data List?
The type of x within your Select statement is IGrouping, change it to produce a list:
var model_pdv = db_pdv.Pdv.GroupBy(x => new { Pdv = x.Clave_PDV, Nombre_Pdv = x.Nombre_Pdv})
.Select(x => new DishVM()
{
Clave_PDV = x.Key.Pdv,
Nombre_Pdv = x.Key.Nombre_Pdv,
Days = daysList,
Data = x.ToList()
}).ToList();

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);

Categories

Resources