IEnumerable<T> GroupBy with Fluent Validation - c#

So the problem I am trying to solve is that I am using a lot of generic classes with <T> that need to execute a .NET Async REST call to retrieve an IEnumerable<T> list of objects from an API. At runtime things are resolved fine with the T stuff because I have some concrete instances higher up the chain.
I have a worker class:
public class Worker<T> where T : class, new()
That has a REST client factory:
IBatchClientFactory batchClientFactory
where in that factory basically creates an instance of this:
public class BatchClient<T> where T : class, new()
That BatchClient has an important method:
public BaseBatchResponse<T> RetrieveManyAsync(int top = 100, int skip = 0)
so that the worker class's method does something like:
var batchClient = this.batchClientFactory.Create<T>(siteId);
var batchResponse = await batchClient.RetrieveManyAsync(top, skip);
Batch Response looks like:
public class BaseBatchResponse<T>
{
public List<T> Value { get; set; }
public BaseBatchResponse<T> Combine(BaseBatchResponse<T> baseBatchResponse)
{
return new BaseBatchResponse<T>
{
Value = this.Value.Concat(baseBatchResponse.Value).ToList()
};
}
}
Now at runtime things are ok because higher up the chain i will instantiate Worker into something like.. new Worker<Appointment>(); And the T's will all just work perfectly since everything down the chain is just doing generics.
My problem now is that I would like to evaluate my batchResponse and go through the List and run some validation against each element in the list. I saw this article on stack overflow that seems to let you split a list into 2 lists using GroupBy via a Dictionary where some SomeProp is the thing you're splitting around.. but can you do that GroupBy logic using a method call? And more importantly can I use FluentValidation as that method call? Ideally my code would look like:
var groups = allValues.GroupBy(val => validationService.Validate(val)).ToDictionary(g => g.Key, g => g.ToList());
List<T> valids = groups[true];
List<T> invalids= groups[false];
Where the result would be a List of my objects that are valid, and a second List of my objects that are invalid.
Ideally I would then just make a FluentValidation class that binds to my concreate Appointment class and has a rule inside it:
this.When(x => !string.IsNullOrWhiteSpace(x.Description), () =>
this.RuleFor(x => x.Description).Length(1, 4000));
Which will hook everything together and be used to determine if my object at runtime belongs in the valids or invalids list

I'm not sure fluent means, there is a approch to achieve that using LINQ:
using System.Collections.Generic;
using System.Linq;
namespace Investigate.Samples.Linq
{
class Program
{
public class SomeEntity
{
public string Description { get; set; }
}
static void Main(string[] args)
{
//Mock some entities
List<SomeEntity> someEntities = new List<SomeEntity>()
{
new SomeEntity() { Description = "" },
new SomeEntity() { Description = "1" },
new SomeEntity() { Description = "I am good" },
};
//Linq: Where to filter out invalids, then category to result with ToDictionary
Dictionary<bool, SomeEntity> filteredAndVlidated = someEntities.Where(p => !string.IsNullOrWhiteSpace(p.Description)).ToDictionary(p => (p.Description.Length > 1));
/* Output:
* False: new SomeEntity() { Description = "1" }
* True: new SomeEntity() { Description = "I am good" }
* */
}
}
}
Code segment:
Dictionary<bool, SomeEntity> filteredAndVlidated = someEntities.Where(p => !string.IsNullOrWhiteSpace(p.Description)).ToDictionary(p => (p.Description.Length > 1));

Related

How to get the assigned properties of an object initializer using reflection?

I have classes that specify hard coded values. An example looks like the following:
[CodeGenAsValueObject]
public class DfltTaxableTypes : WithEnumerablePropsOfType<TaxableType>
{
public static TaxableType Taxable = new TaxableType()
{ // <- I want what's inside of here specifically as Dict<string, object>
TaxableTypeId = new Guid("7a702ec4-94a3-431b-8217-2b44ef0ae22a"),
Name = "Taxable"
}; // <- To here
public static TaxableType NonTaxable = new TaxableType()
{
TaxableTypeId = new Guid("76eee771-cbdc-4a7b-95d5-0d6e0959bdff"),
Name = "NonTaxable"
};
}
Assume TaxableType has many properties that otherwise I don't care about.
How can I using reflection get something like a Dictionary<string, object> of assigned properties for each static TaxableType?
I am using this in custom Code Generation for our Typescript clients so performance is not an issue.
What I have as a start:
var dfltValObjContainers = myAssy.GetTypes()
.Where(x => x.CustomAttributes.Select(y => y.AttributeType)
.ToList()
.Contains(typeof(CodeGenAsValueObject)))
.Distinct().ToList();
var containerInstances= dfltValObjContainers.Select(x => {
var instance = Activator.CreateInstance(x);
// Could get all Fields' values where value != default(fieldType)..
// Would prefer a more direct get assigned properties of object initializer.
var eaValueObj = x.GetFields().Select(f => {
return f.GetObjectInitializersAsDict() // <- Need help here.
}).ToList();
}).ToList();

How to yield multiple objects with respect to a multi-valued column in Dynamic Linq

Scenario:
I have to export an excel file which will contain list of Parts. We have enabled the user to select the columns and get only selected columns' data in the exported file. Since this is a dynamic report, I am not using any concrete class to map the report as this will result in exporting empty column headers in the report, which is unnecessary. I am using Dynamic Linq to deal with this scenario.
I have a list of dynamic objects fetched from dynamic linq.
[
{"CleanPartNo":"Test","Description":"test","AliasPartNo":["258","145","2313","12322"]},
{"CleanPartNo":"Test1","Description":"test1","AliasPartNo":[]}
]
How can I get 4 rows out of this json like
Please note that I cannot use a strongly typed object to deserialize/ Map it using JSON.Net
Update
Following is the code:
public class Part
{
public int Id { get; set; }
public string CleanPartNo { get; set; }
public string Description { get; set; }
public List<PartAlias> AliasPartNo { get; set; }
}
public class PartAlias
{
public int PartId { get; set; }
public int PartAliasId { get; set; }
public string AliasPartNo { get; set; }
}
var aliases = new List<PartAlias> {
new PartAlias{AliasPartNo="258" },
new PartAlias{AliasPartNo="145" },
new PartAlias{AliasPartNo="2313" },
new PartAlias{AliasPartNo="12322" }
};
List<Part> results = new List<Part> {
new Part{CleanPartNo="Test", Description= "test", PartAlias=aliases },
new Part{CleanPartNo="Test1", Description= "test1" }
};
var filters = "CleanPartNo,Description, PartAlias.Select(AliasPartNo) as AliasPartNo";
var dynamicObject = JsonConvert.SerializeObject(results.AsQueryable().Select($"new ({filters})"));
in the dynamicObject variable I get the json mentioned above
Disclaimer: The following relies on anonymous classes, which is not exactly the same as dynamic LINQ (not at all), but I figured that it may help anyway, depending on your needs, hence I decided to post it.
To flatten your list, you could go with a nested Select, followed by a SelectMany (Disclaimer: This assumes that every part has at least one alias, see below for the full code)
var flattenedResult = result.Select(part => part.AliasPartNumber.Select(alias => new
{
CleanPartNo = part.CleanPartNo,
Description = part.Description,
AliasPartNo = alias.AliasPartNo
})
.SelectMany(part => part);
You are first projecting your items from result (outer Select). The projection projects each item to an IEnumerable of an anonymous type in which each item corresponds to an alias part number. Since the outer Select will yield an IEnumerable<IEnumerable> (or omething alike), we are using SelectMany to get a single IEnumerable of all the items from your nested IEnumerables. You can now serialize this IEnumerable of instances of an anonymous class with JsonConvert
var json = sonConvert.SerializeObject(flatResults);
Handling parts without aliases
If there are no aliases, the inner select will yield an empty IEnumerable, hence we will have to introduce a special case
var selector = (Part part) => part.AliasPartNumber?.Any() == true
? part.AliasPartNumber.Select(alias => new
{
CleanPartNo = part.CleanPartNo,
Description = part.Description,
AliasPartNo = alias.AliasPartNo
})
: new[]
{
new
{
CleanPartNo = part.CleanPartNo,
Description = part.Description,
AliasPartNo = alias.AliasPartNo
}
};
var flattenedResult = result.Select(selector).SelectMany(item => item);
From json you provided you can get values grouped by their name in this way:
var array = JArray.Parse(json);
var lookup = array.SelectMany(x => x.Children<JProperty>()).ToLookup(x => x.Name, x => x.Value);
then this is just a manner of simple loop over the lookup to fill the excel columns.
However, I would suggest to do the flatenning before JSON. I tried for some time to make it happen even without knowing the names of the columns that are arrays, but I failed, and since it's your job, I won't try anymore :P
I think the best way here would be to implement custom converter that would just multiply objects for properties that are arrays. If you do it well, you would get infinite levels completely for free.

MongoDB Linq OfType() on fields

This is my MongoDB document structure:
{
string _id;
ObservableCollection<DataElement> PartData;
ObservableCollection<DataElement> SensorData;
...
other ObservableCollection<DataElement> fields
...
other types and fields
...
}
Is there any possibility to retrieve a concatenation of fields with the type ObservableCollection<DataElement>? Using LINQ I would do something like
var query = dbCollection
.AsQueryable()
.Select(x => new {
data = x
.OfType(typeof(ObservableCollection<DataElement>))
.SelectMany(x => x)
.ToList()
});
or alternatively
data = x.Where(y => typeof(y) == typeof(ObservableCollection<DataElement>)
.SelectMany(x => x).ToList()
Unfortunately .Where() and .OfType() do not work on documents, only on queryables/lists, so is there another possibility to achieve this? The document structure must stay the same.
Edit:
After dnickless answer I tried it with method 1b), which works pretty well for getting the fields thy way they are in the collection. Thank you!
Unfortunately it wasn't precisely what I was looking for, as I wanted to be all those fields with that specific type put together in one List, at it would be returned by the OfType or Where(typeof) statement.
e.g. data = [x.PartData , x.SensorData, ...] with data being an ObsverableCollection<DataElement>[], so that I can use SelectMany() on that to finally get the concatenation of all sequences.
Sorry for asking the question unprecisely and not including the last step of doing a SelectMany()/Concat()
Finally I found a solution doing this, but it doesn't seem very elegant to me, as it needs one concat() for every element (and I have more of them) and it needs to make a new collection when finding a non-existing field:
query.Select(x => new
{
part = x.PartData ?? new ObservableCollection<DataElement>(),
sensor = x.SensorData ?? new ObservableCollection<DataElement>(),
}
)
.Select(x => new
{
dataElements = x.part.Concat(x.sensor)
}
).ToList()
In order to limit the fields returned you would need to use the MongoDB Projection feature in one way or the other.
There's a few alternatives depending on your specific requirements that I can think of:
Option 1a (fairly static approach): Create a custom type with only the fields that you are interested in if you know them upfront. Something like this:
public class OnlyWhatWeAreInterestedIn
{
public ObservableCollection<DataElement> PartData { get; set; }
public ObservableCollection<DataElement> SensorData { get; set; }
// ...
}
Then you can query your Collection like that:
var collection = new MongoClient().GetDatabase("test").GetCollection<OnlyWhatWeAreInterestedIn>("test");
var result = collection.Find(FilterDefinition<OnlyWhatWeAreInterestedIn>.Empty);
Using this approach you get a nicely typed result back without the need for custom projections.
Option 1b (still pretty static): A minor variation of Option 1a, just without a new explicit type but a projection stage instead to limit the returned fields. Kind of like that:
var collection = new MongoClient().GetDatabase("test").GetCollection<Test>("test");
var result = collection.Find(FilterDefinition<Test>.Empty).Project(t => new { t.PartData, t.SensorData }).ToList();
Again, you get a nicely typed C# entity back that you can continue to operate on.
Option 2: Use some dark reflection magic in order to dynamically create a projection stage. Downside: You won't get a typed instance reflecting your properties but instead a BsonDocument so you will have to deal with that afterwards. Also, if you have any custom MongoDB mappings in place, you would need to add some code to deal with them.
Here's the full example code:
First, your entities:
public class Test
{
string _id;
public ObservableCollection<DataElement> PartData { get; set; }
public ObservableCollection<DataElement> SensorData { get; set; }
// just to have one additional property that will not be part of the returned document
public string TestString { get; set; }
}
public class DataElement
{
}
And then the test program:
public class Program
{
static void Main(string[] args)
{
var collection = new MongoClient().GetDatabase("test").GetCollection<Test>("test");
// insert test record
collection.InsertOne(
new Test
{
PartData = new ObservableCollection<DataElement>(
new ObservableCollection<DataElement>
{
new DataElement(),
new DataElement()
}),
SensorData = new ObservableCollection<DataElement>(
new ObservableCollection<DataElement>
{
new DataElement(),
new DataElement()
}),
TestString = "SomeString"
});
// here, we use reflection to find the relevant properties
var allPropertiesThatWeAreLookingFor = typeof(Test).GetProperties().Where(p => typeof(ObservableCollection<DataElement>).IsAssignableFrom(p.PropertyType));
// create a string of all properties that we are interested in with a ":1" appended so MongoDB will return these fields only
// in our example, this will look like
// "PartData:1,SensorData:1"
var mongoDbProjection = string.Join(",", allPropertiesThatWeAreLookingFor.Select(p => $"{p.Name}:1"));
// we do not want MongoDB to return the _id field because it's not of the selected type but would be returned by default otherwise
mongoDbProjection += ",_id:0";
var result = collection.Find(FilterDefinition<Test>.Empty).Project($"{{{mongoDbProjection}}}").ToList();
Console.ReadLine();
}
}

Querying derived type values using MongoDB C# driver

I'm porting some code from the old legacy MongoDB driver to use the new driver and have hit a problem. I have a collection which contains multiple derived types from a common base class. Previously I was able to query the collection (which is declared using the base type) using derived class properties and just retrieve the derived class documents. So given these classes :
[BsonDiscriminator(RootClass = true)]
[BsonKnownTypes(typeof(Cat),typeof(Dog))]
class Animal
{
[BsonId(IdGenerator = typeof(StringObjectIdGenerator))]
public string Id { get; set; }
public string Name { get; set; }
}
class Cat : Animal
{
public bool LikesFish { get; set; }
}
class Dog : Animal
{
public string FavouriteBone { get; set; }
}
I could then do something like :
MongoCollection<Animal> animals = db.GetCollection<Animal>("Animals");
var q = Query<Cat>.EQ(c => c.LikesFish, true);
var catsThatLikeFish = animals.FindAs<Animal>(q).ToList();
which worked fine.
Now however I have to type the filter and can no longer compile :
IMongoCollection<Animal> animals = db.GetCollection<Animal>("Animals");
var query = Builders<Cat>.Filter.Eq(c => c.LikesFish, true);
var catsThatLikeFish = animals.FindSync(query);
and get this error :
Error CS0411 The type arguments for method 'IMongoCollection<Animal>.FindSync<TProjection>(FilterDefinition<Animal>, FindOptions<Animal, TProjection>, CancellationToken)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
Is this no longer possible using the new driver? We have classes that allow the generic querying of this collection and I can't see any elegant way around this right now.
EDIT :
Sadly separate collections is a non-starter as we mix filter expressions to pull back different types using the same query. In the "cats and dogs" example from above like this :
var catQuery = Query<Cat>.EQ(c => c.LikesFish, true);
var dogQuery = Query<Dog>.EQ(c => c.FavouriteBone, "Beef");
var q = Query.Or(catQuery, dogQuery);
var catsThatLikeFishOrDogsThatLikeBeef = animals.FindAs<Animal>(q).ToList();
I'll look at the "nameof" method above - that may work, but it seems to lack the elegance of the old way to me...
Any help much appreciated!
Thanks,
Steve
You have a collection of cats and dogs togetehr and you want to filter just cats? Or do you want to get all dogs and only that cats that like fish?
In first case i would suggest to query only cats or only dogs from the collection:
var collectionAsCats = db.GetCollection<Cat>("Animal");
var collectionAsDogs = db.GetCollection<Dog>("Animal");
var catsDoesntlikeFish = collectionAsCats.Find(c => c.LikesFish == false).ToList();
var dogs = collectionAsDogs.Find(c => c.FavouriteBone == "bla").ToList();
Or you could have one collection of Animals and query your data with strings:
var collectionAll = db.GetCollection<Animal>("Animal");
var filter = Builders<Animal>.Filter.Eq(nameof(Cat.LikesFish), false);
var catsDoesntlikeFish = collectionAll.Find(filter).As<Animal>().ToList();
You could extend this filter if you want to get dogs together with cats:
var collectionAll = db.GetCollection<Animal>("Animal");
var filter = Builders<Animal>.Filter.Eq(nameof(Cat.LikesFish), false);
var exists = Builders<Animal>.Filter.Exists(nameof(Cat.LikesFish), false);
var orFilter = Builders<Animal>.Filter.Or(filter, exists);
var catsDoesntlikeFishAndDogs = collectionAll.Find(orFilter).ToList();
EDIT
I add here a comment from Craig Wilson, very interest information (thanks, Craig):
in the new API there is an OfType() method...
IMongoCollection<Dog> dogs = db.GetCollection<Animal>("Animals").OfType<Dog>()
OK, this does seem to work :
var catQuery = Builders<Animal>.Filter.Eq(nameof(Cat.LikesFish), true);
var dogQuery = Builders<Animal>.Filter.Eq(nameof(Dog.FavouriteBone), "Beef");
var query = Builders<Animal>.Filter.Or(catQuery, dogQuery);
var catsThatLikeFishOrDogsThatLikeBeef = animals.FindSync(query).ToList();
although I do think it lacks the elegance of the old driver method.

Unable to use use .Except on two List<String>

I am working on an asp.net mvc-5 web applicatio. i have these two model classes:-
public class ScanInfo
{
public TMSServer TMSServer { set; get; }
public Resource Resource { set; get; }
public List<ScanInfoVM> VMList { set; get; }
}
public class ScanInfoVM
{
public TMSVirtualMachine TMSVM { set; get; }
public Resource Resource { set; get; }
}
and i have the following method:-
List<ScanInfo> scaninfo = new List<ScanInfo>();
List<String> CurrentresourcesNames = new List<String>();
for (int i = 0; i < results3.Count; i++)//loop through the returned vm names
{
var vmname = results3[i].BaseObject == null ? results3[i].Guest.HostName : results3[i].BaseObject.Guest.HostName;//get the name
if (!String.IsNullOrEmpty(vmname))
{
if (scaninfo.Any(a => a.VMList.Any(a2 => a2.Resource.RESOURCENAME.ToLower() == vmname.ToLower())))
{
CurrentresourcesNames.Add(vmname);
}
}
}
var allcurrentresourcename = scaninfo.Select(a => a.VMList.Select(a2 => a2.Resource.RESOURCENAME)).ToList();
var finallist = allcurrentresourcename.Except(CurrentresourcesNames).ToList();
now i want to get all the String that are inside the allcurrentrecoursename list but not inside the CurrentresourcesName ?
but that above code is raising the following exceptions :-
Error 4 'System.Collections.Generic.List>'
does not contain a definition for 'Except' and the best extension
method overload
'System.Linq.Queryable.Except(System.Linq.IQueryable,
System.Collections.Generic.IEnumerable)' has some invalid
arguments
Error 3 Instance argument: cannot convert from
'System.Collections.Generic.List>'
to 'System.Linq.IQueryable'
It looks to me like
var allcurrentresourcename = scaninfo.Select(a => a.VMList.Select(a2 => a2.Resource.RESOURCENAME)).ToList();
is not a list of strings at all like you seem to expect it to be. scaninfo is of type List<ScanInfo>, and the lambda expression
a => a.VMList.Select(a2 => a2.Resource.RESOURCENAME)
yields one IEnumerable<TSomething> for each ScanInfo object. So it would seem that allcurrentresourcename is not a List<string>, but rather a List<IEnumerable<TSomething>>, where TSomething is the type of RESOURCENAME (most likely string).
Edit: What you presumably want to use here is the SelectMany LINQ method (see #pquest's comment). It flattens the lists that you get to "one big list" of resource names, which you can then use Except on:
var allcurrentresourcename = scaninfo.SelectMany(a => a.VMList.Select(
b => b.Resource.RESOURCENAME));
You shouldn't even need the ToList() at the end of the line.

Categories

Resources