Add dynamic property to Linq result set objects - linq

I have object lists:
List<dynamic> prods = new List<dynamic>();
foreach (DataRow row in dataTable.Rows)
{
dynamic obj = new ExpandoObject();
IDictionary<string, object> oprops = obj;
foreach (System.Data.DataColumn col in dataTable.Columns)
oprops.Add(col.ColumnName, row[col.ColumnName]);
obj = oprops;
list.Add(obj);
}
And pricing list is filled the same way from the DataTable having product pricing.
I have this query:
var result = (from product in prods join price in pricing on product.ID equals price.ProductID select new { product, price }).ToList();
And I want to add some more properties to the result set.
foreach (dynamic obj in q)
{
obj.Test = "yes, testing."; // This line gives runtime error
Console.WriteLine(
"{0} - {1}",
obj.product.Name,
obj.price.UnitPrice);
}
I have some more objects/values to add to the each individual result object in foreach loop.
I am just unable to do that as it throws runtime error as follows:
<>f__AnonymousType1<dynamic,dynamic>' does not contain a definition for 'Test' and no extension method 'Test' accepting a first argument of type '<>f__AnonymousType1<dynamic,dynamic>' could be found (are you missing a using directive or an assembly reference?)

Add the property while querying the data so it is there to set in the foreach loop; eg:
var result = (from product in prods
join price in pricing on product.ID equals price.ProductID
select new { product, price, Test = (string)null, }
).ToList();
or, something I've never tried, maybe use an ExpandoObject:
var result = (from product in prods
join price in pricing on product.ID equals price.ProductID
select new ExpandoObject { product, price }
).ToList();

So, this is what I have found, we cant expand the linq query result set however creating a new exapndo objects solves the problem which enables adding new properties.
This is actually what I did:
List<dynamic> list = new List<dynamic>();
foreach (var o in q)
{
dynamic obj = new ExpandoObject();
IDictionary<string, object> oprops = obj;
oprops.Add("Test", "yes, testing!");
oprops.Add("product", o.product);
oprops.Add("price", o.price);
obj = oprops;
list.Add(obj);
}

Related

EF Core LINQ use scalar function

I use Entity Framework Core 2.1.
I have a scalar function in the database which adds specified number of days.
I created an extension method to execute it:
public static class AdventureWorks2012ContextExt
{
public static DateTime? ExecFn_AddDayPeriod(this AdventureWorks2012Context db, DateTime dateTime, int days, string periodName)
{
var sql = $"set #result = dbo.[fn_AddDayPeriod]('{dateTime.ToString("yyyy-MM-dd HH:mm:ss.fff")}', {days}, '{periodName}')";
var output = new SqlParameter { ParameterName = #"result", DbType = DbType.DateTime, Size = 16, Direction = ParameterDirection.Output };
var result = db.Database.ExecuteSqlCommand(sql, output);
return output.Value as DateTime?;
}
}
I try to use a scalar function in the query (to simplify things I use AdventureWorks2012) as follows:
var persons =
(from p in db.Person
join pa in db.Address on p.BusinessEntityId equals pa.AddressId
where p.ModifiedDate > db.ExecFn_AddDayPeriod(pa.ModifiedDate, 100, "DayPeriod_day")
select p).ToList();
But get an System.InvalidOperationException: 'A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.'
How can I achieve this?
UPDATE:
I managed to do it with the help of Ivan's answer:
var persons =
(from p in db.Person
join bea in db.BusinessEntityAddress on p.BusinessEntityId equals bea.BusinessEntityId
join a in db.Address on bea.AddressId equals a.AddressId
where p.ModifiedDate > AdventureWorks2012ContextFunctions.AddDayPeriod(a.ModifiedDate, 100, "DayPeriod_day")
select p).ToList();
But now I need to update ModifiedDate for filtered persons. So I'm doing like this:
var persons =
(from p in db.Person
join bea in db.BusinessEntityAddress on p.BusinessEntityId equals bea.BusinessEntityId
join a in db.Address on bea.AddressId equals a.AddressId
let date = AdventureWorks2012ContextFunctions.AddDayPeriod(a.ModifiedDate, 100, "DayPeriod_day")
where p.ModifiedDate > date
select new { Person = p, NewDate = date }).ToList();
foreach (var p in persons)
p.Person.ModifiedDate = p.NewDate ?? DateTime.Now;
db.SaveChanges();
But got System.NotSupportedException: 'Specified method is not supported.'
How can I use scalar function in select statement?
I tried to split the query by two parts:
var filteredPersons = // ok
(from p in db.Person
join bea in db.BusinessEntityAddress on p.BusinessEntityId equals bea.BusinessEntityId
join a in db.Address on bea.AddressId equals a.AddressId
where p.ModifiedDate > AdventureWorks2012ContextFunctions.AddDayPeriod(a.ModifiedDate, 100, "DayPeriod_day")
select new { Person = p, a.ModifiedDate }).ToList();
var persons = // here an exception occurs
(from p in filteredPersons
select new { Person = p, NewDate = AdventureWorks2012ContextFunctions.AddDayPeriod(p.ModifiedDate, 100, "DayPeriod_day") }).ToList();
Instead of invoking the function client side (which is this particular case happens as part of the client evaluation of the query filter, while the query reading is still in progress), you can use EF Core Database scalar function mapping so it
can be used in LINQ queries and translated to SQL.
One way to do that is to create a public static method in the derived context class and mark it with DbFunction attribute:
public partial class AdventureWorks2012Context
{
[DbFunction("fn_AddDayPeriod")]
public static DateTime? AddDayPeriod(DateTime dateTime, int days, string periodName) => throw new NotSupportedException();
}
and use
where p.ModifiedDate > AdventureWorks2012Context.AddDayPeriod(pa.ModifiedDate, 100, "DayPeriod_day")
Another way is to create a public static method in another class
public static class AdventureWorks2012DbFunctions
{
[DbFunction("fn_AddDayPeriod")]
public static DateTime? AddDayPeriod(DateTime dateTime, int days, string periodName) => throw new NotSupportedException();
}
but then you'll need to register it with fluent API (which happens automatically for methods defined inside the context derived class):
modelBuilder
.HasDbFunction(() => AdventureWorks2012DbFunctions.AddDayPeriod(default(DateTime), default(int), default(string)));
The usage is the same:
where p.ModifiedDate > AdventureWorksDbFunctions.AddDayPeriod(pa.ModifiedDate, 100, "DayPeriod_day")

Linq CopyToDataTable using extension methods

Hello I'm trying to copy the following linq results to a datatable. The only examples I see of copytodatatable is using the query format, and not the extension methods. Wondering if anyone knows how to use it with the extension methods (I've tried casting the results to IEnumerable datarow but it didn't work).
DataTable dtItemPricingBreakDown =
SiteHelper.getItemPricingBreakDown(CustomerId,
PriceBookID,
deliveryZip,
dtItems,
db,
departmentId);
dtItemPricingBreakDown.AsEnumerable()
.GroupBy(i => new {
sku = i.Field<string>("sku"),
deptid = i.Field<int>("department_id")
})
.Select(group => new
{
sku = group.Key.sku,
deptid = group.Key.deptid,
cnt = group.Count()
});
Update
Better late than never, sorry for the delay in response. My actual issue appears to be for both the query syntax and the extension methods.
Something like this works according to msdn
(http://msdn.microsoft.com/en-us/library/bb386921(v=vs.110).aspx):
// Fill the DataSet.
DataSet ds = new DataSet();
ds.Locale = CultureInfo.InvariantCulture;
FillDataSet(ds);
DataTable orders = ds.Tables["SalesOrderHeader"];
DataTable details = ds.Tables["SalesOrderDetail"];
var query =
from order in orders.AsEnumerable()
join detail in details.AsEnumerable() on order.Field<int>("SalesOrderID") equals detail.Field<int>("SalesOrderID")
where order.Field<bool>("OnlineOrderFlag") == true
&& order.Field<DateTime>("OrderDate").Month == 8
select new
{
SalesOrderID = order.Field<int>("SalesOrderID"),
SalesOrderDetailID = detail.Field<int>("SalesOrderDetailID"),
OrderDate = order.Field<DateTime>("OrderDate"),
ProductID = detail.Field<int>("ProductID")
};
DataTable orderTable = query.CopyToDataTable();
But when I try this...
var query = from exx in dtItemPricingBreakDown.AsEnumerable()
group exx by new { sku = exx.Field<string>("sku"), companyId = exx.Field<int>("department_id") } into grp
select new { sku = grp.Key.sku, DepartmentID = grp.Key.companyId, Cnt = grp.Count() };
DataTable dt3 = query.CopyToDataTable();
I get this exception: "No implicit reference conversion from anonymoustype1 to system.data.datarow". I've tried doing what they did with the dataset in the msdn example as well and I still get the error. Extension method wise I was trying something like this...and still got the same exception.
DataTable dt3 = dtItemPricingBreakDown.AsEnumerable().GroupBy(i => new
{
sku = i.Field<string>("sku"),
deptid = i.Field<int>("department_id")
}).Select(group => new
{
sku = group.Key.sku,
DepartmentID = group.Key.deptid,
cnt = group.Count()
}).Cast<DataRow>().CopyToDataTable();

Replacing foreach loops with LINQ expressions

replacing the foreach loop
IList<EmployeeSearch> empItems = new List<EmployeeSearch>();
EmployeeSearch empItem = new EmployeeSearch();
foreach (var _emp in mediaResults.results)
{
empItem.Title = _emp.title;
empItem.Desc = _emp.description;
empItems.Add(empItem);
}
with the following linq expression and getting error:
empItems = from _emp in employeeResults.results
select new EmployeeSearch
{
Title = _emp.title,
Desc = _emp.description
};
Error:
Error 2 Cannot implicitly convert type 'System.Collections.Generic.IEnumerable' to 'System.Collections.Generic.IList'. An explicit conversion exists (are you
You need to convert it to a List:
(from _emp in employeeResults.results
select new EmployeeSearch
{
Title = _emp.title,
Desc = _emp.description
}).ToList();
Or...change your empItems definitions to use a var implicit typing:
var empItems = from _emp in employeeResults.results
select new EmployeeSearch
{
Title = _emp.title,
Desc = _emp.description
};
empItems will be an IEnumerable of EmployeeSearch objects.
Either add a call to the ToList extension method at the end of your query:
empItems =
(from _emp in employeeResults.results
select new EmployeeSearch
{
Title = _emp.title,
Desc = _emp.description
})
.ToList();
Or if you can possibly refactor your code to use an IEnumerable<T> instead, that could possibly be better:
IEnumerable<EmployeeSearch> empItems =
from _emp in employeeResults.results
select new EmployeeSearch
{
Title = _emp.title,
Desc = _emp.description
};
This should generally be used if you're just looping through the results and you don't actually need to add / remove elements from the result set or have per-index access to the results. Converting the query results to a list requires allocation of a single, contiguous array in memory, which is a fairly expensive operation.

Linq to SQL Associations with fixed fields

Linq to SQL, in the dbml designer (or otherwise)
I have 3 tables:
Orders, Deliveries and EmailTemplates.
Orders have many Deliveries, and Orders and Deliveries have a status (int) field.
EmailTemplates have a status they apply to and a bool IsForDeliveries field.
I have Linq to sql associations for Order->EmailTemplate on order.status == emailTemplate.status, but I want to add a condition on the association such that emailTemplate.IsForDeliveries == false. Is this possible, or do I just have to remember to check this condition whenever I access order.EmailTemplates?
Edit
AssociateWith is problematic because I also need the counterpart Delivery<->EmailTemplate association which only shows templates with e.is_for_delivery == true.
Adding a property to the class is problematic because I'd like this to be translatable to SQL.
You can use DataLoadOptions.AssociateWith() for this or just create an additional property on the class. Since the Order class is a partial, you could just create a method in a partial beside it adding this:
public IEnumerable<EmailTemplate> DeliveryEmailTemplates {
get { return EmailTemplates.Where(e => e.IsForDeliveries == false); }
}
or alternatively, when creating the datacontext:
var dc = new DataContext();
var dlo = new DataLoadOptions();
dlo.AssociateWith<EmailTemplates>(o => o.EmailTemplates.Where(e => e.IsForDeliveries == false));
dc.LoadOptions = dlo;
var orders = from o in DB.Orders
where o.Id == 5
select o;
foreach(var o in orders) {
//o.EmailTemplates will contain only IsForDeliveries==false...
}
If you fetch your DataContext in a static way you can add this every time, for example I add a method in a partial class of the DataContext called .New:
public static DataContext New {
get {
var dc = new DataContext(MyConnectionString);
var dlo = new DataLoadOptions();
dlo.AssociateWith<EmailTemplates>(o => o.EmailTemplates.Where(e => e.IsForDeliveries == false));
dc.LoadOptions = dlo;
return dc;
}
}
Then use:
var DB = DataContext.New;
var orders = from o in DB.Orders
where o.Id == 5
select o;

LINQ - Joins in a dynamic query

Because of some business decisions I need to change a bit of what I was doing. Yay me. :)
Currently, I have:
public IOrderedQueryable<ProductDetail> GetProductList(string productGroupName, string productTypeName, Dictionary<string,List<string>> filterDictionary)
{
string whereClause = "ProductGroupName='" + productGroupName + "' AND ProductTypeName='" + productTypeName + "'";
string comma = "";
foreach (KeyValuePair<string, List<string>> myKVP in filterDictionary)
{
comma = "";
if (myKVP.Value.Count > 0)
{
whereClause = String.Format("{0} AND FieldName = {1} AND FieldValue IN (", whereClause, myKVP.Key);
foreach (string value in myKVP.Value)
{
whereClause = String.Format("{0}{1}'{2}'", whereClause, comma, value);
comma = ",";
}
whereClause = String.Format("{0})", whereClause);
}
}
var q = db.ProductDetail
.Where (whereClause)
.OrderBy ("ProductTypeName");
return q;
}
Instead of foing this directly, I now need to join through 2 other tables to apply the filter correctly. I'm trying to figure out how to correctly join in a dynamic LINQ query. In TSQL it would be something like:
SELECT pd.*
FROM ProductDetail pd
INNER JOIN ProductFilterAssignment pfa ON pd.ProductID = pfs.ProductID
INNER JOIN ProductFilter pf ON pfs.FIlterID = pf.FIlterID
WHERE pf.FieldName = 'var1' AND pf.FieldValue IN ('var1a','var1b','var1c',etc)
AND pf.FieldName = 'var2' AND pf.FieldValue IN ('var2a','var2b','var2c',etc)
Ouch. Yeah, that's a complicated requirement. You know, lambdas are cumulative, so you can do this much simpler if you use successive linq expressions. Note that subsequent linq expressions are using the prior expression result and the entire isn't actually executed until iterated.
public IOrderedQueryable<ProductDetail> GetProductList(string productGroupName, string productTypeName, Dictionary<string,List<string>> filterDictionary)
{
// Initial select on productGroupName and productTypeName
var products = from product in db.ProductDetail
where product.ProductGroupName == productGroupName && product.ProductTypeName == productTypeName
select product;
// Now add each filter item present.
foreach (KeyValuePair<string, List<string>> myKVP in filterDictionary)
{
products = from product in products
join pfa in db.ProductFilterAssignment on product.ProductID equals pfa.ProductID
join pf in db.Product on pfa.FilterID equals pf.FilterId
where pf.FieldName == myKVP.Key && myKVP.Value.Contains(pf.FieldValue)
select product;
}
return products.OrderBy ("ProductTypeName");
}
I don't have a good "answer" for you, but more of an aside. Check out LINQPad. You might even see an ad on the right-hand side of this page, too. It is very slick for writing LINQ queries. It might help in writing and validating this and any other future LINQ query you write.
Try to use Spolty Framework. It helps to do dynamic query for Linq To SQL and Entity Framework. You can dynamically create left/inner join, add conditions, orderings and other things. If you use Spolty Framework then your code will be look like below:
public IQueryable<ProductDetail> GetProductList(string productGroupName, string productTypeName, Dictionary<string, List<string>> filterDictionary)
{
// create root node
JoinNode productDetailNode = new JoinNode(typeof(ProductDetail));
productDetailNode.AddConditions(new Condition("ProductGroupName", productGroupName),
new Condition("ProductTypeName", productTypeName));
// if there are conditions than we create joins
if (filterDictionary.Count > 0)
{
// create joinNode
// INNER JOIN ProductFilterAssignment pfa ON pd.ProductID = pfs.ProductID
JoinNode productFilterAssignmentNode = new JoinNode(typeof(ProductFilterAssignment));
productDetailNode.AddChildren(productFilterAssignmentNode);
// create joinNode
// INNER JOIN ProductFilter pf ON pfs.FIlterID = pf.FIlterID
JoinNode productFilterNode = new JoinNode(typeof(ProductFilter));
productFilterNode.AddChildren(productFilterNode);
foreach (KeyValuePair<string, List<string>> myKVP in filterDictionary)
{
// create condition pf.FieldName = {1} And AND pf.FieldValue IN ('var1a','var1b','var1c',etc)
productFilterNode.AddConditions(new Condition("FieldName", myKVP.Key),
OrCondition.Create("FieldValue", myKVP.Value.ToArray()));
}
}
// create result query by JoinNode productDetailNode
QueryDesigner queryDesigner = new QueryDesigner(db, productDetailNode).
OrderBy(new Ordering("ProductTypeName"));
return queryDesigner.Cast<ProductDetail>();
}

Resources