Arbitrary Length ORDER BYs using NHibernate - c#

If I were doing this using PHP and MySQL, it would look something like this (disclaimer that this PHP code is not suitable for external/web-facing use, as it's vulnerable to SQL injection):
<?php
function orderByColumns ($columns, $sql) {
if (0 < count($columns)) {
$column = array_shift($columns);
if (! stripos($sql, "ORDER BY")) {
$sql .= " ORDER BY";
}
$sql .= " {$column['name']} {$column['dir']}";
$sql .= 0 < count($columns) ? "," : "";
return orderByColumns($columns, $sql);
}
return $sql;
}
$columns = array(
array(
"name" => "foo",
"dir" => "ASC"
),
array(
"name" => "bar",
"dir" => "DESC"
)
);
$sql = "SELECT * FROM baz";
$sql = orderByColumns($columns, $sql); // And from here I could make my query
The point is that $columns is to be an input from a user somewhere, and that that could be used to order the columns without knowing the list in advance, and in a method that is reusable.
I'm looking for a way to do something similar using C# and specifically NHibernate, but it doesn't really seem to work. Here is something along the lines of what I've been trying in C#:
List<string> columns = new List<string>()
{
"Column1",
"Column2",
"Column3"
// And there could be more.
}
string column = columns.First();
fq = foo.Queryable.OrderBy(
i => i.GetType().GetProperty(column).GetValue(i, null)
);
foreach (string column in columns)
{
fq = fq.ThenBy(
i => i.GetType().GetProperty(column).GetValue(i, null)
);
}
And, I've looked at a few StackOverflow answers (ok, more than a few), but they don't seem to be addressing how to build NHibernate queries dynamically in the way I'm looking for. The one that felt most promising is Dynamic QueryOver in nHibernate, but I'm having a hard time fully grokking whether that's even in the right direction.

So, the problem where is that you aren't executing anything at this point, so nhibernate is going to try to translate that to SQL, which is going to complain because it doesn't know about the GetType() method.
You'd have to build up your own Expression instance, and there aren't great ways of doing that dynamically, though it can be done, but still not fun to do.
I think it'd be easier to make a dictionary of lambda expressions and columns
var lookup = new Dictionary<string, Expression<Func<T, object>>> {
{ "ColumnA", x => x.ColumnA },
{ "ColumnB", x => x.ColumnB }
};
foreach (string column in columns) {
fq = fq.ThenBy(lookup[column]);
}
Even then, this might not work if it complains about Expression<Func<T,object>>

I was intrigued by this question and wanted to take a crack at making #DarrenKopp's answer generic. My code got more long-winded than I expected, but I believe it does work. I tested with Linq to Objects, so nHibernate's Linq provider is untested.
The code is available here.
You can call to it with something like this...
var sortedItems = items.OrderBy(
new OrderByKeyInfo ("MyPropertyA", OrderByDirection.Descending),
new OrderByKeyInfo ("MyPropertyB", OrderByDirection.Ascending),
new OrderByKeyInfo ("MyPropertyC", OrderByDirection.Ascending));

Here's a quick proof of concept around dynamic sort conditions. You might find that avoiding trips to the database via NHibernate may be better, as it may be confusing to the user if the initial sort contains, for example, 8 records, but sorting the data again returns 9, as a new record was added in between and is now displayed as we've gone back to the DB rather than just re-sorting the in-memory collection - and I'm not sure if/how this would map to NHibernate anyway.
This is a quick and dirty solution for a console application, simply to prove that it'll work, there'll be a few tweaks and optimisations available no doubt. Ultimately, the overload
List<T>.Sort(Comparison<T>)
is the one that will prevent having to dynamically create a class that implements IComparer of T:
class Program
{
private class Person
{
public string Name { get; set; }
public int Age { get; set; }
public int NumberOfChildren { get; set; }
}
private static List<Person> people = new List<Person>()
{
new Person() { Name="Andrew", Age=35, NumberOfChildren=3},
new Person() { Name="Maria",Age=33,NumberOfChildren=3},
new Person() {Name="Tim",Age=67,NumberOfChildren=4},
new Person() {Name="Tim",Age=62,NumberOfChildren=2},
new Person() {Name="Jim", Age=67,NumberOfChildren=2},
new Person() {Name="Tim",Age=33,NumberOfChildren=0},
new Person() {Name="Bob",Age=35,NumberOfChildren =3},
new Person() {Name="Daisy",Age=1,NumberOfChildren=0}
};
static void Main(string[] args)
{
List<string> sortConditions = new List<string>() { "Age", "Name", "NumberOfChildren" };
var properties = GetSortProperties<Person>(sortConditions);
people.Sort((Person a, Person b) =>
{
int result = 0;
foreach (PropertyInfo prop in properties)
{
result = ((IComparable)prop.GetValue(a, null)).CompareTo(prop.GetValue(b, null));
if (result != 0)
break;
}
return result;
});
}
static List<PropertyInfo> GetSortProperties<T>(List<string> propertyNames)
{
List<PropertyInfo> properties = new List<PropertyInfo>();
var typeProperties = typeof(T).GetProperties();
foreach (string propName in propertyNames)
{
properties.Add(typeProperties.SingleOrDefault(tp => tp.Name == propName));
}
return properties;
}
}

Related

Replace property values in a class from List<Dictionary> values

I have a method that takes a List<Dictionary<string,object>> as a parameter. The plan is to use that parameter, but only update the values held in a particular class. Here is the (partially written) method
public async Task<Errors> UpdatePageForProject(Guid projectId, List<Dictionary<string, object>> data)
{
if (!IsValidUserIdForProject(projectId))
return new Errors { ErrorMessage = "Project does not exist", Success = false };
if (data.Count == 0)
return new Errors { ErrorMessage = "No data passed to change", Success = false };
var page = await _context.FlowPages.FirstOrDefaultAsync(t => t.ProjectId == projectId);
foreach (var d in data)
{
}
return new Errors { Success = true };
}
My original plan is to take each dictionary, check if the key and the property in page match and then alter the value (so I can pass in 1 dictionary or 8 dictionaries in the list and then alter page to save back to my entity database).
I'd rather not use reflection due to the speed hit (though C#9 is really fast, I'd still rather not use it), but I'm not sure how else this can be done. I did consider using AutoMapper to do this, but for now would rather not (it's a PoC, so it is possibly overkill)
If you want to do this without Reflection (which I agree is a good idea, not just for performance reasons) then you could use a "map" or lookup table with actions for each property.
var map = new Dictionary<string,Action<Page,object>>()
{
{ "Title", (p,o) => p.Title = (string)o },
{ "Header", (p,o) => p.Field1 = (string)o },
{ "DOB", (p,o) => p.DateOfBirth = (DateTime)o }
};
You can then iterate over your list of dictionaries and use the map to execute actions that update the page.
foreach (var dictionary in data)
{
foreach (entry in dictionary)
{
var action = map[entry.Key];
action(page, entry.Value);
}
}

Given a list of several of the same object, group and combine them based on field value

Sorry for the incoherent title. I don't know how to concisely explain my problem, which is why I didn't really know how to look it up. I'll explain using an example...
Let's say I have a class:
public class cas
{
public string name { get; set; }
public int num { get; set; }
}
With that class, I make several objects and stick them into a list. For the sake of example, I will make 4:
var list = new List<cas>
{
new cas { name = "firstname", num = 1 },
new cas { name = "firstname", num = 2 },
new cas { name = "lastname", num = 3 },
new cas { name = "lastname", num = 4 }
};
Is there a way to take this List and combine any objects with the same name field?
So, the new list would be 1 object with:
name = "firstname", num = 3,
name = "lastname", num = 7
There's the obvious "long" way to do it, but it would be clunky and expensive (go through the list several times to find like-objects). I was wondering if I was missing any clean way of doing it. I intentionally made a simple example so that the answer would be a proof of concept rather than writing my code for me. My actual problem is more complex than this, but I can't figure out this one aspect of it.
Using Linq, you have a GroupBy Method and a Select Method:
list = list.GroupBy(x=> x.name)
.Select(x=> new cas() { name = x.Key, num = x.Sum(y=> y.num) }).ToList();
Or using Elegant query-syntax:
list = (from item in list
group item by item.name into grouping
select new cas()
{
name = grouping.Key,
num = grouping.Sum(x => x.num)
}).ToList();
Note that to use these methods, you have to add using System.Linq at the top of your source file.
You can use linq, you would have to group them on name property and then sum on the num property of each group like:
var result = list.GroupBy(x=>x.name)
.Select(g=> new cas
{
name = g.Key,
num = g.Sum(x=>x.num)
});

C# - Nested Array/Data structures

Recently, I have been getting into C# (ASP.NET) and moving on from PHP. I want to achieve something like this:
mainArray (
array 1 (
'name' => 'example'
),
array 2 (
'name' => 'example2'
)
);
I know that you can use an Array in C# however, you must indicate the length of the Array before doing so which is where the problem is.
I want to loop through a Database in a Class function which returns an Array of all the columns, ie:
id, username, email.
I have tried:
public Array search_bustype(string match, string forthat)
{
db = new rkdb_07022016Entities2();
var tbl = (from c in db.tblbus_business select c).ToArray();
List<string> List = new List<string>();
int i = 0;
foreach (var toCheck in tbl)
{
if (toCheck.BusType.ToString() == match)
{
if (forthat == "Name")
{
List.Add(toCheck.Name);
}
if (forthat == "Address")
{
}
}
i++;
}
return List.ToArray();
}
But as you can see, I am having to only return the single column because the List is not multidimensional (can't be nested).
What can I use to solve this issue? I have looked at some links:
C# Arrays
StackOverflow post
But these again are an issue for my structure since I don't know how many index's I need in the Array when declaring it - The Database grows everyday.
Thanks in advance.
Try something like this. First, define a class for your business model.
public class Person
{
public string Name {get;set;}
public string Address {get;set;}
}
Then use a generic list instead of a string list.
public Person[] search_bustype(string match, string forthat)
{
var db = new rkdb_07022016Entities2();
List<Person> personList = new List<Person>();
foreach (var toCheck in db.tblbus_business.Where(b => b.BusType.ToString() == match))
{
var model = new Person { Name = toCheck.Name, Address = toCheck.Address };
personList.Add(model);
}
return personList.ToArray();
}
I'm not sure what you are trying to do with the forthat variable.
You can use a list of lists
IList<IList<string>> multiList;

Removing duplicates from a list with "priority"

Given a collection of records like this:
string ID1;
string ID2;
string Data1;
string Data2;
// :
string DataN
Initially Data1..N are null, and can pretty much be ignored for this question. ID1 & ID2 both uniquely identify the record. All records will have an ID2; some will also have an ID1. Given an ID2, there is a (time-consuming) method to get it's corresponding ID1. Given an ID1, there is a (time-consuming) method to get Data1..N for the record. Our ultimate goal is to fill in Data1..N for all records as quickly as possible.
Our immediate goal is to (as quickly as possible) eliminate all duplicates in the list, keeping the one with more information.
For example, if Rec1 == {ID1="ABC", ID2="XYZ"}, and Rec2 = {ID1=null, ID2="XYZ"}, then these are duplicates, --- BUT we must specifically remove Rec2 and keep Rec1.
That last requirement eliminates the standard ways of removing Dups (e.g. HashSet), as they consider both sides of the "duplicate" to be interchangeable.
How about you split your original list into 3 - ones with all data, ones with ID1, and ones with just ID2.
Then do:
var unique = allData.Concat(id1Data.Except(allData))
.Concat(id2Data.Except(id1Data).Except(allData));
having defined equality just on the basis of ID2.
I suspect there are more efficient ways of expressing that, but the fundamental idea is sound as far as I can tell. Splitting the initial list into three is simply a matter of using GroupBy (and then calling ToList on each group to avoid repeated queries).
EDIT: Potentially nicer idea: split the data up as before, then do:
var result = new HashSet<...>(allData);
result.UnionWith(id1Data);
result.UnionWith(id2Data);
I believe that UnionWith keeps the existing elements rather than overwriting them with new but equal ones. On the other hand, that's not explicitly specified. It would be nice for it to be well-defined...
(Again, either make your type implement equality based on ID2, or create the hash set using an equality comparer which does so.)
This may smell quite a bit, but I think a LINQ-distinct will still work for you if you ensure the two compared objects come out to be the same. The following comparer would do this:
private class Comp : IEqualityComparer<Item>
{
public bool Equals(Item x, Item y)
{
var equalityOfB = x.ID2 == y.ID2;
if (x.ID1 == y.ID1 && equalityOfB)
return true;
if (x.ID1 == null && equalityOfB)
{
x.ID1 = y.ID1;
return true;
}
if (y.ID1 == null && equalityOfB)
{
y.ID1 = x.ID1;
return true;
}
return false;
}
public int GetHashCode(Item obj)
{
return obj.ID2.GetHashCode();
}
}
Then you could use it on a list as such...
var l = new[] {
new Item { ID1 = "a", ID2 = "b" },
new Item { ID1 = null, ID2 = "b" } };
var l2 = l.Distinct(new Comp()).ToArray();
I had a similar issue a couple of months ago.
Try something like this...
public static List<T> RemoveDuplicateSections<T>(List<T> sections) where T:INamedObject
{
Dictionary<string, int> uniqueStore = new Dictionary<string, int>();
List<T> finalList = new List<T>();
int i = 0;
foreach (T currValue in sections)
{
if (!uniqueStore.ContainsKey(currValue.Name))
{
uniqueStore.Add(currValue.Name, 0);
finalList.Add(sections[i]);
}
i++;
}
return finalList;
}
records.GroupBy(r => r, new RecordByIDsEqualityComparer())
.Select(g => g.OrderByDescending(r => r, new RecordByFullnessComparer()).First())
or if you want to merge the records, then Aggregate instead of OrderByDescending/First.

How do I use LINQ Contains(string[]) instead of Contains(string)

I got one big question.
I got a linq query to put it simply looks like this:
from xx in table
where xx.uid.ToString().Contains(string[])
select xx
The values of the string[] array would be numbers like (1,45,20,10,etc...)
the Default for .Contains is .Contains(string).
I need it to do this instead: .Contains(string[])...
EDIT : One user suggested writing an extension class for string[]. I would like to learn how, but any one willing to point me in the right direction?
EDIT : The uid would also be a number. That's why it is converted to a string.
Help anyone?
spoulson has it nearly right, but you need to create a List<string> from string[] first. Actually a List<int> would be better if uid is also int. List<T> supports Contains(). Doing uid.ToString().Contains(string[]) would imply that the uid as a string contains all of the values of the array as a substring??? Even if you did write the extension method the sense of it would be wrong.
[EDIT]
Unless you changed it around and wrote it for string[] as Mitch Wheat demonstrates, then you'd just be able to skip the conversion step.
[ENDEDIT]
Here is what you want, if you don't do the extension method (unless you already have the collection of potential uids as ints -- then just use List<int>() instead). This uses the chained method syntax, which I think is cleaner, and
does the conversion to int to ensure that the query can be used with more providers.
var uids = arrayofuids.Select(id => int.Parse(id)).ToList();
var selected = table.Where(t => uids.Contains(t.uid));
If you are truly looking to replicate Contains, but for an array, here is an extension method and sample code for usage:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ContainsAnyThingy
{
class Program
{
static void Main(string[] args)
{
string testValue = "123345789";
//will print true
Console.WriteLine(testValue.ContainsAny("123", "987", "554"));
//but so will this also print true
Console.WriteLine(testValue.ContainsAny("1", "987", "554"));
Console.ReadKey();
}
}
public static class StringExtensions
{
public static bool ContainsAny(this string str, params string[] values)
{
if (!string.IsNullOrEmpty(str) || values.Length > 0)
{
foreach (string value in values)
{
if(str.Contains(value))
return true;
}
}
return false;
}
}
}
Try the following.
string input = "someString";
string[] toSearchFor = GetSearchStrings();
var containsAll = toSearchFor.All(x => input.Contains(x));
LINQ in .NET 4.0 has another option for you; the .Any() method;
string[] values = new[] { "1", "2", "3" };
string data = "some string 1";
bool containsAny = values.Any(data.Contains);
Or if you already have the data in a list and prefer the other Linq format :)
List<string> uids = new List<string>(){"1", "45", "20", "10"};
List<user> table = GetDataFromSomewhere();
List<user> newTable = table.Where(xx => uids.Contains(xx.uid)).ToList();
How about:
from xx in table
where stringarray.Contains(xx.uid.ToString())
select xx
This is an example of one way of writing an extension method (note: I wouldn't use this for very large arrays; another data structure would be more appropriate...):
namespace StringExtensionMethods
{
public static class StringExtension
{
public static bool Contains(this string[] stringarray, string pat)
{
bool result = false;
foreach (string s in stringarray)
{
if (s == pat)
{
result = true;
break;
}
}
return result;
}
}
}
This is a late answer, but I believe it is still useful.
I have created the NinjaNye.SearchExtension nuget package that can help solve this very problem.:
string[] terms = new[]{"search", "term", "collection"};
var result = context.Table.Search(terms, x => x.Name);
You could also search multiple string properties
var result = context.Table.Search(terms, x => x.Name, p.Description);
Or perform a RankedSearch which returns IQueryable<IRanked<T>> which simply includes a property which shows how many times the search terms appeared:
//Perform search and rank results by the most hits
var result = context.Table.RankedSearch(terms, x => x.Name, x.Description)
.OrderByDescending(r = r.Hits);
There is a more extensive guide on the projects GitHub page: https://github.com/ninjanye/SearchExtensions
Hope this helps future visitors
Linq extension method. Will work with any IEnumerable object:
public static bool ContainsAny<T>(this IEnumerable<T> Collection, IEnumerable<T> Values)
{
return Collection.Any(x=> Values.Contains(x));
}
Usage:
string[] Array1 = {"1", "2"};
string[] Array2 = {"2", "4"};
bool Array2ItemsInArray1 = List1.ContainsAny(List2);
I believe you could also do something like this.
from xx in table
where (from yy in string[]
select yy).Contains(xx.uid.ToString())
select xx
So am I assuming correctly that uid is a Unique Identifier (Guid)? Is this just an example of a possible scenario or are you really trying to find a guid that matches an array of strings?
If this is true you may want to really rethink this whole approach, this seems like a really bad idea. You should probably be trying to match a Guid to a Guid
Guid id = new Guid(uid);
var query = from xx in table
where xx.uid == id
select xx;
I honestly can't imagine a scenario where matching a string array using "contains" to the contents of a Guid would be a good idea. For one thing, Contains() will not guarantee the order of numbers in the Guid so you could potentially match multiple items. Not to mention comparing guids this way would be way slower than just doing it directly.
You should write it the other way around, checking your priviliged user id list contains the id on that row of table:
string[] search = new string[] { "2", "3" };
var result = from x in xx where search.Contains(x.uid.ToString()) select x;
LINQ behaves quite bright here and converts it to a good SQL statement:
sp_executesql N'SELECT [t0].[uid]
FROM [dbo].[xx] AS [t0]
WHERE (CONVERT(NVarChar,[t0].[uid]))
IN (#p0, #p1)',N'#p0 nvarchar(1),
#p1 nvarchar(1)',#p0=N'2',#p1=N'3'
which basicly embeds the contents of the 'search' array into the sql query, and does the filtering with 'IN' keyword in SQL.
I managed to find a solution, but not a great one as it requires using AsEnumerable() which is going to return all results from the DB, fortunately I only have 1k records in the table so it isn't really noticable, but here goes.
var users = from u in (from u in ctx.Users
where u.Mod_Status != "D"
select u).AsEnumerable()
where ar.All(n => u.FullName.IndexOf(n,
StringComparison.InvariantCultureIgnoreCase) >= 0)
select u;
My original post follows:
How do you do the reverse? I want to
do something like the following in
entity framework.
string[] search = new string[] { "John", "Doe" };
var users = from u in ctx.Users
from s in search
where u.FullName.Contains(s)
select u;
What I want is to find all users where
their FullName contains all of the
elements in `search'. I've tried a
number of different ways, all of which
haven't been working for me.
I've also tried
var users = from u in ctx.Users select u;
foreach (string s in search) {
users = users.Where(u => u.FullName.Contains(s));
}
This version only finds those that
contain the last element in the search
array.
The best solution I found was to go ahead and create a Table-Valued Function in SQL that produces the results, such as ::
CREATE function [dbo].[getMatches](#textStr nvarchar(50)) returns #MatchTbl table(
Fullname nvarchar(50) null,
ID nvarchar(50) null
)
as begin
declare #SearchStr nvarchar(50);
set #SearchStr = '%' + #textStr + '%';
insert into #MatchTbl
select (LName + ', ' + FName + ' ' + MName) AS FullName, ID = ID from employees where LName like #SearchStr;
return;
end
GO
select * from dbo.getMatches('j')
Then, you simply drag the function into your LINQ.dbml designer and call it like you do your other objects. The LINQ even knows the columns of your stored function. I call it out like this ::
Dim db As New NobleLINQ
Dim LNameSearch As String = txt_searchLName.Text
Dim hlink As HyperLink
For Each ee In db.getMatches(LNameSearch)
hlink = New HyperLink With {.Text = ee.Fullname & "<br />", .NavigateUrl = "?ID=" & ee.ID}
pnl_results.Controls.Add(hlink)
Next
Incredibly simple and really utlizes the power of SQL and LINQ in the application...and you can, of course, generate any table valued function you want for the same effects!
I believe that what you really want to do is:
let's imagine a scenario
you have two database
and they have a table of products in common
And you want to select products from the table "A" that id has in common with the "B"
using the method contains would be too complicated to do this
what we are doing is an intersection, and there is a method called intersection for that
an example from msdn:
http://msdn.microsoft.com/en-us/vcsharp/aa336761.aspx#intersect1
int [] numbers = (0, 2, 4, 5, 6, 8, 9);
int [] numbersB = (1, 3, 5, 7, 8);
var = commonNumbers numbersA.Intersect (numbersB);
I think what you need is easily solved with intersection
Check this extension method:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace ContainsAnyProgram
{
class Program
{
static void Main(string[] args)
{
const string iphoneAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like...";
var majorAgents = new[] { "iPhone", "Android", "iPad" };
var minorAgents = new[] { "Blackberry", "Windows Phone" };
// true
Console.WriteLine(iphoneAgent.ContainsAny(majorAgents));
// false
Console.WriteLine(iphoneAgent.ContainsAny(minorAgents));
Console.ReadKey();
}
}
public static class StringExtensions
{
/// <summary>
/// Replicates Contains but for an array
/// </summary>
/// <param name="str">The string.</param>
/// <param name="values">The values.</param>
/// <returns></returns>
public static bool ContainsAny(this string str, params string[] values)
{
if (!string.IsNullOrEmpty(str) && values.Length > 0)
return values.Any(str.Contains);
return false;
}
}
}
from xx in table
where xx.uid.Split(',').Contains(string value )
select xx
Try:
var stringInput = "test";
var listOfNames = GetNames();
var result = from names in listOfNames where names.firstName.Trim().ToLower().Contains(stringInput.Trim().ToLower());
select names;
var SelecetdSteps = Context.FFTrakingSubCriticalSteps
.Where(x => x.MeetingId == meetid)
.Select(x =>
x.StepID
);
var crtiticalsteps = Context.MT_CriticalSteps.Where(x =>x.cropid==FFT.Cropid).Select(x=>new
{
StepID= x.crsid,
x.Name,
Checked=false
});
var quer = from ax in crtiticalsteps
where (!SelecetdSteps.Contains(ax.StepID))
select ax;
string texto = "CALCA 40";
string[] descpart = texto.Split(' ');
var lst = (from item in db.InvItemsMaster
where descpart.All(val => item.itm_desc.Contains(val))
select item
).ToList();
Console.WriteLine("ITM".PadRight(10) + "DESC".PadRight(50)+"EAN".PadRight(14));
foreach(var i in lst)
{
Console.Write(i.itm_id.ToString().PadRight(10));
Console.Write(i.itm_desc.ToString().PadRight(50));
Console.WriteLine(i.itm_ean.ToString().PadRight(14));
}
Console.ReadKey();
string[] stringArray = {1,45,20,10};
from xx in table
where stringArray.Contains(xx.uid.ToString())
select xx
Dim stringArray() = {"Pink Floyd", "AC/DC"}
Dim inSQL = From alb In albums Where stringArray.Contains(alb.Field(Of String)("Artiste").ToString())
Select New With
{
.Album = alb.Field(Of String)("Album"),
.Annee = StrReverse(alb.Field(Of Integer)("Annee").ToString())
}

Categories