I'm using nHibernate with c# to get a list of records or strings from the database as show in the first couple of lines of code below. This works fine. What I want to do is select a few specific fields from the record and not the entire record. I have tried various techniques and can't seem to find any examples on how to do this. Could someone have a look at the code below and let me know if I am going off in the wrong direction.
Thanks!
// THIS WORKS - Retrieve a list of my records from the table.
Ilist<MyClass> classList = db.Session.CreateQuery("FROM MyTable WHERE t.Name='AName'").List<MyClass>();
// THIS WORKS - Retrieve a list of strings from the table
IList<string> stringList = db.Session.CreateQuery("SELECT c.ConstName FROM MyTable c WHERE c.Name='AName'").List<string>();
// THIS DOES NOT WORK (RUN-TIME ERRORS). HOW CAN I SELECT ONLY A FEW FIELDS FROM EACH RECORD?
// This class contains only the records I want.
public struct MyClassB
{
private string Name;
private string Address;
public string Name
{
get { return Name; }
set { Name = value; }
}
public string Address
{
get { return Address; }
set { stationName = Address; }
}
}
IList<MyClassB> classListB = db.Session.CreateQuery("SELECT t.Name, t.Address FROM MyTable t WHERE t.Name='AName'").List<MyClassB>();
Have a look at the AliasToBeanResultTransformer - usage is demonstrated here.
You are trying to cast an Anonymous type into your MyClassB which isn't valid. Instead create a mapping for MyClassB.
Or just use:
var specificFields = db.Session.CreateQuery("SELECT t.Name, t.Address FROM MyTable t WHERE t.Name='AName'").List();
var specificFields = db.Session.CreateQuery("SELECT t.Name, t.Address FROM MyTable t WHERE t.Name='AName'").List<Tuple<string,string>>();
The objects in the list will have the two properties.
As long your class has a contructor you should be able to do the following:
IList<MyClassB> classListB = db.Session.CreateQuery("SELECT new MyClassB(t.Name, t.Address) FROM MyTable t WHERE t.Name='AName'").List<MyClassB>();
Related
I have a class called Customer that has several string properties like
firstName, lastName, email, etc.
I read in the customer information from a csv file that creates an array of the class:
Customer[] customers
I need to remove the duplicate customers having the same email address, leaving only 1 customer record for each particular email address.
I have done this using 2 loops but it takes nearly 5 minutes as there are usually 50,000+ customer records. Once I am done removing the duplicates, I need to write the customer information to another csv file (no help needed here).
If I did a Distinct in a loop how would I remove the other string variables that are a part of the class for that particular customer as well?
Thanks,
Andrew
With Linq, you can do this in O(n) time (single level loop) with a GroupBy
var uniquePersons = persons.GroupBy(p => p.Email)
.Select(grp => grp.First())
.ToArray();
Update
A bit on O(n) behavior of GroupBy.
GroupBy is implemented in Linq (Enumerable.cs) as this -
The IEnumerable is iterated only once to create the grouping. A Hash of the key provided (e.g. "Email" here) is used to find unique keys, and the elements are added in the Grouping corresponding to the keys.
Please see this GetGrouping code. And some old posts for reference.
What's the asymptotic complexity of GroupBy operation?
What guarantees are there on the run-time complexity (Big-O) of LINQ methods?
Then Select is obviously an O(n) code, making the above code O(n) overall.
Update 2
To handle empty/null values.
So, if there are instances where the value of Email is null or empty, the simple GroupBy will take just one of those objects from null & empty each.
One quick way to include all those objects with null/empty value is to use some unique keys at the run time for those objects, like
var tempEmailIndex = 0;
var uniqueNullAndEmpty = persons
.GroupBy(p => string.IsNullOrEmpty(p.Email)
? (++tempEmailIndex).ToString() : p.Email)
.Select(grp => grp.First())
.ToArray();
I'd do it like this:
public class Person {
public Person(string eMail, string Name) {
this.eMail = eMail;
this.Name = Name;
}
public string eMail { get; set; }
public string Name { get; set; }
}
public class eMailKeyedCollection : System.Collections.ObjectModel.KeyedCollection<string, Person> {
protected override string GetKeyForItem(Person item) {
return item.eMail;
}
}
public void testIt() {
var testArr = new Person[5];
testArr[0] = new Person("Jon#Mullen.com", "Jon Mullen");
testArr[1] = new Person("Jane#Cullen.com", "Jane Cullen");
testArr[2] = new Person("Jon#Cullen.com", "Jon Cullen");
testArr[3] = new Person("John#Mullen.com", "John Mullen");
testArr[4] = new Person("Jon#Mullen.com", "Test Other"); //same eMail as index 0...
var targetList = new eMailKeyedCollection();
foreach (var p in testArr) {
if (!targetList.Contains(p.eMail))
targetList.Add(p);
}
}
If the item is found in the collection, you could easily pick (and eventually modify) it with:
if (!targetList.Contains(p.eMail))
targetList.Add(p);
else {
var currentPerson=targetList[p.eMail];
//modify Name, Address whatever...
}
I've just started using Dapper and I've run into the following problem.
I want to insert a bunch of records, and return the inserted records alongside the auto-incremented id.
Using Postgres, I want to run the equivalent of this query:
INSERT INTO players (name)
VALUES ('Player1'), ('Player2'), ('Player3'), ('Player4'), ('Player5')
RETURNING id, name;
Using Dapper to run this query on a list of players and serialise back into a list of players (with the ids) I thought I could do this:
public class Player
{
public int Id { get; set; }
public string Name { get; set; }
}
var players = new List<Player> { new Player { Name = "Player1" }, new Player { Name = "Player2" }, new Player { Name = "Player3" }, new Player { Name = "Player4" }, new Player { Name = "Player5" }}
connection.Query<Player>("INSERT INTO players (name) VALUES (#Name) \r\n" +
"RETURNING id, name, tag;",
players);
This throws the following error (it's a list of players each with a name):
Parameter '#Name' referenced in SQL but not found in parameter list
I believe that Query() may not support lists of parameters, so I tried connection.Execute() instead. Execute works, but obviously it doesn't return back the inserted players with their Ids.
It is worth noting that I can do an INSERT and RETURNING like this when I insert only one value.
Does anyone know how I can do INSERT and RETURNING for multiple values like this with Dapper?
Update
I have this (somewhat dirty) solution:
var sb = new StringBuilder();
sb.Append("INSERT INTO players (name) VALUES \r\n");
var parameters = new ExpandoObject() as IDictionary<string, object>;
var values = new List<string>();
for (int i = 0; i < players.Count; i++)
{
var p = players[i];
values.Add($"(#Name{i})");
parameters[$"Name{i}"] = p.Name;
}
sb.Append(string.Join(", \r\n", values));
sb.Append(" \r\nRETURNING id, name, tag;");
// parameters = { Name1 = "Player1", Name2 = "Player2, ... etc}
var ret = connection.Query<Player>(sb.ToString(), parameters);
So building an ExpandoObject with properties from my Players and then passing that into Dapper Query(). It works, but it seems pretty dirty. Any suggestions on how to improve this?
Firstly, it should be noted that passing a List<Player> to the Execute method as the outermost parameter is essentially the same as:
foreach(var player in players)
connection.Execute(
"INSERT INTO players (name) VALUES (#Name) \r\n" +
"RETURNING id, name, tag;", player);
Dapper just unrolls it for you (unless it is a very specific async scenario where it can pipeline the commands). Dapper does support list-parameter expansion, but this is for leaf-level values, and was constructed for in (...) usage, so the syntax would not come out quite as you want; as an example:
DateTime dateStart = ...
int[] custIds = ...
var orders = conn.Query<Order>(#"
select * from Order
where OrderDate >= #dateStart and CustomerId in #custIds",
new { dateStart, custIds }).AsList();
which becomes the SQL:
select * from Order
where OrderDate >= #dateStart and CustomerId in (#custIds0, #custIds1, ...)
(depending on the number of items in the array)
Your expected usage is one that has been suggested and discussed quite a bit recently; at the current time it isn't supported - the loop unrolling only works for Execute, however, it is looking increasingly likely that we will add something here. The tricky bit is in deciding what the correct behavior is, and whether it is expected that this would essentially concatenate the results of multiple separate operations.
However; to do what you want via LINQ:
var results = players.SelectMany(
player => connection.Query<Player>("...", player)).AsList();
This is the same "unroll the loop and concatenate the results" behavior, except it should work.
I have a table on my Database where, aside from other columns (one of which is a UniqueIdentifier) I also have one column where I have a JSON array string with values like this (formatted):
[
{
"AttributeId": "fe153d69-8ac1-6e0c-8793-ff0000804eb3",
"AttributeValueId": "64163d69-8ac1-6e0c-8793-ff0000804eb3"
},
{
"AttributeId": "00163d69-8ac1-6e0c-8793-ff0000804eb3",
"AttributeValueId": "67163d69-8ac1-6e0c-8793-ff0000804eb3"
}
]
I then have this AttributeValuePair class which will allow me to read this data on code:
public class AttributeValuePair
{
public AttributeValuePair();
public Guid AttributeId { get; set; }
public Guid AttributeValueId { get; set; }
}
Whenever I get a list of items from this table, I want to be able to filter the resulting array based on only one AttributeValueId and get only the items where this is a match, independently of the value of any other attributes.
Since that on code, to read these attribute collection I must have a List<AttributeValuePair>, how in LINQ can I get the items where a particular AttributeValueId is present?
List<AttributeValuePair> attributeValuePairs = serializer.Deserialize<List<AttributeValuePair>>(item.Variant);
I've been lost at it for two hours already and can't seem to find an escape from this one.
EDIT
Being more clear about the problem, what I'm trying to do is, from a List<ProductVariation>, get the possible values for the attribute "Portions", when the attribute "Days" is the specified value. I'm having a lot of trouble using the serializer to build the LINQ statement.
//This code is wrong, I know, but I'm trying to show what I want
result = model.ProductVariations.Find(x, new {serializer.Deserialize<List<AttributeValuePair>>(item.Variant).Where(valuePair => valuePair.AttributeId == attributeId)});
Can you try
attributeValuePairs.Where(valuePair => valuePair.AttributeId == new Guid("SomeValue"));
The answer to this question was actually a lot simpler than previously expected:
public string SelectedVariation(string mealsAttribute, string portionsAttribute, string product)
{
Guid productId = new Guid(product);
CatalogManager catalogManager = CatalogManager.GetManager();
EcommerceManager ecommerceManager = EcommerceManager.GetManager();
RegisterOrderAccountFormModel model = new RegisterOrderAccountFormModel();
model.Product = catalogManager.GetProduct(productId);
List<ProductVariation> productVariationsCollection = catalogManager.GetProductVariations(productId).ToList();
//This is the really interesting part for the answer:
return productVariationsCollection.Where(x => x.Variant.ToLower().Contains(mealsAttribute.ToLower()) && x.Variant.ToLower().Contains(portionsAttribute.ToLower())).FirstOrDefault().Id.ToString();
}
I have this linq query I am trying to optimize. I want to replace this query with a fast constant (preferably) retrieval of the value. I thought about a twin key dictionary but I have no idea which order the fname or lname will come first. I wanted to ask here if there is a fast way to do this.
I wanted to take a list of names, search through it for fname-lname the - is the delimeter and return all that match the full name that is searched. The list of people could be moderately large.
var nameList = from p in listOfPeople
where ((p.lname+"-"+p.fname == fullName)
|| (p.fname+"-"+p.lname == fullname))
select p;
Edit: listOfPeople can be any datatype, not necessarily a list.
Here's how you can create your dictionary.
var nameLookup = new Dictionary<Tuple<string,string>, List<Person>>();
foreach(var person in listOfPeople)
{
List<Person> people = null;
var firstLast = Tuple.Create(person.fname, person.lname);
if(nameLookup.TryGetValue(firstLast, out people))
{
people.Add(person);
}
else
{
nameLookup.Add(firstLast, new List<Person> { person });
}
// If the person's first and last name are the same we don't want to add them twice.
if(person.fname == person.lname)
{
continue;
}
var lastFirst = Tuple.Create(person.lname, person.fname);
if(nameLookup.TryGetValue(lastFirst, out people))
{
people.Add(person);
}
else
{
nameLookup.Add(lastFirst, new List<Person> { person });
}
}
Then your lookup would be
// split by the delimiter to get these if needed
var fullName = Tuple.Create(firstName, lastName);
List<Person> nameList = null;
if(!nameLookup.TryGetValue(fullName, out nameList))
{
nameList = new List<Person>();
}
It's important to keep the first and last names separate or you have to pick a delimiter that will not show up the the first or last name. Hyphen "-" could be part of a first or last name. If the delimiter is guaranteed to not be part of the first or last name you can just substitute the use of the Tuple.Create(x,y) with x + delimiter + y and change the dictionary to Dictionary<string, List<Person>>.
Additionally the reason for having a List<Person> as the value of the dictionary is to handle cases like "Gary William" and "William Gary" being two different people.
In your "P" definition, which I guess it's a "People" type, I would add a "FullName" property, which will be your comparator:
public string FullName {get {return fname + "-" + lname;}}
And modify your LINQ with:
Where string.Equals(p.FullName, fullName) .
If you REALLY want to use with ANY datatype, which would include just string or even DataTable, i really don't see any better way than the way you did...
I tested with Stopwatch and this appears to be a little more effective
var nameList = from n in(
from p in listOfPeople
select new{FullName = p.fname +"-"+ p.lname}
)
where n.FullName==fullName
select n;
My Db column in a string (varchar) and i need to assign it to a int value.
I am using linq to query.Though the code compiles am getting an error at the run time .
Thanks in advance.
PFB my query :
var vlauesCap = from plan in entities.PA_RTM_CAP_Group
select new Business.PartnerProfile.LookUp
{
Id =Convert.ToInt32(plan.cap_group_code),
//(Int32)plan.cap_group_code,
Value = plan.cap_group_name
};
return vlauesCap.ToList();
The EF provider does not know how to translate Convert.ToInt() into SQL it can run against the database. Instead of doing the conversion on the server, you can pull the results back and do the conversion using linq to objects:
// the ToList() here causes the query to be executed on the server and
// the results are returned in a list of anonymous objects
var results = (from plan in entities.PA_RTM_CAP_Group
select new
{
Code = plan.cap_group_code,
Name = plan.cap_group_name
}).ToList();
// the conversion can now be done here using Linq to Objects
var vlauesCap = from r in results
select new Business.PartnerProfile.LookUp
{
Id = Convert.ToInt32(r.Code),
Value = r.Name
};
return vlauesCap.ToList();
You can't do this directly, what you can do is declare a private variable to handle your "mapped" value, and expose the unmapped property...
[Column(Name = "cap_group_code", Storage = "m_cap_group_code")]
private string m_cap_group_code;
public int cap_group_code {
get
{
return Int32.Parse(m_cap_group_code);
}
set
{
m_cap_group_code = value.ToString();
}
}
Try this:
var vlauesCap = from plan in entities.PA_RTM_CAP_Group
select new Business.PartnerProfile.LookUp
{
Id =Convert.ToInt32(plan.cap_group_code),
Convert.ToInt32(plan.cap_group_code),
Value = plan.cap_group_name
};
return vlauesCap.ToList();
Why aren't you using casting for such a purpose, which is a more effective way of achieving this.
Just replace Convert.ToInt32(plan.cap_group_code) with (int)plan.cap_group_code
Do remember, there should be a value in the string and is int, else it will show Exception. If you are not sure about it, then you can further expand the casting to use null coalesciting operator