Linq with DynamicObject - linq

I have List where MyType : DynamicObject. The reason for MyType inheriting from DynamicObject, is that I need a type that can contain unknown number of properties.
It all works fine until I need to filter List. Is there a way I can do a linq that will do something like this:
return all items where any of the properties is empty string or white space?

(from the comment) can I do above linq query with List?
Yes, here is how you can do it with ExpandoObject:
var list = new List<ExpandoObject>();
dynamic e1 = new ExpandoObject();
e1.a = null;
e1.b = "";
dynamic e2 = new ExpandoObject();
e2.x = "xxx";
e2.y = 123;
list.Add(e1);
list.Add(e2);
var res = list.Where(
item => item.Any(p => p.Value == null || (p.Value is string && string.IsNullOrEmpty((string)p.Value)))
);
The ExpandoObject presents an interface that lets you enumerate its property-value pairs as if they were in a dictionary, making the process of checking them a lot simpler.

Well as long as each object's properties are not unknown internally to themselves you could do it.
There isn't a great generic way to test all the properties of a dynamic object, if you don't have control over the DynamicObject you hope the implementer implemented GetDynamicMemberNames() and you can use the nuget package ImpromptuInterface's methods for getting the property names and dynamically invoking those names.
return allItems.Where(x=> Impromptu.GetMemberNames(x, dynamicOnly:true)
.Any(y=>String.IsNullOrWhiteSpace(Impromptu.InvokeGet(x,y));
Otherwise, since it's your own type MyType you can add your own method that can see internal accounting for those member values.
return allItems.Where(x => x.MyValues.Any(y=>String.IsNullOrWhitespace(x));

Related

Group by Dynamic Aggregate

In the code below I want to replace x.BaseSalary with any other property whose name is stored in feildtoretrive:
var feildtoretrive = "BaseSalary"
var groupcal = db.Employees.GroupBy(x=>x.Region)
.Select(group => new {
Key = group.Key,
Avg = group.Average(x => x.BaseSalary)
})
.ToList();
You need to
create a MemberExpression that accesses that member of an instance
compile a lambda expression that returns that member for a passed instance parameter
(I assume that the elements in Employees are of type Employee)
// the parameter expression for the lambda (your 'x')
var parameter = Expression.Parameter(typeof(Employee));
// the property access expression (your 'x.BaseSalary' or 'x.<feildtoretrive')
var propAccess = Expression.PropertyOrField(parameter, feildtoretrive);
// the build-together and compiled lambda
var expression = (Expression<Func<Employee, int?>>)Expression.Lambda(propAccess, parameter);
You can now use lambda for the x.Average call:
new { Key = group.Key, Avg = group.Average(lambda) }
A caveat: This only works now for members of type int?. I'm lacking a little experience on how to do this more type independent, but what types can you calculate an average over? But if there are int or double members, another cast expression maybe necessary.
EDIT: (changed the return type to int?). According to Ivan Stoev's comment on my follow up question you could try this:
new { Key = group.Key, Avg = group.AsQueryable().Average(expression) }
EF6 should recognize the call to AsQueryable() and then use the correct Average method (note that I use the expression as argument instead of the lambda). EF Core and linq2Sql won't work with that.

object reference not set error in mvc5

i am using viewmodel to display data from two tables (Eta and Voyage) and i have used viewmodel name as 'EtaVoyage'.The problem is when i use this query, it gives me this error
Additional information: Object reference not set to an instance of an object.
var Test = db.Etas.AsEnumerable().Select(v => new EtaVoyage()
{
ShippingAgent = v.ShippingAgent,
VesselInformation = v.VesselInformation,
Port = v.Port,
CPort = v.CPort,
EtaDate = v.EtaDate,
GoodsCarried = v.VoyageDetails.FirstOrDefault().GoodsCarried,
VoyagePurpose = v.VoyageDetails.FirstOrDefault().VoyagePurpose
}).ToList();
return View(Test);
But when i comment the last two fields related to voyagedetails, it is working fine.
var Test = db.Etas.AsEnumerable().Select(v => new EtaVoyage()
{
ShippingAgent = v.ShippingAgent,
VesselInformation = v.VesselInformation,
Port = v.Port,
CustomPort = v.CustomPort,
EtaDate = v.EtaDate,
// GoodsCarried = v.VoyageDetails.FirstOrDefault().GoodsCarried,
// VoyagePurpose = v.VoyageDetails.FirstOrDefault().VoyagePurpose
}).ToList();
return View(Test);
i need to display these two columns too in the index page.
FirstOrDefault() might return null,
Enumerable.FirstOrDefault : Return Value
Type: TSource
default(TSource) if source is empty; otherwise, the first element in source.
Use
.Select(i=>i.GoodsCarried).FirstOrDefault()
....
GoodsCarried = v.VoyageDetails.Select(i=>i.GoodsCarried).FirstOrDefault(),
VoyagePurpose = v.VoyageDetails.Select(i=>i.VoyagePurpose).FirstOrDefault()
}).ToList();
The collection v.VoyageDetails must not contain any items, and therefore FirstOrDefault is returning the default (null for reference types). You can handle this special case separately, or, since you seem to just be flattening a collection, you can use a null-conditional operator to set GoodsCarried and VoyagePurpose to null when FirstOrDefault returns null.
GoodsCarried = v.VoyageDetails.FirstOrDefault()?.GoodsCarried,
VoyagePurpose = v.VoyageDetails.FirstOrDefault()?.VoyagePurpose
Note it is also possible that:
v.VoyageDetails itself is null, depending on how your class is initialized and data is loaded. If this is expected, you may need to handle this case as well. Again with the null-conditional operator:
GoodsCarried = v.VoyageDetails?.FirstOrDefault()?.GoodsCarried,
VoyagePurpose = v.VoyageDetails?.FirstOrDefault()?.VoyagePurpose
If you are using an ORM such as Entity Framework, the VoyageDetails collection is not eagerly loaded, it may simply not be retrieving the data for you. If this applies, you need to explicitly load the data in the collection. In Entity Framework this is done with an Include call. Note your AsEnumerable() call will stop Linq-To-Sql from optimizing this into a single query, but I assume this is intentional:
db.Etas.Include(x => x.VoyageDetails).AsEnumerable().Select(...)

Using Linq to Determine if List of Uris is Base of another Uri

I would like to build a method that determines if a given URL is a child of one of a number of URL's in a List. I thought of approaching this using Linq but the syntax seems beyond my understanding. Here is what I have attempted and I would expect isChild == true.
List<Uri> ProductionUriList = new List<Uri>(){
new Uri(#"https://my.contoso.com/sites/Engineering",UriKind.Absolute),
new Uri(#"https://my.contoso.com/sites/APAC",UriKind.Absolute),
new Uri(#"https://my.contoso.com/sites/China",UriKind.Absolute),
new Uri(#"https://my.contoso.com/sites/EMEA",UriKind.Absolute),
new Uri(#"https://my.contoso.com/sites/India",UriKind.Absolute),
new Uri(#"https://my.contoso.com/sites/Mexico",UriKind.Absolute),
new Uri(#"https://my.contoso.com/sites/SamCam",UriKind.Absolute),
new Uri(#"https://my.contoso.com/sites/USA",UriKind.Absolute),
};
var isChild =
ProductionUriList.SelectMany (p => p.IsBaseOf(new Uri("https://my.contoso.com/sites/China/Site1",UriKind.Absolute)));
The runtime error says:
The type arguments for method
'System.Linq.Enumerable.SelectMany(System.Collections.Generic.IEnumerable,
System.Func>)'
cannot be inferred from the usage. Try specifying the type arguments
explicitly.
If you just want to check for a bool condition on a set you can use the any operator:
var isChild = ProductionUriList.Any(p => p.IsBaseOf(new Uri("https://my.contoso.com/sites/China/Site1", UriKind.Absolute)));
Concerning your error: The selectmany operator expects a delegate that returns IEnumerable which you do not supply. You are mixing up select and selectmany. If you choose select as the linq operator you could do a count > 0 on the result which would give the same result as using the any operator:
var isChild = ProductionUriList.Select(p => p.IsBaseOf(new Uri("https://my.contoso.com/sites/China/Site1", UriKind.Absolute)).Count > 0;
To determine if the uri is a child of one or more:
var isChild = ProductionUriList.Any(p => p.IsBaseOf(newUri("https://my.contoso.com/sites/China/Site1",UriKind.Absolute)));
To determine if the uri is a child of exactly one:
var isChild = ProductionUriList.Count(p => p.IsBaseOf(newUri("https://my.contoso.com/sites/China/Site1",UriKind.Absolute))) == 1;

LINQ and dynamic strong types

I try to find some references on LINQ with dynamic added strong types, static I have as in example:
var rowColl = _data.AsEnumerable();
var json = (from r in rowColl
select new
{
name = r.Field<string>("name"),
id = r.Field<int>("id"),
}).ToList();
Now where I am interested in if its possible to make "name" and "id" dynamic added on runtime as the information is available in the DataTable "_data", I think there is a simple solution for this however cant find any references on this
It's better practice to avoid using "var" as your type when possible. It looks like rowColl is of type IEnumerable<DataRow>. If that's true, then I would look to see what you can use in the DataRow class (see msdn documentation).
Given that rowColl is actually IEnumerable<DataRow>... If the columns are always going to be in the same order, then you could just do something like r[0] and r[1] in your select statement, i.e.
var json = (from r in rowColl
select new
{
name = r[0], // Where 0 is the index of the "name" column
id = r[1],
}).ToList();
If it complains at you for type-safety, then you could use int.Parse() to cast the string to an int and use the .ToString() extension on your columns instead.

How to filter child collections in Linq

I need to filter the child elements of an entity in linq using a single linq query. Is this possible?
Suppose I have two related tables. Verses and VerseTranslations. The entity created by LINQ to SQL is such that i have a Verse object that contains a child object that is a collection of VerseTranslation.
Now if i have the follow linq query
var res = from v in dc.Verses
where v.id = 1
select v;
I get a collection of Verses whose id is 1 and each verse object contains all the child objects from VerseTranslations.
What I also want to do is filter that child list of Verse Translations.
So far the only way i have been able to come up with is by using a new Type Anonymous or otherwise. As follows
var res= from v in dc.Verses
select new myType
{
VerseId = v.VerseId,
VText = v.Text,
VerseTranslations = (from trans in v.VerseTranslations
where languageId==trans.LanguageId
select trans
};
The above code works, but i had to declare a new class for it. Is there no way to do it in such a manner such that the filtering on the child table can be incorporated in the first linq query so that no new classes have to be declared.
Regards,
MAC
So i finally got it to work thanks to the pointers given by Shiraz.
DataLoadOptions options = new DataLoadOptions();
options.AssociateWith<Verse>(item => item.VerseTranslation.Where(t => languageId.Contains(t.LanguageId)));
dc.LoadOptions = options;
var res = from s in dc.Verse
select s;
This does not require projection or using new extension classes.
Thanks for all your input people.
Filtering on the enclosed collection of the object,
var res = dc.Verses
.Update(v => v.VerseTranslations
= v.VerseTranslations
.Where(n => n.LanguageId == languageId));
By using extension method "Update" from HookedOnLinq
public static class UpdateExtensions {
public delegate void Func<TArg0>(TArg0 element);
/// <summary>
/// Executes an Update statement block on all elements in an IEnumerable<T> sequence.
/// </summary>
/// <typeparam name="TSource">The source element type.</typeparam>
/// <param name="source">The source sequence.</param>
/// <param name="update">The update statement to execute for each element.</param>
/// <returns>The numer of records affected.</returns>
public static int Update<TSource>(this IEnumerable<TSource> source, Func<TSource> update) {
if (source == null) throw new ArgumentNullException("source");
if (update == null) throw new ArgumentNullException("update");
if (typeof(TSource).IsValueType)
throw new NotSupportedException("value type elements are not supported by update.");
int count = 0;
foreach(TSource element in source) {
update(element);
count++;
}
return count;
}
}
If this is coming from a database, you could run your first statement.
Then do a Load or Include of VerseTranslations with a Where clause.
http://msdn.microsoft.com/en-us/library/bb896249.aspx
Do you have a relationship in your model between Verse and VerseTranslations. In that case this might work:
var res= from v in
dc.Verses.Include("VerseTranslations").Where(o => languageId==o.LanguageId)
select v;
Is there no way to do it in such a
manner such that the filtering on the
child table can be incorporated in the
first linq query so that no new
classes have to be declared?
Technically, the answer is no. If you're trying to return more data than a single entity object (Verse, VerseTranslation) can hold, you'll need some sort of object to "project" into. However, you can get around explicitly declaring myType by using an anonymous type:
var res = from v in dc.Verses
select new
{
Verse = v,
Translations = (from trans in v.VerseTranslations
where languageId==trans.LanguageId
select trans).ToList()
};
var first = res.First();
Console.WriteLine("Verse {0} has {1} translation(s) in language {2}.",
first.Verse.VerseId, first.Translations.Count, languageId);
The compiler will generate a class with appropriately-typed Verse and Translations properties for you. You can use these objects for just about anything as long as you don't need to refer to the type by name (to return from a named method, for example). So while you're not technically "declaring" a type, you're still using a new type that will be generated per your specification.
As far as using a single LINQ query, it all depends how you want the data structured. To me it seems like your original query makes the most sense: pair each Verse with a filtered list of translations. If you expect only a single translation per language, you could use SingleOrDefault (or FirstOrDefault) to flatten your subquery, or just use a SelectMany like this:
var res= from v in dc.Verses
from t in v.VerseTranslations.DefaultIfEmpty()
where t == null || languageId == t.LanguageId
select new { Verse = v, Translation = t };
If a Verse has multiple translations, this will return a "row" for each Verse/Translation pair. I use DefaultIfEmpty() as a left join to make sure we will get all Verses even if they're missing a translation.

Resources