Sort enum column based on language - c#

Say I have the following simple setup:
public enum Status
{
Pending = 0,
Accepted = 1,
Rejected = 2
}
public class MyEntity
{
public Status Status { get; set; }
}
...and I desire the following ascending sorting for Status for two different languages:
Language 1: 1, 0, 2
Language 2: 0, 1, 2
Is there a simple way to do this in entity framework? I feel like this is probably a common scenario.
I'm thinking the only way is to maintain a separate table with all the translations for Status... then sort by the name column in that table based on the users current language. I wanted to see if anyone had any other ideas though.

You could write a query without having a separate table, but it's not pretty. Nor is it very flexible. Since EF relies on well known methods to map to the equivalent SQL function, you can't interject custom functions. However, you can manually set the order using a let:
var query = from e in MyEntity
let sortOrder = UserLanguage == Language1 // Handle Language 1 Sort
? e.Status == Pending
? 1 : e.Status == Accepted ? 0 : 2
: e.Status == Pending // Handle Language 2 sort
? 0 : e.Status == Accepted ? 1 : 2
orderby sortOrder
select e
And this is only for two language. Another way I could think of is to write the expression tree yourself. This would have the advantage that you can split the logic for each language. We can extract the ternary condition logic and place them in an extension static class. Here's a sample of what it could look like:
public static class QuerySortExtension
{
private static readonly Dictionary<string, Expression<Func<MyEntity, int>>> _orderingMap;
private static readonly Expression<Func<MyEntity, int>> _defaultSort;
public static IOrderedQueryable<MyEntity> LanguageSort(this IQueryable<MyEntity> query, string language)
{
Expression<Func<MyEntity, int>> sortExpression;
if (!_orderingMap.TryGetValue(language, out sortExpression))
sortExpression = _defaultSort;
return query.OrderBy(sortExpression);
}
static QuerySortExtension()
{
_orderingMap = new Dictionary<string, Expression<Func<MyEntity, int>>>(StringComparer.OrdinalIgnoreCase) {
{ "EN", e => e.Status == Status.Pending ? 1 : e.Status == Status.Accepted ? 0 : 2 },
{ "JP", e => e.Status == Status.Pending ? 2 : e.Status == Status.Accepted ? 1 : 0 }
};
// Default ordering
_defaultSort = e => (int)e.Status;
}
}
And you can use the following method this way:
var entities = new[] {
new MyEntity { Status = Status.Accepted },
new MyEntity { Status = Status.Pending },
new MyEntity { Status = Status.Rejected }
}.AsQueryable();
var query = from e in entities.LanguageSort("EN")
select e;
var queryJp = from e in entities.LanguageSort("JP")
select e;
Console.WriteLine("Sorting with EN");
foreach (var e in query)
Console.WriteLine(e.Status);
Console.WriteLine("Sorting with JP");
foreach (var e in queryJp)
Console.WriteLine(e.Status);
Which output the following:
Sorting with EN
Accepted
Pending
Rejected
Sorting with JP
Rejected
Accepted
Pending

EF will sort based on the type of DB column you have mapped the enum to (presumably an int), so it will sort the same way it sorts ints.
IF you want some kind of custom sorter you will have to write your own and sort in memory.
Not really sure i understand your separate table concept, could you give more details.

I accepted Simon's answer since it was an alternative to what I mentioned in the question. Here's a way I figured out how to do it with a separate table that works. Obvious downside is that you have to maintain translations in the database...
public enum Status
{
Pending = 0,
Accepted = 1,
Rejected = 2
}
public class MyEntity
{
public int MyEntityID { get; set; }
public Status Status { get; set; }
}
public enum Language
{
Language1 = 0,
Language2 = 1
}
public class StatusTranslation
{
public int StatusTranslationID { get; set; }
public Language Language { get; set; }
public Status Status { get; set; }
public string Name { get; set; }
}
Insert all the translations into the SQL table in whatever way:
INSERT INTO StatusTranslations (Language, Status, Name) VALUES (0, 0, "Pending")
// etc...
INSERT INTO StatusTranslations (Language, Status, Name) VALUES (1, 0, "En attendant")
// etc...
Now join and sort:
var userLanguage = Language.Language1;
var results = db.MyEntities.Join(
db.StatusTranslations,
e => e.Status,
s => s.Status,
(e, s) => new { MyEntity = e, Status = s }
)
.Where(e => e.Status.Language == userLanguage)
.Select(e => new {
MyEntityID = e.MyEntity.MyEntityID
StatusName = e.Status.Name
}).OrderBy(e => e.StatusName).ToList();
I never compiled this example code (only my own code) so I apologise for any mistakes, but it should give the idea well enough.

Related

Dapper always returning false in case of fetching bit datatype from SQL SERVER 2014 database

I am using Dapper version="2.0.78" for .NETFramework = 4.7.2 based asp.net web api2 application.
Here goes my POCO class :
public class TestGroupResult : APIResult
{
public TestGroupVM TestGroup
{
get;
set;
}
}
public class TestGroupVM
{
public int TestGroupId
{
get;
set;
}
public string GroupName
{
get;
set;
}
public bool IsTestPublishedAndOnboarded
{
get;
set;
}
public bool IsPublished
{
get;
set;
}
public CountryVM Country
{
get;
set;
}
}
public async Task<TestGroupResult> GetDataAsync(RequestDTO value, string locale)
{
var localeLangId = _commonService.GetLanguageFromLocale(locale).LanguageId;
var result = new TestGroupVM();
var cgresult = new TestGroupResult();
using (var conn = await _dapperService.CreateConnection())
{
var data = await conn.QueryAsync<TestGroupVM, CountryVM, TestGroupVM>("stp_FetchData", (cg, c) =>
{
cg.Country = c;
return cg;
}, splitOn: "Uuid", param: new
{
#pI_NCOUNTRYCODE = value.Code, #pI_ILOCALELANGID = localeLangId
}
, commandType: CommandType.StoredProcedure);
result = data.FirstOrDefault();
var countryVM = new CountryVM{Uuid = Convert.ToString(result.Country.Uuid), DisplayName = result.Country.DisplayName, DisplayNameShort = result.Country.DisplayNameShort, Name = Helper.ReplaceChars(result.Country.DisplayName)};
result.Country = countryVM;
cgresult.CountryGroup = result;
}
return cgresult;
}
stp_FetchData:
SELECT CGC.TestGroupId, CGC.GroupName, C.CountryId AS Uuid, CC.DisplayName,CC.DisplayNameShort,case when C.IsPublished = 1 then 1 else 0 end AS IsTestPublishedAndOnboarded, C.IsPublished As IsPublished
FROM [Countries] C join [CountryContents] CC ON CC.CountryId = C.CountryId
join [CountryGroupAssociatedCountries] CGAC on C.CountryId = CGAC.CountryId
join [CountryGroupContents] CGC on CGC.CountryGroupId = CGAC.CountryGroupId where C.CountryCode=#pI_NCOUNTRYCODE AND CC.LanguageId IN (#pI_ILOCALELANGID)
On testing the above stored procedure I see the below result :
TestGroupId : 5
GroupName: ABC
Uuid : 12
DisplayName : Test
DisplayNameShort = Testing
IsTestPublishedAndOnboarded : 1
IsPublished : 1
But the method : GetDataAsync always returns
TestGroupId : 5
GroupName: ABC
Uuid : 12
DisplayName : Test
DisplayNameShort = Testing
IsTestPublishedAndOnboarded : false
IsPublished : false
I am not sure why column type: Bit in SQL Server are not getting mapped to bool datatype in C# in this case.
Can anyone help me here by providing their guidance to fix this issue?
The problem here is in the column ordering, the components are the type structure here:
conn.QueryAsync<TestGroupVM, CountryVM, TestGroupVM>(...)
And note the splitOn: "Uuid".
The dapper args are: <Type1, Type2, ReturnType>. In the query your columns are, in order:
CGC.TestGroupId (Type1: TestGroupVM)
CGC.GroupName (Type1: TestGroupVM)
C.CountryId AS Uuid (Type2: CountryVM - note: at this point, we switch to populating the CountryVM object, because that's what splitOn said to do)
CC.DisplayName (Type2: CountryVM)
CC.DisplayNameShort (Type2: CountryVM)
case when C.IsPublished = 1 then 1 else 0 end AS IsTestPublishedAndOnboarded (Type2: CountryVM - not a property!)
C.IsPublished As IsPublished (Type2: CountryVM - not a property!)
So overall, we're trying to populate properties on CountryVM after we've moved on to the second object...and it doesn't have those properties.
To resolve this, what you want to do is to move those 2 columns before the Uuid switch, so that it's mapping to the right place, for example:
SELECT CGC.TestGroupId, CGC.GroupName, case when C.IsPublished = 1 then 1 else 0 end AS IsTestPublishedAndOnboarded, C.IsPublished As IsPublished, C.CountryId AS Uuid, CC.DisplayName,CC.DisplayNameShort
...or, move those properties to country if that's where they belong (I'm noting a mismatch vs. your database model here - so consider that option!)

Linq to Entities: Where In with 1 value and many columns

I know that in sql you do something like this
WHERE 'val' IN (field1, field2, field3, field4, ...)
I was wondering if there is a way doing something similar using Linq to entities? The only thing I can think of right now is just to create a giant "or" statement of the fields I want to search over like the following
.where(m =>
m.field1.Contains('val') ||
m.field2.Contains('val') ||
m.field3.Contains('val') ||
m.field4.Contains('val'));
Is there a cleaner way of writing this search or is what I have as good as it gets?
You are not using Contains() properly as pointed out by Theodor Zoulias, because IN on SQL checks equality, while contains would be LIKE on SQL. also you are enclosing your string val with ' instead of ", ' only works for a single character.
Assuming you are trying to retrive "m" where any property has a certain value, you're going to have to use reflection:
First create a method to loop trough an object and match the desired value
public bool FieldSearch(object a, string b)
{
//Get the type of your object, to loop through its properties
Type t = a.GetType();
//loop and check (the loop stops once the first property that matches has been found!)
foreach(PropertyInfo p in t.GetProperties())
{
if(p.GetValue(a).ToString()==b)
{
return true;
}
}
return false;
}
Be careful with GetProperties(), you might want to add BidingAttributes because it retrieves every(public) property.
Now just use your new bool method on your linq: (not a good idea regarding performance depending on the context)
.where(m => FieldSearch(m,"val"))
Although all of this is possible, you probably have an architecture problem, because you are going to lose reference very quick, since this linq query returns any object that has that value on any field; without specifying which field.
There are probably better ways to do what you are trying to do..
You can do
.Where(f => new string[] { f.field1, f.field2, f.field3 }.Any(s => s.Contains("val")));
which have the behavior of the code you posted, or
.Where(f => new string[] { f.field1, f.field2, f.field3 }.Contains("val"));
which check for equality.
But I can't say if it's a good idea regarding performance.
Here is an example of the code:
public class ClassWithFields
{
public int Id { get; set; }
public string Field1 { get; set; }
public string Field2 { get; set; }
public string Field3 {get;set;}
}
public class Program
{
public static void Main()
{
var listFields = new List<ClassWithFields>()
{
new ClassWithFields { Id = 1, Field1 = "val", Field2 = "qewr", Field3 = "asdqw" },
new ClassWithFields { Id = 2, Field1 = "asdf", Field2 = "asdd", Field3 = "asdqw" },
new ClassWithFields { Id = 3, Field1 = "asdf", Field2 = "qewr", Field3 = "qwvaleqwe" }
};
var containsVal = listFields.Where(f => new string[] { f.Field1, f.Field2, f.Field3 }.Any(s => s.Contains("val")));
var equalsVal = listFields.Where(f => new string[] { f.Field1, f.Field2, f.Field3 }.Contains("val"));
}
}
You can run it at https://dotnetfiddle.net/lXSoB4

Filtering nested lists with nullable property

Say I have the following class structures
public class EmailActivity {
public IEnumerable<MemberActivity> Activity { get; set; }
public string EmailAddress { get; set; }
}
public class MemberActivity {
public EmailAction? Action { get; set; }
public string Type { get; set; }
}
public enum EmailAction {
None = 0,
Open = 1,
Click = 2,
Bounce = 3
}
I wish to filter a list of EmailActivity objects based on the presence of a MemberActivity with a non-null EmailAction matching a provided list of EmailAction matches. I want to return just the EmailAddress property as a List<string>.
This is as far as I've got
List<EmailAction> activityTypes; // [ EmailAction.Open, EmailAction.Bounce ]
List<string> activityEmailAddresses =
emailActivity.Where(
member => member.Activity.Where(
activity => activityTypes.Contains(activity.Action)
)
)
.Select(member => member.EmailAddress)
.ToList();
However I get an error message "CS1503 Argument 1: cannot convert from 'EmailAction?' to 'EmailAction'"
If then modify activityTypes to allow null values List<EmailAction?> I get the following "CS1662 Cannot convert lambda expression to intended delegate type because some of the return types in the block are not implicitly convertible to the delegate return type".
The issue is the nested .Where it's returning a list, but the parent .Where requires a bool result. How would I tackle this problem?
I realise I could do with with nested loops however I'm trying to brush up my C# skills!
Using List.Contains is not ideal in terms of performance, HashSet is a better option, also if you want to select the email address as soon as it contains one of the searched actions, you can use Any:
var activityTypes = new HashSet<EmailAction>() { EmailAction.Open, EmailAction.Bounce };
List<string> activityEmailAddresses =
emailActivity.Where(
member => member.Activity.Any(
activity => activity.Action.HasValue &&
activityTypes.Contains(activity.Action.Value)
)
)
.Select(activity => activity.EmailAddress)
.ToList();
You want to use All or Any depends if you want each or at least one match...
HashSet<EmailAction> activityTypes = new HashSet<EmailAction> { EmailAction.None };
var emailActivity = new List<EmailActivity>
{
new EmailActivity { Activity = new List<MemberActivity>{ new MemberActivity { Action = EmailAction.None } }, EmailAddress = "a" },
new EmailActivity { Activity = new List<MemberActivity>{ new MemberActivity { Action = EmailAction.Click } }, EmailAddress = "b" }
};
// Example with Any but All can be used as well
var activityEmailAddresses = emailActivity
.Where(x => x.Activity.Any(_ => _.Action.HasValue && activityTypes.Contains(_.Action.Value)))
.Select(x => x.EmailAddress)
.ToArray();
// Result is [ "a" ]

How to set flag in one list for the Id's which match with the Id's in another list using Lambda expression c#

I have two lists classes
public class class1{
public Int Id { get; set; }
public Bool Flag{ get; set; }
}
public class class2{
public Int Id { get; set; }
}
Now i have List<class1> and List<class2>,
Now i have to update Flag property to true in List<class1> for only those Ids which match with the Id's present in List<class2> using lambda expression c#.Don't want to use foreach.
using lambda expression. Don't want to use foreach.
That's usually a silly requirement and a hallmark that you're not really familiar with C#, Linq or performance analysis. You have a collection whose elements you want to modify, so you should use foreach().
If you're trying out functional programming, then you should treat the list elements as immutable and project into a new collection.
The first part of your problem, looking up which list elements to modify based on a presence of one of their properties in another collection's elements' properties, is trivial:
var elementsToModify = list1.Where(l1 => list2.Any(l2 => l2.Id == l1.Id));
Now with a foreach(), this'll be simple:
foreach (var l1 in elementsToModify)
{
l1.Flag = true;
}
Or, even denser (not that less code equals more performance):
foreach (var l1 in list1.Where(l1 => list2.Any(l2 => l2.Id == l1.Id)))
{
l1.Flag = true;
}
So, there's your code. But you didn't want to use foreach(). Then you need to project into a new collection:
var newList1 = list1.Where(l1 => list2.Any(l2 => l2.Id == l1.Id))
.Select(l1 => new Class1
{
Id = l1.Id,
Flag = true,
})
.ToList();
There you have it, a List<Class1> with only flagged items. Optionally you could use this list in a foreach() to update the original list1. Oh, wait.
The below solution does not use the classical "for each", but is compiled to one under the hood. If that's not what you meant, then please explain what you are trying to achieve. Using for each in this example is a good approach. One could also use while or for loops, but is it really what's being asked here?
Object definition:
public class MyObject
{
public int Id { get; set; }
public bool Flag { get; set; }
}
List initialization:
var list = new List<MyObject>()
{
new MyObject() { Id= 1 },
new MyObject() { Id= 2 },
new MyObject() { Id= 3 },
new MyObject() { Id= 4 }
};
var list2 = new List<MyObject>()
{
new MyObject() { Id= 2 },
new MyObject() { Id= 4 }
};
Code:
list.ForEach(el => el.Flag = list2.Any(el2 => el2.Id == el.Id));
EDIT:
An example with a while loop (a bit nasty to do it this way):
int i = -1;
int numberOfElements = list.Count;
while (++i < numberOfElements)
{
list[i].Flag = list2.Any(el => el.Id == list[i].Id);
}
I guess you can write a for loop yourself...

Performance mongodb query with starts-with

For a proof of concept I have loaded ~54 million records into mongodb. The goal is to investigate the query speed of mongodb.
I use the following class to store the data:
[BsonDiscriminator("Part", Required = true)]
public class Part
{
[BsonId]
public ObjectId Id { get; set; }
[BsonElement("pgc")]
public int PartGroupCode { get; set; }
[BsonElement("sc")]
public int SupplierCode { get; set; }
[BsonElement("ref")]
public string ReferenceNumber { get; set; }
[BsonElement("oem"), BsonIgnoreIfNull]
public List<OemReference> OemReferences { get; set; }
[BsonElement("alt"), BsonIgnoreIfNull]
public List<AltReference> AltReferences { get; set; }
[BsonElement("crs"), BsonIgnoreIfNull]
public List<CrossReference> CrossReferences { get; set; }
[BsonElement("old"), BsonIgnoreIfNull]
public List<FormerReference> FormerReferences { get; set; }
[BsonElement("sub"), BsonIgnoreIfNull]
public List<SubPartReference> SubPartReferences { get; set; }
}
And I created the following indexes:
Compound Index on ref, sc, pgc
Ascending Index on oem.refoem
Ascending Index on alt.refalt
Ascending Index on crs.refcrs
Ascending Index on old.refold
Ascending Index on sub.refsub
I perform the following queries to test the performance:
var searchValue = "345";
var start = DateTime.Now;
var result1 = collection.AsQueryable<Part>().OfType<Part>().Where(part => part.ReferenceNumber == searchValue);
long count = result1.Count();
var finish = DateTime.Now;
start = DateTime.Now;
var result2 = collection.AsQueryable<Part>().OfType<Part>().Where(part =>
part.ReferenceNumber.Equals(searchValue) ||
part.OemReferences.Any(oem => oem.ReferenceNumber.Equals(searchValue)) ||
part.AltReferences.Any(alt => alt.ReferenceNumber.Equals(searchValue)) ||
part.CrossReferences.Any(crs => crs.ReferenceNumber.Equals(searchValue)) ||
part.FormerReferences.Any(old => old.ReferenceNumber.Equals(searchValue))
);
count = result2.Count();
finish = DateTime.Now;
start = DateTime.Now;
var result3 = collection.AsQueryable<Part>().OfType<Part>().Where(part =>
part.ReferenceNumber.StartsWith(searchValue) ||
part.OemReferences.Any(oem => oem.ReferenceNumber.StartsWith(searchValue)) ||
part.AltReferences.Any(alt => alt.ReferenceNumber.StartsWith(searchValue)) ||
part.CrossReferences.Any(crs => crs.ReferenceNumber.StartsWith(searchValue)) ||
part.FormerReferences.Any(old => old.ReferenceNumber.StartsWith(searchValue))
);
count = result3.Count();
finish = DateTime.Now;
var regex = new Regex("^345"); //StartsWith regex
start = DateTime.Now;
var result4 = collection.AsQueryable<Part>().OfType<Part>().Where(part =>
regex.IsMatch(part.ReferenceNumber) ||
part.OemReferences.Any(oem => regex.IsMatch(oem.ReferenceNumber)) ||
part.AltReferences.Any(alt => regex.IsMatch(alt.ReferenceNumber)) ||
part.CrossReferences.Any(crs => regex.IsMatch(crs.ReferenceNumber)) ||
part.FormerReferences.Any(old => regex.IsMatch(old.ReferenceNumber))
);
count = result4.Count();
finish = DateTime.Now;
The results are not what I would have expected:
Search 1 on 345 results in: 3 records (00:00:00.3635937)
Search 2 on 345 results in: 58 records (00:00:00.0671566)
Search 3 on 345 results in: 6189 records (00:01:17.6638459)
Search 4 on 345 results in: 6189 records (00:01:17.0727802)
Why is the StartsWith query (3 and 4) so much slower?
The StartsWith query performance is the make or break decision.
Did I create the wrong indexes? Any help is appreciated.
Using mongodb with the 10gen C# driver
UPDATE:
The way the query is translated from Linq to a MongoDB query is very important for the performance. I build the same query (like 3 and 4) again but with the Query object:
var query5 = Query.And(
Query.EQ("_t", "Part"),
Query.Or(
Query.Matches("ref", "^345"),
Query.Matches("oem.refoem", "^345"),
Query.Matches("alt.refalt", "^345"),
Query.Matches("crs.refcrs", "^345"),
Query.Matches("old.refold", "^345")));
start = DateTime.Now;
var result5 = collection.FindAs<Part>(query5);
count = result5.Count();
finish = DateTime.Now;
The result of this query is returned in 00:00:00.4522972
The query translated as
command: { count: "PSG", query: { _t: "Part", $or: [ { ref: /^345/ }, { oem.refoem: /^345/ }, { alt.refalt: /^345/ }, { crs.refcrs: /^345/ }, { old.refold: /^345/ } ] } }
Compared with Query 3 and 4 the difference is big:
command: { count: "PSG", query: { _t: "Part", $or: [ { ref: /^345/ }, { oem: { $elemMatch: { refoem: /^345/ } } }, { alt: { $elemMatch: { refalt: /^345/ } } }, { crs: { $elemMatch: { refcrs: /^345/ } } }, { old: { $elemMatch: { refold: /^345/ } } } ] } }
So why is query 3 and 4 not using the indexes?
From the index documentation:
Every query, including update operations, uses one and only one index.
In other words, MongoDB doesn't support index intersection. Thus, creating a huge number of indexes is pointless unless there are queries that use this index and this index only. Also, make sure you're calling the correct Count() method here. If you call the linq-to-object extensions (IEnumerable's Count() extension rather than MongoCursor's Count, it will actually have to fetch and hydrate all objects).
It is probably easier to throw these in a single mutli-key index like this:
{
"References" : [ { id: new ObjectId("..."), "_t" : "OemReference", ... },
{ id: new ObjectId("..."), "_t" : "CrossReferences", ...} ],
...
}
where References.id is indexed. Now, a query db.foo.find({"References.id" : new ObjectId("...")}) will automatically search for any match in the array of references. Since I assume the different types of references must be distinguished, it makes sense to use a discriminator so the driver can support polymorphic deserialization. In C#, you'd declare this like
[BsonDiscriminator(Required=true)]
[BsonKnownTypes(typeof(OemReference), typeof(...), ...)]
class Reference { ... }
class OemReference : Reference { ... }
The driver will automatically serialize the type name in a field called _t. That behaviour can be adjusted to your needs, if required.
Also note that shortening the property names will decrease storage requirements, but won't affect index size.

Categories