Reuse DTO mappings across both single and list variants - entity-framework

We regularily write extension methods like this that convert from Database objects to DTO objects for use elsewhere in our system.
As you can see in the example below, the actual mapping code is repeated. Is it possible to write a reusable select mapping that can be used in both of these methods?
public static async Task<List<Group>> ToCommonListAsync(this IQueryable<DataLayer.Models.Group> entityGroups)
{
var groups =
await entityGroups.Select(
g =>
new Group()
{
Id = g.Id,
AccountId = g.AccountId,
Name = g.Name,
ParentId = g.ParentId,
UserIds = g.GroupUserMappings.Select(d => d.UserId).ToList()
}).ToListAsync();
return groups;
}
public static async Task<Group> ToCommonFirstAsync(this IQueryable<DataLayer.Models.Group> entityGroups)
{
var group =
await entityGroups.Select(
g =>
new Group()
{
Id = g.Id,
AccountId = g.AccountId,
Name = g.Name,
ParentId = g.ParentId,
UserIds = g.GroupUserMappings.Select(d => d.UserId).ToList()
}).FirstOrDefaultAsync();
return group;
}

You could move your mapping/projection code out into a variable like this:
public static class Extensions
{
private static readonly Expression<Func<DataLayer.Models.Group, Group>> Projection = g =>
new Group
{
Id = g.Id,
AccountId = g.AccountId,
Name = g.Name,
ParentId = g.ParentId,
UserIds = g.GroupUserMappings.Select(d => d.UserId).ToList()
};
public static async Task<List<Group>> ToCommonListAsync(this IQueryable<DataLayer.Models.Group> entityGroups)
{
return await entityGroups.Select(Projection).ToListAsync();
}
public static async Task<Group> ToCommonFirstAsync(this IQueryable<DataLayer.Models.Group> entityGroups)
{
return await entityGroups.Select(Projection).FirstOrDefaultAsync();
}
}

Related

Filters not working in my repository in ASP.NET Core

I have these parameters in a class:
public class UserParams
{
public string Gender {get; set;}
public int MinAge {get; set;} = 1;
public int MaxAge {get; set;} = 19;
}
The query is done in the repository as shown below. First is to query for the child sex or gender and the second is to query for the child sex or gender
var query = _context.Children.AsQueryable();
query = query.Where(c => c.Sex == userParams.Gender);
var minchildDob = DateTime.Today.AddYears(-userParams.MaxAge - 1);
var maxchildDob = DateTime.Today.AddYears(-userParams.MinAge);
query = query.Where(u => u.DateOfBirth >= minchildDob && u.DateOfBirth <= maxchildDob);
return await PagedList<Child>.CreateAsync(query.AsNoTracking(), userParams.PageNumber, userParams.PageSize);
The gender filter returns empty array of children and the minchildDob and maxchildDob too not working
if (!string.IsNullOrEmpty(temp.Gender))
{
all = all.Where(u => new[] { "men", "women" }.Contains(u.sex));
//all = all.Where(t => t.sex == temp.Gender);
}
=======================Update=======================
var temp = new UserParams();
temp.Gender = "men";
var minchildDob = DateTime.Today.AddYears(-temp.MaxAge - 1);
var maxchildDob = DateTime.Today.AddYears(-temp.MinAge);
IEnumerable<Table> all = from m in _context.data
select m;
_logger.LogError("all data");
foreach (var item in all)
{
_logger.LogError(item.name);
}
_logger.LogError("============================================");
if (!string.IsNullOrEmpty(temp.Gender)) {
all = all.Where(t => t.sex == temp.Gender);
}
_logger.LogError("filter gender");
foreach (var item in all) {
_logger.LogError(item.name);
}
_logger.LogError("============================================");
if (temp.MaxAge > 0) {
all = all.Where(t => t.birthday >= minchildDob && t.birthday <= maxchildDob);
}
_logger.LogError("filter age");
foreach (var item in all)
{
_logger.LogError(item.name);
}
_logger.LogError("============================================");

How to loop through dbcontext all dbset in Entity Framework Core to get count?

I have 20 Dbsets in my context which I want to get the row count for each dbset to make sure all dbset row count is 0. To get the count for one dbset, this is my code:
var person = context.Persons.Count();
Is there a way to loop through the context, get the count for each dbset dynamically?
There is solution. Usage is simple:
var tablesinfo = ctx.GetTablesInfo();
if (tablesinfo != null)
{
var withRecords = tablesinfo
.IgnoreQueryFilters()
.Where(ti => ti.RecordCount > 0)
.ToArray();
}
Extension returns IQueryable<TableInfo> and you can reuse this query later. Probably you will need to filter out Views, but I think you can handle that. Note that IgnoreQueryFilters can be important if you have Global Query Filters defined.
What extension do:
It scans Model for entity types registered for particular DbContext and generates big Concat of Count queries. Here we have to do that via grouping by constant value.
Schematically it will generate the following LINQ query:
var tablesinfo =
ctx.Set<Entity1>.GroupBy(e => 1).Select(g => new TableInfo { TableName = "Entity1", RecordCount = g.Count()})
.Concat(ctx.Set<Entity2>.GroupBy(e => 1).Select(g => new TableInfo { TableName = "Entity2", RecordCount = g.Count()}))
.Concat(ctx.Set<Entity3>.GroupBy(e => 1).Select(g => new TableInfo { TableName = "Entity3", RecordCount = g.Count()}))
...
Which wll be converted to the following SQL:
SELECT "Entity1" AS TableName, COUNT(*) AS RecordCount FROM Entity1
UNION ALL
SELECT "Entity2" AS TableName, COUNT(*) AS RecordCount FROM Entity2
UNION ALL
SELECT "Entity3" AS TableName, COUNT(*) AS RecordCount FROM Entity3
...
Implementation:
public static class QueryableExtensions
{
public class TableInfo
{
public string TableName { get; set; } = null!;
public int RecordCount { get; set; }
}
public static IQueryable<TableInfo> GetTablesInfo(this DbContext ctx)
{
Expression query = null;
IQueryProvider provider = null;
var ctxConst = Expression.Constant(ctx);
var groupingKey = Expression.Constant(1);
// gathering information for MemberInit creation
var newExpression = Expression.New(typeof(TableInfo).GetConstructor(Type.EmptyTypes));
var tableNameProperty = typeof(TableInfo).GetProperty(nameof(TableInfo.TableName));
var recordCountProperty = typeof(TableInfo).GetProperty(nameof(TableInfo.RecordCount));
foreach (var entityType in ctx.Model.GetEntityTypes())
{
var entityParam = Expression.Parameter(entityType.ClrType, "e");
var tableName = entityType.GetTableName();
// ctx.Set<entityType>()
var setQuery = Expression.Call(ctxConst, nameof(DbContext.Set), new[] {entityType.ClrType});
// here we initialize IQueryProvider, which is needed for creating final query
provider ??= ((IQueryable) Expression.Lambda(setQuery).Compile().DynamicInvoke()).Provider;
// grouping paraneter has generic type, we have to specify it
var groupingParameter = Expression.Parameter(typeof(IGrouping<,>).MakeGenericType(typeof(int), entityParam.Type), "g");
// g => new TableInfo { TableName = "tableName", RecordCount = g.Count() }
var selector = Expression.MemberInit(newExpression,
Expression.Bind(tableNameProperty, Expression.Constant(tableName)),
Expression.Bind(recordCountProperty,
Expression.Call(typeof(Enumerable), nameof(Enumerable.Count), new[] {entityParam.Type}, groupingParameter)));
// ctx.Set<entityType>.GroupBy(e => 1)
var groupByCall = Expression.Call(typeof(Queryable), nameof(Queryable.GroupBy), new[]
{
entityParam.Type,
typeof(int)
},
setQuery,
Expression.Lambda(groupingKey, entityParam)
);
// ctx.Set<entityType>.GroupBy(e => 1).Select(g => new TableInfo { TableName = "tableName", RecordCount = g.Count()}))
groupByCall = Expression.Call(typeof(Queryable), nameof(Queryable.Select),
new[] {groupingParameter.Type, typeof(TableInfo)},
groupByCall,
Expression.Lambda(selector, groupingParameter));
// generate Concat if needed
if (query != null)
query = Expression.Call(typeof(Queryable), nameof(Queryable.Concat), new[] {typeof(TableInfo)}, query,
groupByCall);
else
query = groupByCall;
}
// unusual situation, but Model can have no registered entities
if (query == null)
return null;
return provider.CreateQuery<TableInfo>(query);
}
}

EF Core - how to select count from child table based on foreign key

TableB has a field TableAId which is linked to the Id of TableA. I want to select the count from TableB based on TableAId like -
SELECT *,
(SELECT COUNT(*) FROM tableB where tableB.TableAId = tableA.Id) as count
FROM tableA
So far I have the code:
var data = _context.TableA.AsQueryable();
...
data = data.Select(l => l.TableAId= p.Id).Count();
But on the last line, p is not recognized as a variable.
How do I make this work?
EDIT :
my original query is quiet complex and already filtering data
var data = _context.TableA.AsQueryable();
data = data.Include(p => p.SomeClassA)
.Include(p => p.SomeClassB);
data = data.Where(p => p.Id == somevalue);
data = data.Where(p => p.SomeClassA.Name.Contains(someothervalue));
data = data.Where(p => p.SomeClassA.SomeField.Contains(yetanothervalue));
I tried adding this but it cannot compile
(TableAId & Count do not exist):
data = data.Join(
_context.TableB,
groupByQuery => groupByQuery.TableAId ,
TableA => TableA.Id,
(groupByQuery, TableAItem) => new
{
TableAId = groupByQuery.Id,
Count = groupByQuery.Count,
TableAItem = TableAItem
}
);
If you are just interested in count, then do the following:
var data = _context.TableB.AsQueryable();
var groupByCountQuery = data.GroupBy(a=>a.TableAId, (tableAId, tableBItems) => new
{
TableAId = tableAId,
Count = tableBItems.Count()
});
var result = groupByCountQuery.ToList(); // or change to ToListAsync()
This will give you the count based on TableAId.
If you need the tableA items as well in the result, following ca be done:
var groupByCountQuery = data.GroupBy(a=>a.TableAId, (tableAId, tableBItems) => new
{
TableAId = tableAId,
Count = tableBItems.Count()
}).Join(_context.TableA,
groupByQuery => groupByQuery.TableAId,
tableA => tableA.Id,
(groupByQuery , tableA) => new {
TableAId = groupByQuery.TableAId,
Count = groupByQuery.Count,
TableAItem = tableA
} );
Let's say TableA has properties - Id, FieldA1 and FieldA2.
First, you have to include TableB in your query so that you can take its count, like -
var data = _context.TableA.Include(p=> p.TableB).AsQueryable();
Then in the Select method you have to create a new object with TableA's properties and TableB's count, like -
var list = data.Select(p =>
new
{
Id = p.Id,
A1 = p.FieldA1,
A2 = p.FieldA2,
Count = p.OrderLines.Count
}).ToList();
Notice, this is assigned to a new variable list. That is because it does not return a list of TableA, it returns a list of an anonymous object with properties - Id, A1, A2 and Count. Therefore, you cannot assign it to the previously declared data variable, because data is of type IQueryable<TableA>.
Alternatively, you can declare a class to hold the data values, like -
public class MyData
{
public int Id { get; set; }
public string A1 { get; set; }
public string A2 { get; set; }
public int Count { get; set; }
}
and use it like -
var list = data.Select(p =>
new MyData
{
Id = p.Id,
A1 = p.FieldA1,
A2 = p.FieldA2,
Count = p.OrderLines.Count
}).ToList();
If you want to start from TableA, you can use the following linq query:
var data = _context.TableAs.AsQueryable();
var x = (from a in data
join b in _context.TableBs on a.Id equals b.TableAId
group a.TableB by a.Id into g
select new
{
TableAId = g.Key,
TableBItem = g.FirstOrDefault(),
Count = g.Count()
}).ToList();
Result:

Dynamic list using array from anthor list

My application is ASP.NET MVC 5 / SQL Server.
I am trying to select specific columns from a list based on an array:
First list has 200 columns: Age, Gender, .....
var list1 = _reportRepository.ShowMasteView().ToList();
Second list has 20 columns: Age, Gender, ......
From the view I select the items to be displayed:
string[] lits2 = showColumn.Where(c => c.Value == true).Select(c=> c.Key).ToArray();
I get
To get these two specific columns, I tried
var nList = list1.Select(t2 => lits2.Any(t1 => t2.Contains(t1)));
I get an error
Can not resolve symbol "Contains"
I was able to do it using the following
var keys = "Age,Gender";
var connection =
ConfigurationManager.ConnectionStrings["DALEntities"].ConnectionString;
using (var dataAdapter = new SqlDataAdapter("SELECT " + keys
+ " from dbo.vw_MasterView", connection))
{
var dataTable = new DataTable();
dataAdapter.Fill(dataTable);
dataAdapter.FillSchema(dataTable, SchemaType.Mapped);
return dataTable;
}
Is there a better way in linq?
From my understand it appears you are trying to extract/select a dynamic object that only has the desired properties/columns.
This can be achieved by building a dynamic expression/function to apply to the Select
The following builds an expression based on the model type and the provided properties
static class DynamicExtensions {
public static IQueryable<dynamic> SelectDynamic<TModel>(this IQueryable<TModel> query, ISet<string> propertyNames) {
var selector = query.BuildSelectorFor(propertyNames);
return query.Select(selector);
}
static Expression<Func<TModel, dynamic>> BuildSelectorFor<TModel>(this IQueryable<TModel> query, ISet<string> propertyNames) {
var modelType = typeof(TModel);
var properties = modelType.GetProperties().Where(p => propertyNames.Contains(p.Name));
// Manually build the expression tree for
// the lambda expression v => new { PropertyName = v.PropertyName, ... }
// (TModel v) =>
var parameter = Expression.Parameter(modelType, "v");
// v.PropertyName
var members = properties.Select(p => Expression.PropertyOrField(parameter, p.Name));
var addMethod = typeof(IDictionary<string, object>).GetMethod(
"Add", new Type[] { typeof(string), typeof(object) });
// { { "PropertyName", v.PropertyName}, ... }
var elementInits = members.Select(m =>
Expression.ElementInit(addMethod, Expression.Constant(m.Member.Name), Expression.Convert(m, typeof(object))));
// new ExpandoObject()
var newExpando = Expression.New(typeof(ExpandoObject));
// new ExpandoObject() { { "PropertyName", v.PropertyName}, ... }
var expando = Expression.ListInit(newExpando, elementInits);
// (TModel v) => new ExpandoObject() { { "PropertyName", v.PropertyName}, ... }
var lambdaExpression = Expression.Lambda<Func<TModel, dynamic>>(expando, parameter);
return lambdaExpression;
}
}
This takes advantage of ExpandoObject whose members can be dynamically added and removed at run time.
The following test was used as an example of how the above function is invoked.
[TestMethod]
public void DynamicList() {
var list1 = new List<Person>
{
new Person{ Gender = "Male", Age = 10, FirstName = "Nama1", SampleNumber = 12},
new Person{ Gender = "Male", Age = 12, FirstName = "Nama2", SampleNumber = 13},
new Person{ Gender = "Female", Age = 13, FirstName = "Nama3", SampleNumber = 14},
new Person{ Gender = "Male", Age = 14, FirstName = "Nama4", SampleNumber = 15},
};
var keys = new string[] { "Age", "Gender", };
var nList = list1.AsQueryable().SelectDynamic(new HashSet<string>(keys));
foreach (IDictionary<string, object> row in nList) {
var msg = $"{{ {keys[0]} = {row[keys[0]]}, {keys[1]} = {row[keys[1]]} }}";
Debug.WriteLine(msg);
}
}
and produces the following output
{ Age = 10, Gender = Male }
{ Age = 12, Gender = Male }
{ Age = 13, Gender = Female }
{ Age = 14, Gender = Male }
The dynamic objects can be used in the View and it is a simple matter of calling the desired members.
For example suppose you have a model as follows
public class MyViewModel {
public string MyProperty { get; set; }
public string[] Keys { get; set; }
public List<dynamic> MyDynamicProperty { get; set; }
}
that was populated with data and given to the view
var list1 = _reportRepository.ShowMasteView();
var keys = new string[] { "Age", "Gender", };
var nList = list1.AsQueryable().SelectDynamic(new HashSet<string>(keys));
var viewModel = new MyViewModel {
MyProperty = "Hello World",
MyDynamicProperty = nList.ToList(),
Keys = keys
};
return View(viewModel);
Then in the view you can use the model as desired, casting to get access to members in the expando object.
#model MyViewModel
...
<h2>#Model.MyProperty</h2>
<table>
<tr>
#foreach(string key in Model.Keys) {
<th>#key</th>
}
</tr>
#foreach (IDictionary<string, object> row in Model.MyDynamicProperty) {
<tr>
#foreach(string key in Model.Keys) {
<td>#row[#key]</td>
}
</tr>
}
</table>
I think you just need to use Contains on your list2.
var nList = list1.Where(t => lits2.Contains(t1));
Contains is a method for Lists. The code you had was trying to use it on a string.
If you have two list of a person's class
public class Person
{
public int id { get; set; }
public string name { get; set; }
}
If the lists are as below:
var list1 = new List<Person>
{
new Person{ id = 1, name = "Nama1"},
new Person{ id = 2, name = "Nama2"},
new Person{ id = 3, name = "Nama3"},
new Person{ id = 4, name = "Nama4"},
};
var list2 = new List<Person>
{
new Person{ id = 1, name = "Nama1"},
new Person{ id = 2, name = "Nama2"},
};
You can filter in the following ways
var keys = list2.Select(x => x.id).ToList();
var filter1= list1.Where(x => keys.Contains(x.id)).ToList();
var filter2= list1.Where(x => keys.Contains(x.id)).Select(x => new { x.name }).ToList();
var filter3= list1.Select(x => new
{
id = x.id,
name = x.name,
check = keys.Contains(x.id)
}).Where(x => x.check).ToList();
If you have array of string
you can use below code
array string same
var lis1 = new string[] {"name1", "name2","name3" };
var lis2 = new string[] { "name1" };
You can filter array of string in the following ways
var items1= lis1.Where(x=>lis2.Contains(x)).ToList();
var items= lis1.Select(x=> new { x, check= lis2.Contains(x) }).Where(x=>x.check == true).ToList();

Return unique values only in Webapi controller

I have the following line in a WebApi controller;
string Codes = i.Products.FirstOrDefault().Code
As the line states, it gets the code, from the first Product.
But, what I really want it to do is to get all unique codes, and return them as a comma separated string.
So, let's say, there are 6 related products, and they have the following codes:
45
54
45
120
54
45
Right now, the statement just returns "45", given the above data.
But I want the statement above to return "45, 54, 120" (as a string).
How do I do this?
Complete code:
public System.Data.Entity.DbSet<WebAPI.Models.Product> Products { get; set; }
private ApplicationDbContext db = new ApplicationDbContext();
var product = await db.Products.Select(i =>
new ProductDTO()
{
Id = i.Id,
Created = i.Created,
Title = i.Title,
Codes = i.Products.FirstOrDefault().Code
}).SingleOrDefaultAsync(i => i.Id == id);
To convert to a comma separated list:
IEnumerable<string> distinctCodes = i.Products.Select(product => product.Code).Distinct();
return string.Join(",", distinctCodes);
But it's probably better if your controller returns a collection of string instead of a concatenated string.
Edit, after OP code update:
var DBProduct = await db.Products.SingleOrDefaultAsync(i => i.Id == id);
IEnumerable<string> productCodes = DBProduct.Products.Select(p => p.Code).Distinct();
var product = new ProductDTO()
{
Id = DBProduct.Id,
Created = DBProduct.Created,
Title = DBProduct.Title,
Codes = string.Join(",", productCodes)
};
I think Distinctand String.Join works for you.Please try this:
var product = await db.Products.AsEnumerable()//Turn AsEnumarable
.Select(i =>
new ProductDTO()
{
Id = i.Id,
Created = i.Created,
Title = i.Title,
Codes = string.Join(",",
i.Products.Select(l => l.Code).Distinct())
}).SingleOrDefaultAsync(i => i.Id == id);