Select template - c#

Is it possible to make a template for SELECT in a LINQ query? Right now I have 6 methods that uses the exact same SELECT, i would like to use a template if possible.
This is the code I'm using, when I want to make a change to the select I have to change the same thing at so many places in my code.
result = query.Select(b => new
{
route_id = b.b.route_id,
name = b.b.name,
description = b.b.description,
distance = b.b.distance,
distance_to_route = (int)b.distance_to_from_me,
departure_place = b.b.departure_place,
arrival_place = b.b.arrival_place,
owner = b.b.user.username,
average_rating = b.avg_rating,
is_favorite = b.is_favorite,
date = b.b.date,
attributes = b.b.route_attributes.Select(c =>
c.route_attribute_types.attribute_name),
coordinates = b.b.coordinates.Select(c =>
new coordinateToSend { sequence = c.sequence,
lat = c.position.Latitude,
lon = c.position.Longitude })
});

Here is a simple example of one way you could do this:
In your example, you're converting the source type to an anonymous type. You could create a class to represent your converted/result type, for example:
public class ResultClass
{
public string ResultPropA { get; set; }
}
For examples sake, lets say the following was the definition of your source class:
public class SourceClass
{
public string SourcePropA { get; set; }
}
Now that you have type definitions for your source and result objects, you can create an extension method to convert a collection of your source class to a collection of your result class:
public static class SourceToResultRepository
{
public static IEnumerable<ResultClass> ConvertSourceToResult
(this IEnumerable<SourceClass> source)
{
return source.Select(s => new ResultClass
{
ResultPropA = s.SourcePropA
//Add all other property transformations here
});
}
}
And here is an example of how you could use it wherever you need to perform the transformation:
//Extension usage:
var result = Database.Source.ConvertSourceToResult();
//Direct usage:
var result = SourceToResultRepository.ConvertSourceToResult(Database.Source);

Related

C# Change selected object from a list without changing the original list

Here I've selected the record which a want to get into a new variable. and after that, when I'm changing the value of 'itm.note' it's automatically modifying the original list (Obj.Testlist) as well. How can this be avoided? Only want to change 'itm' object and Obj.Testlist list should be keep as it is.
var itm = Obj.Testlist.Where(x => x.id == 1).SingleOrDefault();
itm.note = "text";
You could clone the fetched item, but that wouldn't be efficient.
How about this:
var changedFetchedItem = Obj.Testlist.Where(x => x.id == 1)
.Select(x => new
{
Id = 1,
Note = "text",
// copy the other properties that you plan to use
// don't copy the ones that you don't use.
}
.SingleOrDefault();
Since you will be fetching at utmost one item, this is very efficient.
I chose to create an object with anonymous type. If you need a specific type, you can add the type after the keyword new
Since I have large model, decided to use cloning for this (Sample code),
public class TestClone : ICloneable
{
public bool IsSuccess { get; set; }
public string Note { get; set; }
public string ErrorDetail { get; set; }
public object Clone()
{
return this.MemberwiseClone();
}
}
static void Main(string[] args)
{
var test1 = new TestClone() { IsSuccess = true, Note= "text", ErrorDetail = "DTL1" };
var test2 = (TestClone) test1.Clone();
test2.Note= "new text";
}

MongoDB Driver C#, Search by nested property using filter definition

I need help with building filter for MongoCollection of class A when I have filters for class B
public class A
{
public string ExampleAProperty { get; set; }
public B NestedB { get; set; }
public ICollection<B> NestedBCollection { get; set; }
}
public class B
{
public string ExampleBProperty { get; set; }
}
public class SearchClass
{
public async Task<ICollection<A>> SearchAsync(IMongoCollection<A> collection)
{
// this is just simple example of possible dozens predefined filters for B class.
// see FilterProvider logic used now
// var bFilter = Builders<B>.Filter.Eq(x => x.ExampleBProperty, "Example");
// in need filter A where B meets provided filters
var cursor = await collection.FindAsync(
Builders<A>.Filter.And(
// predefined filters are easy to reuse with array of elements
Builders<A>.Filter.ElemMatch(x => x.NestedBCollection, FilterProvider.SearchValue("Oleh")),
// but i did not found how to do this with single nested element
Builders<A>.Filter.Eq(x => x.NestedB, FilterProvider.SearchValue("Oleh")) // how?
)
);
return await cursor.ToListAsync();
}
}
// statics is not good but just for working example :)
public static class FilterProvider
{
public static FilterDefinition<B> SearchValue(string? value)
{
var builder = Builders<B>.Filter;
// if value is null show all
if (string.IsNullOrEmpty(value))
{
return builder.Empty;
}
// if value is "Oleh" search for "Amir"
if (value == "Oleh")
{
value = "Amir";
}
// any other additional logic to compose proper filter
// this could be search by serveral properties and so on
// however just for example i will search by hashed value :)
value = value.GetHashCode().ToString();
return builder.Eq(x => x.ExampleBProperty, value);
}
}
Please DON'T
use IMongoQueryable
propose to filter nested element by duplicate code (like x => x.NestedB.ExampleBProperty = "something")
UPDATED: example for Amir with explanation why p.2 is not than case and why code will be is duplicated if you his approach :)
As you may see we have complex (but very simple in current example) filter of data in B class. If you will use your approach - logic of composing filter for B (specified in FilterProvider.SearchValue) will be duplicated.
Thank you
You can easily do this:
public async Task<ICollection<A>> SearchAsync(IMongoCollection<A> collection, string search)
{
var bFilter = Builders<B>.Filter.Eq(x => x.ExampleBProperty, search);
var cursor = await collection.FindAsync(
Builders<A>.Filter.And(
Builders<A>.Filter.ElemMatch(x => x.NestedBCollection, bFilter),
Builders<A>.Filter.Eq(x => x.NestedB.ExampleBProperty, search)
)
);
return await cursor.ToListAsync();
}

C# / Sorting a text file / IComparer / Custom sort

I have a text file that I want to be sorted.
Each line has a package name, a pipe and a version number.
Examples:
AutoFixture|4.15.0
Castle.Windsor.Lifestyles|0.3.0
I tried to use the default list.Sort() method but I obtained:
AutoFixture|4.15.0
Castle.Core|3.3.0
Castle.Windsor.Lifestyles|0.3.0
Castle.Windsor|3.3.0
FluentAssertions|5.10.3
Instead of
AutoFixture|4.15.0
Castle.Core|3.3.0
Castle.Windsor|3.3.0
Castle.Windsor.Lifestyles|0.3.0
FluentAssertions|5.10.3
As shown, I would like "Castle.Windsor" to appear before "Castle.Windsor.Lifestyles".
I'm pretty sure I have to use the IComparer but I can't find a way to get the shorter name first.
So far, I created a custom sort like this which is not working..
public class PackageComparer : IComparer<string>
{
// Assume that each line has the format: name|number
private readonly Regex packageRegEx = new Regex(#"[\w.]+\|[\d.]+", RegexOptions.Compiled);
public int Compare(string x, string y)
{
var firstPackage = this.packageRegEx.Match(x);
var firstLeft = firstPackage.Groups[1].Value;
var firstRight = firstPackage.Groups[2].Value;
var secondPackage = this.packageRegEx.Match(y);
var secondLeft = secondPackage.Groups[1].Value;
var secondRight = secondPackage.Groups[2].Value;
if (firstLeft < secondLeft)
{
return -1;
}
if (firstRight > secondLeft)
{
return 1;
}
return string.CompareOrdinal(firstSceneAlpha, secondSceneAlpha);
}
}
Well, you can use Linq, split by the pipe and order by the package name then by the versioning:
var input = #"AutoFixture|4.15.0
Castle.Core|3.3.0
Castle.Windsor.Lifestyles|0.3.0
Castle.Windsor|3.3.0
FluentAssertions|5.10.3
Castle.Core|3.1.0";
var list = input.Split(new string[]{"\r\n","\n"},StringSplitOptions.None).ToList();
list = list
.OrderBy(x => x.Split('|')[0])
.ThenBy(x => new Version(x.Split('|')[1]))
.ToList();
Outputs:
AutoFixture|4.15.0
Castle.Core|3.1.0
Castle.Core|3.3.0
Castle.Windsor|3.3.0
Castle.Windsor.Lifestyles|0.3.0
FluentAssertions|5.10.3
You can do something like this:
public class YourClassName
{
public string PackageName { get; set; }
public string Pipe { get; set; }
public string Version { get; set; }
}
Load your data into list to sort
List<YourClassName> list = souce of data;
list = SortList<YourClassName>(list, "PackageName");
SortList Method:
public List<YourClassName> SortList<TKey>(List<YourClassName> list, string sortBy)
{
PropertyInfo property = list.GetType().GetGenericArguments()[0].GetProperty(sortBy);
return list.OrderBy(e => property.GetValue(e, null)).ToList<YourClassName>();
}

Check which elements are on one list comparing to another list LINQ

I have two lists, one of all languages and another subset of languages that the site has, the idea is to return all the languages but change the property of a boolean if the element of the subset corresponds to the list of all languages.
DTO of language:
public class DTOLanguage
{
public bool HaveLanguage { get; set; }
public int IdLanguage { get; set; }
//Other properties...
}
Method that returns all languages:
public List<DTOLanguage> GetLanguages()
{
var result = repository.RepSite.GetLanguages().Select(x => new DTOLanguage
{
IdLanguage = x.IdLanguage,
CodName = x.CodName,
Name = x.Name
}).ToList();
return result;
}
Method that returns the subset of languages:
public List<DTOLanguage> GetLanguagesById(int idSite)
{
var result = repository.RepSite.GetLanguagesById(idSite).Select(x => new DTOLanguage
{
IdLanguage = x.IdLanguage
}).ToList();
return result;
}
The GetLanguagesById is called in the DataAccess layer, so what Im thinking is that this method should receive another parameter (what GetLanguages returns) and make some fancy LINQ there.
I know that I can filter (example):
SubsetOfLanguages.Where(lg => lg.IdLanguage == AllLanguagesItem.IdLanguage)
{
AllLanguagesItem.HaveLanguage = True;
}
But Im not really sure as how it should be.
Thanks in advance.
You could use Contains extension method this way:
var languages=GetLanguages();
var subsetids=repository.RepSite.GetLanguagesById(idSite).Select(x =>x.IdLanguage);//Select just the id value
foreach(var l in languages.Where(l=>subsetids.Contains(l.IdLanguage)))
{
l.HaveLanguage = true;
}
You could do this:
var allLanguages = GetLanguages();
var subset = SubsetOfLanguages
.Where(lg => allLanguages.Any(a => lg.IdLanguage == a.IdLanguage))
.ToArray();
foreach(var item in subset)
{
item.HaveLanguage = True;
}

Dynamic Expression using LINQ. How To Find the Kitchens?

I try do implement a user dynamic filter, where used selects some properties, selects some operators and selects also the values.
As I didn't find yet an answer to this question, I tried to use LINQ expressions.
Mainly I need to identify all houses which main rooms are kitchens(any sens, I know).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
//using System.Linq.Dynamic;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
Room aRoom = new Room() { Name = "a Room" };
Room bRoom = new Room() { Name = "b Room" };
Room cRoom = new Room() { Name = "c Room" };
House myHouse = new House
{
Rooms = new List<Room>(new Room[] { aRoom }),
MainRoom = aRoom
};
House yourHouse = new House()
{
Rooms = new List<Room>(new Room[] { bRoom, cRoom }),
MainRoom = bRoom
};
House donaldsHouse = new House()
{
Rooms = new List<Room>(new Room[] { aRoom, bRoom, cRoom }),
MainRoom = aRoom
};
var houses = new List<House>(new House[] { myHouse, yourHouse, donaldsHouse });
//var kitchens = houses.AsQueryable<House>().Where("MainRoom.Type = RoomType.Kitchen");
//Console.WriteLine("kitchens count = {0}", kitchens.Count());
var houseParam = Expression.Parameter(typeof(House), "house");
var houseMainRoomParam = Expression.Property(houseParam, "MainRoom");
var houseMainRoomTypeParam = Expression.Property(houseMainRoomParam, "Type");
var roomTypeParam = Expression.Parameter(typeof(RoomType), "roomType");
var comparison = Expression.Lambda(
Expression.Equal(houseMainRoomTypeParam,
Expression.Constant("Kitchen", typeof(RoomType)))
);
// ???????????????????????? DOES NOT WORK
var kitchens = houses.AsQueryable().Where(comparison);
Console.WriteLine("kitchens count = {0}", kitchens.Count());
Console.ReadKey();
}
}
public class House
{
public string Address { get; set; }
public double Area { get; set; }
public Room MainRoom { get; set; }
public List<Room> Rooms { get; set; }
}
public class Room
{
public double Area { get; set; }
public string Name { get; set; }
public RoomType Type { get; set; }
}
public enum RoomType
{
Kitchen,
Bedroom,
Library,
Office
}
}
var kitchens = from h in houses
where h.MainRoom.Type == RoomType.Kitchen
select h;
But you must set the RoomType property on the rooms before.
Ok, edit:
so you must redefine:
var comparison = Expression.Lambda<Func<House, bool>>(...
Then, when you use it:
var kitchens = houses.AsQueryable().Where(comparison.Compile());
Edit #2:
Ok, here you go:
var roomTypeParam = Expression.Parameter(typeof(RoomType), "roomType");
// ???????????????????????? DOES NOT WORK
var comparison = Expression.Lambda<Func<House, bool>>(
Expression.Equal(houseMainRoomTypeParam,
Expression.Constant(Enum.Parse(typeof(RoomType), "Kitchen"), typeof(RoomType))), houseParam);
// ???????????????????????? DOES NOT WORK
var kitchens = houses.AsQueryable().Where(comparison);
Edit #3: Of, for your needs, I am out of ideas for now. I give you one last one:
Declare an extension method on the String type:
internal static object Prepare(this string value, Type type)
{
if (type.IsEnum)
return Enum.Parse(type, value);
return value;
}
Then use it in that expression like:
Expression.Constant("Kitchen".Prepare(typeof(RoomType)), typeof(RoomType))
That's because apparently enums are treated differently. That extension will leave the string unaltered for other types. Drawback: you have to add another typeof() there.
// ???????????????????????? DOES NOT WORK
var kitchens = houses.AsQueryable().Where(comparison);
The Where method takes a Func<House, bool> or a Expression<Func<House, bool>> as the parameter, but the variable comparison is of type LambdaExpression, which doesn't match. You need to use another overload of the method:
var comparison = Expression.Lambda<Func<House, bool>>(
Expression.Equal(houseMainRoomTypeParam,
Expression.Constant("Kitchen", typeof(RoomType))));
//now the type of comparison is Expression<Func<House, bool>>
//the overload in Expression.cs
public static Expression<TDelegate> Lambda<TDelegate>(Expression body, params ParameterExpression[] parameters);
I wouldn't build the where clause in that way - I think it's more complex than it needs to be for your needs. Instead, you can combine where clauses like this:
var houses = new List<House>(new House[] { myHouse, yourHouse, donaldsHouse });
// A basic predicate which always returns true:
Func<House, bool> housePredicate = h => 1 == 1;
// A room name which you got from user input:
string userEnteredName = "a Room";
// Add the room name predicate if appropriate:
if (!string.IsNullOrWhiteSpace(userEnteredName))
{
housePredicate += h => h.MainRoom.Name == userEnteredName;
}
// A room type which you got from user input:
RoomType? userSelectedRoomType = RoomType.Kitchen;
// Add the room type predicate if appropriate:
if (userSelectedRoomType.HasValue)
{
housePredicate += h => h.MainRoom.Type == userSelectedRoomType.Value;
}
// MainRoom.Name = \"a Room\" and Rooms.Count = 3 or
// ?????????????????????????
var aRoomsHouses = houses.AsQueryable<House>().Where(housePredicate);
I tested this one, honest :)
what about this
var kitchens = houses
.SelectMany(h => h.Rooms, (h, r) => new {House = h, Room = r})
.Where(hr => hr.Room.Type == RoomType.Kitchen)
.Select(hr => hr.House);
To add a new Enum type to dynamic Linq, you must add the following code :
typeof(Enum),
typeof(T)
T : Enum type
in predefined types of dynamic. That works for me.

Categories