EF: How to pass column name dynamically to where clause - c#

i am not before dev pc. i just started working with EF. so curious to know can we pass column name dynamically for where clause.
see a screen shot for searching grid.
i just compose a sample query. please tell me does it work?
public ActionResult Index(String ColumnName,String SearchText)
{
private CustomersEntities db = new CustomersEntities();
var customer = (from s in db.Customers
select new CustomerDTO
{
CustomerID = s.CustomerID,
CompanyName = s.CompanyName,
ContactName = s.ContactName,
ContactTitle = s.ContactTitle,
Address = s.Address
})
.Where(s => s.Field<string>(ColumnName).ToUpper().Contains(SearchText.ToUpper());
return View(customer);
}
thanks

public ActionResult Index(string ColumnName, string SearchText)
{
var arg = Expression.Parameter(typeof(Customer), "x");
var strType = typeof(string);
var ToUpperMeth = strType.GetMethods().Where(x => x.Name == nameof(string.ToUpper)
&& x.GetParameters().Count() == 0).Single();
var ContainsMeth = strType.GetMethods().Where(x => x.Name == nameof(string.Contains)
&& x.GetParameters().Count() == 1).Single();
var exprVal = Expression.Constant(SearchText);
var toUpExprVal = Expression.Call(exprVal, ToUpperMeth);
var exprProp = Expression.Property(arg, ColumnName);
var toUpExpr = Expression.Call(exprProp, ToUpperMeth);
var contExpr = Expression.Call(toUpExpr, ContainsMeth, toUpExprVal);
var predicate = Expression.Lambda<Func<Customer, bool>>(contExpr, arg);
var customer = (from s in db.Customers
select new CustomerDTO
{
CustomerID = s.CustomerID,
CompanyName = s.CompanyName,
ContactName = s.ContactName,
ContactTitle = s.ContactTitle,
Address = s.Address
}).Where(predicate).ToList();
return View(customer);
}

You can create something like this in repository (if you use it)
public IQueryable<T> FindBy(Expression<Func<T, bool>> predicate)
{
return _context.Set<CustomersEntities>().Where(predicate);
}
and then
var result = _repository.FindBy(y => y.CompanyName.IndexOf(SearchText, StringComparison.OrdinalIgnoreCase) >= 0);

The basic pattern is to build up the query at runtime, selectively adding Where expressions before running the query and projecting the results into the DTO.
Like this:
public IList<CustomerDTO> FindCustomers(String ColumnName, String SearchText)
{
var query = from s in db.Customers select s;
if (ColumnName == "CompanyName")
{
query = query.Where(c => c.CompanyName == SearchText);
}
else if (ColumnName == "ContactName")
{
query = query.Where(c => c.ContactName == SearchText);
}
//. . .
else
{
throw new InvalidOperationException($"Column {ColumnName} not found or not supported for searching.");
}
var results = from c in query
select new CustomerDTO()
{
CustomerID = c.CustomerID,
CompanyName = c.CompanyName,
ContactName = c.ContactName,
ContactTitle = c.ContactTitle,
Address = c.Address
};
return results;
}

Related

How do I fill a child list inside a parent list using linq2db in a single query in my .net core app?

I am trying to query my database to return turn reports with any attachments included. I need a list of turn report items which are returned by date, and then for each report I want it to also return all of the attachments associated with the turn reports. The only way to associate them is by the EntryId.
Here is my method to get the turn reports:
public List<TurnReportItem> GetTurnReportsByDateShiftAndDept(DateTime shiftStart, int shiftNum, int dept)
{
try
{
List<TurnReportItem> list;
using (connection)
{
list = (from r in connection.VTurnReports
join a in connection.TurnReportAreas on r.AreaId equals a.AreaId
where a.DeptId == dept && a.Enabled && r.ShiftDate == shiftStart && r.ShiftNum == shiftNum
select new TurnReportItem
{
areaId = r.AreaId,
areaName = a.Name,
author = r.Author,
comment = r.Comment,
datetime = r.Datetime,
id = r.EntryId,
ip = r.Ip,
shiftDate = r.ShiftDate,
shiftNum = r.ShiftNum,
sort_order = a.SortOrder,
attachment_count = r.AttachmentCount,
attachments = (
from at in connection.TurnReportAttachments where at.EntryId == r.EntryId
select new TurnReportAttachment
{
AttachmentId = at.AttachmentId,
FileName = at.FileName
}).ToList()
})
.OrderBy(r => r.sort_order)
.OrderBy(r => r.datetime)
.ToList();
return list;
}
}
Here is the TurnReportItem class that I am filling. If I do not have the subquery I do get all of the turnreports.
public class TurnReportItem
{
public int id;
public string comment;
public DateTime datetime;
public string author;
public int areaId;
public string areaName;
public DateTime shiftDate;
public int shiftNum;
public string ip;
public int? attachment_count;
public int sort_order;
public int area_rating;
public List<TurnReportAttachment> attachments;
public TurnReportItem() { }
}
I have a separate method that will return the all of the comments with the entry id. I have tried to fill the list using that method. I am converting this from a MVC app and I was able to use the method to fill the list however it will not work when I try it in this app, I also would prefer to only make one connection in the database to get what I need.
List<TurnReportItem> list;
using (connection)
{
list = (from r in connection.VTurnReports
join a in connection.TurnReportAreas on r.AreaId equals a.AreaId
where a.DeptId == dept && a.Enabled && r.ShiftDate == shiftStart && r.ShiftNum == shiftNum
select new TurnReportItem
{
areaId = r.AreaId,
areaName = a.Name,
author = r.Author,
comment = r.Comment,
datetime = r.Datetime,
id = r.EntryId,
ip = r.Ip,
shiftDate = r.ShiftDate,
shiftNum = r.ShiftNum,
sort_order = a.SortOrder,
attachment_count = r.AttachmentCount,
attachments = SelectAttachmentsByEntryId(r.EntryId)
})
.OrderBy(r => r.sort_order)
.OrderBy(r => r.datetime)
.ToList();
return list;
}
public List<TurnReportAttachment> SelectAttachmentsByEntryId(int EntryId)
{
using (connection)
{
// we do it this way so that we don't return the blob
var results = from p in connection.TurnReportAttachments
where p.EntryId == EntryId
select new TurnReportAttachment
{
EntryId = p.EntryId,
AttachmentId = p.AttachmentId,
FileName = p.FileName
};
return results.ToList();
}
}
In your case SelectAttachmentsByEntryId should be static, with additional parameter connection. To make it work, it is needed to use ExpressionMethod.
public static class ReportHelpers
{
[ExpressionMethod(nameof(SelectAttachmentsByEntryIdImpl))]
public static List<TurnReportAttachment> SelectAttachmentsByEntryId(MyConnection connection, int EntryId)
{
throw new InvalidOperationException(); // should never enter here
}
private static Expression<Func<MyConnection, int, List<TurnReportAttachment>>> SelectAttachmentsByEntryIdImpl()
{
return (connection, EntryId) =>
(from p in connection.TurnReportAttachments
where p.EntryId == EntryId
select new TurnReportAttachment
{
EntryId = p.EntryId,
AttachmentId = p.AttachmentId,
FileName = p.FileName
})
.ToList();
}
}
Then you can use this method in queries:
public List<TurnReportItem> GetTurnReportsByDateShiftAndDept(DateTime shiftStart, int shiftNum, int dept)
{
using (connection)
{
var list = (from r in connection.VTurnReports
join a in connection.TurnReportAreas on r.AreaId equals a.AreaId
where a.DeptId == dept && a.Enabled && r.ShiftDate == shiftStart && r.ShiftNum == shiftNum
select new TurnReportItem
{
areaId = r.AreaId,
areaName = a.Name,
author = r.Author,
comment = r.Comment,
datetime = r.Datetime,
id = r.EntryId,
ip = r.Ip,
shiftDate = r.ShiftDate,
shiftNum = r.ShiftNum,
sort_order = a.SortOrder,
attachment_count = r.AttachmentCount,
attachments = ReportHelpers.SelectAttachmentsByEntryId(connection, r.EntryId)
})
.OrderBy(r => r.sort_order)
.ThenBy(r => r.datetime)
.ToList();
return list;
}
}
Note that OrderBy.OrderBy has no sense. It should be OrderBy.ThenBy

How do I convert SQL Query to Lambda?

I have the following SQL query that returns the results I need:
SELECT
STAFF_ID
FROM [dbo].[StaffTable]
WHERE STAFF_ID NOT IN (SELECT STAFF_ID
FROM [dbo].[StaffingTable]
WHERE [DATE] = #DATE
AND MODEL_ID = #Model)
I have the following controller method to try and return the correct results:
public JsonResult GetStaffResults(DateTime date, string modelId)
{
Guid modelGuid = Guid.Parse(modelId);
var settings = new JsonSerializerSettings();
var staff = context.StaffTable.Select(c => new
{
Id = c.StaffId,
Name = c.StaffName
});
var staffing = context.StaffingTable.Select(c => new
{
modelId = c.ModelId,
manufacturerId = c.ManufacturerId,
staffId = c.StaffId,
date = c.Date,
recordId = c.RecordId
});
var staffResults = staff.Where(p => staffing.Select(o => o.modelId).First() == modelGuid && !staffing.Select(o => o.date).Contains(date));
return Json(shiftResults, settings);
}
However, I'm struggling with the Lambda expression, it returns no results so I've missed something somewhere.
You can try something like this:
public JsonResult GetStaffResults(DateTime date, string modelId)
{
Guid modelGuid = Guid.Parse(modelId);
var settings = new JsonSerializerSettings();
var staffQuery = context.StaffTable
.Where(s => !context
.StaffingTable
.Any(st => st.StaffId = s.StaffId && st.modelId == modelGuid && st.date == date))
.Select(c => new
{
Id = c.StaffId,
Name = c.StaffName
});
return Json(staffQuery.ToList(), settings);
}
You are doing separate query with Select which has a performance issue as the queries will return IEnumerable<T> objects. If you want to have separate queries then try to get the query results as IQueryable<T> which will construct the queries and will load data at the end.
For example:
var query =
from st in context.StaffTable
where !(from stff in context.StaffingTable
select stff.CustomerID)
where stff.MODEL_ID = ModelIdVariable AND stff.DATE = DATEVariable
.Contains(st.CustomerID)
select st;
Declare and populate the ModelIdVariable and DATEVariable variables before the query.
Then you can do query.ToList<T>() where you need the data to be loaded.

Unable to Convert Linq to Lambda Notation

public ActionResult EditArticle(int id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var typeId = 0;
var catId = 0;
var subCatId = 0;
var viewModel = (from sa in ems.SupportArticles
join ssc in ems.SupportSubCategories on sa.SubCatID equals ssc.SubCatID
join sc in ems.SupportCategories on ssc.CatID equals sc.CatID
join st in ems.SupportTypes on sc.TypeID equals st.TypeID
where sa.ArcticleId == id
select new SupportArticleViewModel { supportArticle = sa, supportSubCat = ssc, supportCat = sc, supportType = st });
foreach (var vm in viewModel)
{
typeId = vm.supportType.TypeID;
catId = vm.supportCat.CatID;
subCatId = vm.supportSubCat.SubCatID;
}
I want to convert it into Lambda Notation.But, I am unable to do it.Please help.I am using SupportViewModel which contains property of SupportType,SupportCategory ,SupportSubCategoryand SupportArticle.
Following is functional way do query , you have to make use of join function and than you get data
var filteredArtciles = SupportArticles.Where(sa=> sa.ArcticleId == id);
var query =
SupportArticles.
Join(SupportSubCategories,sa => sa.SubCatID ,ssc => ssc.SubCatID,(sa, ssc) => new {sa,ssc}).
Join(SupportCategories,sassc => sassc.ssc.CatID ,sc=>sc.CatID ,(sassc, sc) => new {sassc,sc}).;
Join(SupportTypes,sasscsc => sasscsc.sc.TypeID ,st=>st.TypeID ,(sc, st) => new {sasscsc,st}).
Select(j=>
new SupportArticleViewModel
{
supportArticle = j.sasscsc.sassc.sa,
supportSubCat = j.sasscsc.sassc.ssc,
supportCat = j.sasscsc.sc,
supportType = j.st
}
));

Linq where clause using filter object

I have a piece of Linq that queries an EntityFramework context in my web controller and returns the result, as follows:
[HttpGet]
public IActionResult GetRoutingRules()
{
var query = (from rr in _context.RoutingRules
join dest in _context.RoutingZones on rr.DestinationZoneId equals dest.ZoneId
join origin in _context.RoutingZones on rr.OriginZoneId equals origin.ZoneId
join hub in _context.RoutingHub on rr.HubId equals hub.HubId
select new RoutingRulesDto(rr) { DestinationZoneName = dest.ZoneName, OriginZoneName = origin.ZoneName, HubName = hub.HubName });
return Ok(query);
}
I want a new method that will take a "filter" object, where I can narrow down the results of the above. My filter object looks like this:
public class RoutingSearchFilterDto
{
public int BrandId { get; set; }
public int? ServiceType { get; set; }
public long? OriginZoneId { get; set; }
public long? DestinationZoneId { get; set; }
public int? RuleRanking { get; set; }
public bool? IsRuleActive { get; set; }
}
The minimum info that needs to be set in this class is BrandId. All other properties are options in the filter.
I need to write a new controller method that will utilise this, something like:
[HttpPost("filtered")]
public IActionResult GetFilteredRoutingRules([FromBody] RoutingSearchFilterDto filter)
{
...
}
How do I linq query on properties that could potentially be null? Essentially, a dynamic query depending on the properties set in the filter object.
NOTE: I want this to affect the select statement that the EF runs, not just let EF get all the data, then filter the data set - the point of this is to make the db call more efficient.
Filter object might be sent where BrandId = 1, IsRuleActive = 1. Equally, it could be BrandId = 1, ServiceType = 3 (and therefore IsRuleActive is null so shouldn't be in the linq where clause).
I've tried this:
var param = (Expression.Parameter(typeof(RoutingRules), "rr"));
Expression combinedExpr = null;
if (filter.BrandId != null)
{
var exp = Expression.Equal(Expression.Property(param, "BrandId"), Expression.Constant(filter.BrandId));
combinedExpr = exp;
}
if (filter.DestinationZoneId != null)
{
var exp = Expression.Equal(Expression.Property(param, "DestinationZoneId"), Expression.Constant(filter.DestinationZoneId));
combinedExpr = (combinedExpr == null ? exp : Expression.AndAlso(combinedExpr, exp));
}
if (filter.OriginZoneId != null)
{
var exp = Expression.Equal(Expression.Property(param, "OriginZoneId"), Expression.Constant(filter.OriginZoneId));
combinedExpr = (combinedExpr == null ? exp : Expression.AndAlso(combinedExpr, exp));
}
if (filter.EshopServiceType != null)
{
var exp = Expression.Equal(Expression.Property(param, "EshopServiceType"), Expression.Constant(filter.EshopServiceType));
combinedExpr = (combinedExpr == null ? exp : Expression.AndAlso(combinedExpr, exp));
}
if (filter.IsRuleActive != null)
{
var exp = Expression.Equal(Expression.Property(param, "IsRuleActive"), Expression.Constant(filter.IsRuleActive, typeof(bool?)));
combinedExpr = (combinedExpr == null ? exp : Expression.AndAlso(combinedExpr, exp));
}
if (filter.RuleRanking != null)
{
var exp = Expression.Equal(Expression.Property(param, "RuleRanking"), Expression.Constant(filter.RuleRanking));
combinedExpr = (combinedExpr == null ? exp : Expression.AndAlso(combinedExpr, exp));
}
if (combinedExpr == null)
combinedExpr = Expression.Default(typeof(bool));
var compiled = Expression.Lambda<Func<RoutingRules, bool>>(combinedExpr, param).Compile();
var results = (from rr in _context.RoutingRules.Where(compiled)
join dest in _context.RoutingZones on rr.DestinationZoneId equals dest.ZoneId
join origin in _context.RoutingZones on rr.OriginZoneId equals origin.ZoneId
join hub in _context.RoutingHub on rr.HubId equals hub.HubId
where rr.BrandId == 21
select new RoutingRulesDto(rr) { DestinationZoneName = dest.ZoneName, OriginZoneName = origin.ZoneName, HubName = hub.HubName });
But the Where clause isn't applied to the generated Sql, it seems to pull back all records, then apply the where in memory, which isn't what I need.
Thanks in advance for any pointers!!
You can build an expression tree for this, but have you considered:
IQueryable<...> query = ...;
if (routingSearchFilter.ServiceType != null)
query = query.Where(e => e.ServiceType == routingSearchFilter.ServiceType);
if (...)
query = query.Where(....);
The EF engine is smart enough to combine the Where clauses (with AND of course).
Edit:
It wasn't clear if you wanted to filter on the joined result or only on the first table. In that case it would continue like
var result = (from rr in query
join dest in _context.RoutingZones on rr.DestinationZoneId equals dest.ZoneId
join ...
select new RoutingRulesDto(rr) .... ).ToSometing();
But I'm a little wary about that RoutingRulesDto(rr) constructor parameter.
If you use the fluent API for LINQ, you can conditionally add Where clauses.
var query = _content.RoutingRules.Where(r => r.BrandId == filter.BrandId);
if (filter.OriginZoneId != null) {
query = query.Where(r => r.OriginZoneId == filter.OriginZoneId);
}
if (filter.EshopServiceType != null) {
query = query.Where(r => r.EshopServiceType == filter.EshopServiceType);
}
// etc...
var result = query.ToArray();
Just to have my final solution in black and white, here's what I had in the end:
[HttpPost("filtered")]
public IActionResult GetFilteredRoutingRules([FromBody] RoutingSearchFilterDto filter)
{
// Query to be build on the routing rules table.
IQueryable<RoutingRules> query = _context.RoutingRules;
// Populate the linked foreign key entities.
query.Include(x => x.Hub).Include(y => y.DestinationZone).Include(z => z.OriginZone);
// Build dynamic where statements.
if (filter.BrandId != null)
query = query.Where(r => r.BrandId == filter.BrandId);
if (filter.OriginZoneId != null)
query = query.Where(r => r.OriginZoneId == filter.OriginZoneId);
if (filter.DestinationZoneId != null)
query = query.Where(r => r.DestinationZoneId == filter.DestinationZoneId);
if (filter.IsRuleActive != null)
query = query.Where(r => r.IsRuleActive == filter.IsRuleActive);
if (filter.RuleRanking != null)
query = query.Where(r => r.RuleRanking == filter.RuleRanking);
// If you want to add paging:
query = query.Skip(filter.PageSize * filter.PageNumber).Take(filter.PageSize);
// Perform select on the table and map the results.
var result = query.Select(r => new RoutingRulesDto
{
RoutingRuleId = r.RoutingRuleId,
BrandId = r.BrandId,
LastMileCarrierCode = r.LastMileCarrierCode,
CashOnDelivery = r.CashOnDelivery,
CreationTime = r.CreationTime,
CurrencyCode = r.CurrencyCode,
CurrencyDescription = Enum.Parse(typeof(Enumerations.CurrencyCode), r.CurrencyCode),
DestinationZoneId = r.DestinationZoneId,
EddFromDay = r.EddFromDay,
EddToDay = r.EddToDay,
ServiceType = r.ServiceType,
ServiceTypeName = Enum.Parse(typeof(Enumerations.ServiceType), r.EshopServiceType),
IsPickUpAvailable = r.IsPickUpAvailable,
LastUpdateTime = r.LastUpdateTime,
LastUpdateUser = r.LastUpdateUser,
OriginZoneId = r.OriginZoneId,
RuleRanking = r.RuleRanking,
SignOnDelivery = r.SignOnDelivery,
TermsOfDelivery = r.TermsOfDelivery,
TermsOfDeliveryName = Enum.Parse(typeof(Enumerations.TermsOfDelivery), r.TermsOfDelivery),
ValueOfGoods = r.ValueOfGoods,
WeightLowerLimit = r.WeightLowerLimit,
WeightUpperLimit = r.WeightUpperLimit,
FirstMileCarrierCode = r.FirstMileCarrierCode,
HubId = r.HubId,
IsInsuranceAvailable = r.IsInsuranceAvailable,
IsRuleActive = r.IsRuleActive,
HubName = r.Hub.HubName,
DestinationZoneName = r.DestinationZone.ZoneName,
OriginZoneName = r.OriginZone.ZoneName,
});
// The SQL produced includes the joins and where clauses as well as only
// selecting the column names that are required in the flattened return object.
return Ok(result);
}
Thanks for the help guys!

how we can return a value of select to each property of entities?

I'm new to entity faramwork. I'm trying to find best code for returning my select value to my entity property
here is my code:
private void CustomerForm_Load(object sender, EventArgs e)
{
if (CustomerID != null && CustomerMode !=(int)CustomerModeOperaton.insert)
{
using (var Context = new FactorEntities())
{
var Customercods = from _customers in Context.tblCustomers.Where(c => c.CustomerID == CustomerID) select _customers.CustomerCode;
_tblCustomer.CustomerCode = Customercods.First();
var Customernames = from _customers in Context.tblCustomers.Where(c => c.CustomerID == CustomerID) select _customers.CustomerName;
_tblCustomer.CustomerName = Customernames.First();
var Customerlastname = from _customers in Context.tblCustomers.Where(c => c.CustomerID == CustomerID) select _customers.CustomerLastName;
_tblCustomer.CustomerLastName = Customerlastname.First();
var customerID = from _customers in Context.tblCustomers.Where(c => c.CustomerID == CustomerID) select _customers.CustomerID;
_tblCustomer.CustomerID = customerID.First();
var customerAddresses = from _customers in Context.tblCustomers.Where(c => c.CustomerID == CustomerID) select _customers.CustomerAdresse;
_tblCustomer.CustomerAdresse = customerAddresses.First();
var customerMobile = from _customers in Context.tblCustomers.Where(c => c.CustomerID == CustomerID) select _customers.CustomerCellPhone;
_tblCustomer.CustomerCellPhone = customerMobile.First();
var customerphone = from _customers in Context.tblCustomers.Where(c => c.CustomerID == CustomerID) select _customers.CustomerPhone;
_tblCustomer.CustomerPhone = customerphone.First();
}
}
LoadDataToControl();
}
but I was wondering if I use this code, I have to use several select which is not good and what should I do if my select value be null?
for example this code:
_tblCustomer.CustomerPhone = customerphone.First();
You only have to query for the customer once, and let it return all the customer's information in one object. Then you can just reference the properties of that object for your assignments to the _tblCustomer fields.
If you use FirstOrDefault, then you can just check that the returned object is not null before trying to use it (in the case where there is no customer whose CustomerID matches the one your searching for):
var customer = Context.tblCustomers.FirstOrDefault(c => c.CustomerID == CustomerID);
if (customer != null)
{
_tblCustomer.CustomerCode = customer.CustomerCode;
_tblCustomer.CustomerName = customer.CustomerName;
_tblCustomer.CustomerLastName = customer.CustomerLastName;
_tblCustomer.CustomerID = customer.CustomerID;
_tblCustomer.CustomerAdresse = customer.CustomerAdresse;
_tblCustomer.CustomerCellPhone = customer.CustomerCellPhone;
_tblCustomer.CustomerPhone = customer.CustomerPhone;
}

Categories

Resources