Get Types using IEnumerable GetGenericArguments - c#

I have developed a MVC helper for generating display and editable tables (a jquery plugin is required to allow dynamic addition and deletion of rows with full postback in the editable tables) e.g.
#Htm.TableDisplayFor(m => m.MyCollection as ICollection)
which used in conjunction with attributes will include totals in the footer, add columns for view and edit links, render hyperlinks for complex type etc. e.g.
[TableColumn(IncludeTotals = true)]
I'm about to publish it on CodeProject but before doing so, would like to solve one issue.
The helper first gets the ModelMetadata from the expression, checks that it implements ICollection, then gets the type in the collection (note the following snippet is from accepted answers on SO, but as explained below, is not entirely correct)
if (collection.GetType().IsGenericType)
{
Type type = collection.GetType().GetGenericArguments()[0]
The type is used to generate ModelMetadata for the table header (there might not be any rows in the table) and each row in the table body (in case some items are inherited types which have additional properties and would otherwise screw up the column layout)
foreach (var item in collection)
{
ModelMetadata itemMetadata = ModelMetadataProviders.Current
.GetMetadataForType(() => item, type);
What I would like to be able to do is use IEnumerable rather than ICollection so that .ToList() does not need to be called on linq expressions.
In most cases IEnumerable works fine e.g.
IEnumerable items = MyCollection.Where(i => i....);
is OK because .GetGenericArguments() returns an array containing only one type.
The problem is that '.GetGenericArguments()' on some queries returns 2 or more types and there seems to be no logical order. For example
IEnumerable items = MyCollection.OrderBy(i => i...);
returns [0] the type in the collection, and [1] the type used for ordering.
In this case .GetGenericArguments()[0] still works, but
MyCollection.Select(i => new AnotherItem()
{
ID = i.ID,
Name = 1.Name
}
returns [0] the type in the original collection and [1] the type of AnotherItem
So .GetGenericArguments()[1] is what I need to render the table for AnotherItem.
My question is, is there a reliable way using conditional statements to get the type I need to render the table?
From my tests so far, using .GetGenericArguments().Last() works in all cases except when using OrderBy() because the sort key is the last type.
A few things I've tried so far include ignoring types that are value types (as will often be the case with OrderBy(), but OrderBy() queries might use a string (which could be checked) or even worse, a class which overloads ==, < and > operators (in which case I would not be able to tell which is the correct type), and I have been unable to find a way to test if the collection implements IOrderedEnumerable.

Solved (using comments posted by Chris Sinclair)
private static Type GetCollectionType(IEnumerable collection)
{
Type type = collection.GetType();
if (type.IsGenericType)
{
Type[] types = type.GetGenericArguments();
if (types.Length == 1)
{
return types[0];
}
else
{
// Could be null if implements two IEnumerable
return type.GetInterfaces().Where(t => t.IsGenericType)
.Where(t => t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
.SingleOrDefault().GetGenericArguments()[0];
}
}
else if (collection.GetType().IsArray)
{
return type.GetElementType();
}
// TODO: Who knows, but its probably not suitable to render in a table
return null;
}

Related

Nested LINQ with multiple lists

I think this is just a personal code problem, but I just can't see how I'm supposed to return the IsIdentifier bool from this nested object. dp has a list of dd's, and dd has a list of supportedformats, and one supportedformat has a list of parameters, and in parameter is a property (bool) called isIdentifier and I want to return it.
parameters.Add(new SupportedParameter
{
Name = name,
Version = version,
IsIdentifier = dp.Where(dp => dp.dd
.Where(dd => dd.SupportedFormats
.Where(sf => sf.Parameters.Where(sp => sp.Name == name).FirstOrDefault().IsIdentifier)
)
)
}
);
Errors:
Cannot implicitly convert type IEnumerable<SupportedFormat> to bool.
Cannot convert lambda expression to intended delegate type because some of the return types in the block are not implicitly convertible to the delegate type
Also can somebody tell me how to format the code on stackoverflow? Because I'm on a different pc and usually TAB and SHIFT + TAB do the trick. But it didn't work so I tried CTRL + [ or CTRL + ] and these don't work either.
From looking at your code, it appears that you want to find the first deeply nested Parameter with Name == name and then return its IsIdentifier boolean property.
You can reduce the complexity of this operation by "flattening" out all the nested collections, so you'll end up with one big collection containing every Parameter across all the parent objects, by using the LINQ SelectMany extension method.
Here's how I would approach your specific case:
...
IsIdentifier = dps
.SelectMany(dp => dp.dds)
.SelectMany(dd => dd.SupportedFormats)
.SelectMany(sf => sf.Parameters)
.FirstOrDefault(parameter => parameter.Name == name)?.IsIdentifier ?? false;
...
The ?.IsIdentifier ?? false part is just a fallback in case there are no Parameters with the specified name - it will just set IsIdentifier to false in that case.

How to Update/Merge two huge List<Class> with hundreds of properties in C#, based on common matching Key in most efficient way

I have two large set of collection List<Class> with hundreds of properties.
e.g. Original Collection List<OriginalCollection> and Updated Collection List<UpdatedColleciton>
UpdatedCollection will contain value in certain columns which most probably will not be part of OriginalCollection and UpdatedCollection might have certain KeyColumn [ID Column] which might not be part of OriginalCollection, and I'm receiving thousands of data set in OriginalCollection and UpdatedColletion will increase in records over a period of time.
I do have a requirement where only null or empty column of OriginalCollection should get replaced with matching UpdatedCollection value by ID and if no matching ID is available then those records should get added in OriginalCollection from UpdatedCollection.
I tried with AutoMapper, where I tried to Update OriginalCollection with UpdatedCollection based on matching ID, for which I'm unable to find any AutoMapper configuration for my sets of above mentioned requirement.
I'm looking for most effective solution which should not impact on performance, thats why I did not go threw typical way of Union and Intersection, as Modal have hundreds of property and thousands of records are there, and as I do have plenty of properties I think library like AutoMapper would be good option than writing logic in loop to check value on each column for all thousands of record.
Please suggest any better and performance efficient solution like AutoMapper Configuration or any other .Net inbuilt feature to achieve this scenario.
I also checked with AutoMapper.Collection as below from https://github.com/AutoMapper/AutoMapper.Collection
cfg.CreateMap<OrderItemDTO, OrderItem>().EqualityComparison((odto, o) => odto.ID == o.ID);
Mapping OrderDTO back to Order will compare Order items list based on if their ID's match
Mapper.Map<List<OrderDTO>,List<Order>>(orderDtos, orders);
but it has below behavior and not working as expected to my requirement
If ID's match will map OrderDTO to Order
If OrderDTO exists and Order doesn't add to collection Not Working for me
If Order exists and OrderDTO doesn't remove from collection Not Working for me
AutoMapper is known mapping library and has good documentation as well, but unable to find similar detaild documentation for AutoMapper.Collection, I've explored AutoMapper.Collection but it was not providing solution as per my requirement.
So, I need to go traditional way.
Prepared differences of collection by LINQ query.
var common = original.Where(x => revised.Exists(z => z.ID == x.ID)).ToList();
var nonCommon = revised.Where(x => !original.Exists(z => z.ID == x.ID)).ToList();
foreach(var item in common)
{
var derived = revised.FirstOrDefault(x => x.ID == item.ID);
// Added Extention Method to compare and update property
var data = item.UpdateProperties(derived);
}
common.AddRange(nonCommon);
Utilized Reflection to compare objects and update it's value after comparision on property level, for all datatypes.
public static T UpdateProperties<T>(this T source, T destination)
{
Type type = source.GetType(); // Gets Source Object Type
PropertyInfo[] props = type.GetProperties(); // Gets Source object Properties
foreach (var prop in props) // Iterate threw all properties of source object
{
var sourceValue = prop.GetValue(source); // Get source object value by Property Name
var destinationValue = prop.GetValue(destination); // Get destination object value by Property Name, to update source object
// Update source object property value only if derived object's property has value and source object doesn't
if (string.IsNullOrEmpty(sourceValue?.ToString()) && !string.IsNullOrEmpty(destinationValue?.ToString()))
{
prop.SetValue(source, destinationValue); // Update source object's property with value of derived object
}
}
return source;
}

Can't dynamically cast a collection of objects to a type known at runtime

I know this question was here multiple times but i did some searching and i couldn't find solution that would fit:
private void UpdateCollections<T>(ICollection<T> updatedFormulas,
ICollection<T> currentFormulas) where T: IIdentifiable
{
// Irrelevant code omitted
foreach (var prop in newOrUpdatedFormula.GetType().GetProperties())
{
if (prop.PropertyType != typeof(string) &&
prop.PropertyType.GetInterface(typeof(IEnumerable).Name) != null &&
prop.GetValue(newOrUpdatedFormula) != null)
{
// the dynamic type i need is stored here =>
// prop.PropertyType.GenericTypeArguments[0]
UpdateCollections((ICollection<??>prop.GetValue(newOrUpdatedFormula),
(ICollection<??>prop.GetValue(existingFormula));
}
}
}
I need to go through two collections of objects to compare them. inside of those two collections there are other sub-collections of different type (but still implementing IIdentifiable interface). It goes this way few levels deep. So the '??' type i know at runtime and i know it implements IIdentifiable.
I've tried casting to ICollection<IIdentifiable> and it didn't work, i've tried to play with MakeGenericType() method but also it didn't help.
Something like below won't even compile:
Type typeInCollection = typeof(ICollection<>)
.MakeGenericType(prop.PropertyType.GenericTypeArguments[0])
UpdateCollections(Convert.ChangeType(prop.GetValue(newOrUpdatedFormula),
typeInCollection), Convert.ChangeType(prop.GetValue(existingFormula), typeInCollection));
What's the easiest way to make this recursive method work when i know the type inside ICollection at runtime?

Determine if List<Type> contains ICollection<dynamic>

I need to determine if a List contains ICollection where T is dynamic and not known at compiletime. Heres my code to better understand what I mean:
private void RefreshDataSource<T>(ICollection<T> dataSource) where T : IEquatable<T>
{
dynamic row = view.GetFocusedRow();
//Get's the focused row from a DevExpress-Grid
//I don't know the type because it's MasterDetail and the view can be a DetailView. In this case type T isn't the underlying type.
//Getting all Properties
var dummy = dataSource.FirstOrDefault();
var props = dummy.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).ToList();
//Now comes the real problem, I need to determine the Detail Datasource
//and to do this I want to check if there is a Property from ICollection<typeof(row)>
//How can I check on ICollection<typeof(row)> instead of row
//IsAssignableFrom would better fit my needs but I don't get how to solve my problem with it.
var detailSource = props.FirstOrDefault(p => p.PropertyType.IsInstanceOfType(row));
}
The code is broken to the important points, so don't wonder if sth. doesn't makes sense in your eyes ;-). Is there any way to check on ICollection<T> where T is a dynamic type and just know at runtime?
Notice that the given T on top of the method isn't the type of row, because of MasterDetail relationship!!!
UDPATE
I think I need to clarify what I need. Think of me as a Grid. I'm getting a DataSource which is a ICollection<T> and every row is represented by an object of T. Now I'm using MasterDetail relationships so that T just represents one MasterRow in the Grid. The Rows of a DetailView are represented by any ICollection<AnyClass> which is defined as a Property on T.
Now I need to determine this ICollection<AnyClass> Property from T without knowing what AnyClass is at compile time. Because I know the DetailView I can do this:
dynamic row = view.GetFocusedRow();
So row is of type AnyClass and known at Runtime. But how can I find this ICollection<AnyClass> Property in the PropertyCollection of T? This is my problem.
In general, this should do
.GetType().GetInterfaces().Any(intf => intf == typeof(ICollection<dynamic>));
in case you meant Tand not dynamic, simple replace them.
.GetType().GetInterfaces().Any(intf => intf == typeof(ICollection<T>));
If you want T and subtypes of it, it gets more complicated
.GetType().GetInterfaces().Any(intf => intf.IsGenericType &&
intf.GetGenericTypeDefinition() == typeof(ICollection<>) &&
intf.GetGenericArguments()[0].IsAssigneableFrom(typeof(T)));
EDIT Since you calrified what you actually need:
.GetType().GetInterfaces().Any(intf => intf.IsGenericType &&
intf.GetGenericTypeDefinition() == typeof(ICollection<>));
It could be much simpler, but ICollection<T> does not implement ICollection.

Convert IEnumerable<T> to collection of dynamically generated objects

Recently I asked a more general question about getting properties of model through foreign key.
Now I moved a bit further but still have no idea how transform objects on the fly.
What I've got is an IEnumerable collection which I get through repository
regionRaw = unitOfWork.RegionRepository.Get(
keyOrder: q => q.OrderBy(d => d.RegionID),
filter: p => p.FullName.Contains(lastname) || p.ShortName.Contains(lastname),
orderBy: jtSorting,
includeProperties: "District, ISO31662, GOST767Region");
Further I am going to export data from this collection to Excel. So I need a select statement that gets all the fields I need.
dt = regionRaw
.Select(x => new
{
ISO = x.ISO31662.GOSTName,
DistrictName = x.District.ShortName
})
I do not want to enumerate all the fields I need like on the top.
I am able to make a method that recognizes which of the fields have simple values and which have objects referenced through foreign key. And then that method will return a list of properties.
Now I need some way to write something like a foreach inside select. I see something like this:
dt = regionRaw
.Select(x => new
{
foreach (prop in propList)
{
prop.PropertyName = x.GetType()
.GetProperty(prop.TableName)
.GetValue(x, null).GetType()
.GetProperty(prop.PropertyName)
.GetValue(
x.GetType().GetProperty(prop.TableName).GetValue(x, null),
null);
}
}
Where propList is a collection of properties that I get before.
I do totally realize that upper code is more a pseudo-code but I have no idea how to realize this in .NET.
So if you can suggest some solution for this task I will be very grateful. Or maybe you could explain that all this is a bad idea and shouldn't be realized.
You wont be able to create an anonymous type with dynamic properties as anon types are created during compile and your properties are created during execution.
But why do you need strongly typed properties if you're not going to code against them, as you wont know them until someone executes the query?
Expando object may be of use to you?http://msdn.microsoft.com/en-us/library/system.dynamic.expandoobject.aspx

Categories

Resources