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.
Related
I'm creating a game in Unity3D + C#.
What I've got at the moment: an SQL datatable, consisting of 8 columns holding a total of 3 entries and a list "_WeapList" that holds every entry (as shown below).
public struct data
{
public string Name;
public int ID, dmg, range, magazin, startammo;
public float tbtwb, rltimer;
}
List<data> _WeapList;
public Dictionary<int, data>_WeapoList; //probable change
[...]
//reading the SQL Table + parse it into a new List-entry
while (rdr.Read())
{
data itm = new data();
itm.Name = rdr["Name"].ToString();
itm.ID = int.Parse (rdr["ID"].ToString());
itm.dmg = int.Parse (rdr["dmg"].ToString());
itm.range = int.Parse (rdr["range"].ToString());
itm.magazin = int.Parse (rdr["magazin"].ToString());
itm.startammo = int.Parse (rdr["startammo"].ToString());
itm.tbtwb = float.Parse(rdr["tbtwb"].ToString());
itm.rltimer = float.Parse(rdr["rltimer"].ToString());
_WeapList.Add(itm);
_WeapoList.Add(itm.ID, itm);//probable change
}
Now I want to create a "Weapon"-Class that will have the same 8 fields, feeding them via a given ID
How do I extract the values of a specific item (determined by the int ID, which is always unique) in the list/struct?
public class Weapons : MonoBehaviour
{
public string _Name;
public int _ID, _dmg, _range, _magazin, _startammo;
public float _tbtwb, _rltimer;
void Start()
{//Heres the main problem
_Name = _WeapoList...?
_dmg = _WeapoList...?
}
}
If your collection of weapons may become quite large or you need to frequently look up weapons in it, I would suggest using a Dictionary instead of a List for this (using the weapon ID as the key). A lookup will be much quicker using a Dictionary key than searching through a List using a loop or LINQ.
You can do this by modifying your code to do this as follows:
public Dictionary<int, data>_WeapList;
[...]
//reading the SQL Table + parse it into a new List-entry
while (rdr.Read())
{
data itm = new data();
itm.Name = rdr["Name"].ToString();
itm.ID = int.Parse (rdr["ID"].ToString());
itm.dmg = int.Parse (rdr["dmg"].ToString());
itm.range = int.Parse (rdr["range"].ToString());
itm.magazin = int.Parse (rdr["magazin"].ToString());
itm.startammo = int.Parse (rdr["startammo"].ToString());
itm.tbtwb = float.Parse(rdr["tbtwb"].ToString());
itm.rltimer = float.Parse(rdr["rltimer"].ToString());
_WeapList.Add(itm.ID, itm);//probable change
}
Then, to access elements on the list, just use the syntax:
_WeapList[weaponID].dmg; // To access the damage of the weapon with the given weaponID
Guarding against invalid IDs:
If there's a risk of the weaponID supplied not existing, you can use the .ContainsKey() method to check for it first before trying to access its members:
if (_WeapList.ContainsKey(weaponID))
{
// Retrieve the weapon and access its members
}
else
{
// Weapon doesn't exist, default behaviour
}
Alternatively, if you're comfortable using out arguments, you can use .TryGetValue() instead for validation - this is even quicker than calling .ContainsKey() separately:
data weaponData;
if (_WeapList.TryGetValue(weaponID, out weaponData))
{
// weaponData is now populated with the weapon and you can access members on it
}
else
{
// Weapon doesn't exist, default behaviour
}
Hope this helps! Let me know if you have any questions.
Let specificWeapon be a weapon to be searched in the list, then you can use the following code to select that item from the list of weapons, if it is not found then nullwill be returned. Hope that this what you are looking for:
var selectedWeapon = WeapList.FirstOrDefault(x=> x.ID == specificWeapon.ID);
if(selectedWeapon != null)
{
// this is your weapon proceed
}
else
{
// not found your weapon
}
You can use LINQ to search specific object through weaponId
var Weapon = _WeapList.FirstOrDefault(w=> w.ID == weaponId);
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;
I have records in two databases. That is the entity in the first database:
public class PersonInDatabaseOne
{
public string Name { get; set; }
public string Surname { get; set; }
}
That is the entity in the second database:
public class PersonInDatabaseTwo
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
How can I get records from the second database which don't exist in the first database (the first name and the last name must be different than in the first database). Now I have something like that but that is VERY SLOW, too slow:
List<PersonInDatabaseOne> peopleInDatabaseOne = new List<PersonInDatabaseOne>();
// Hear I generate objects but in real I take it from database:
for (int i = 0; i < 100000; i++)
{
peopleInDatabaseOne.Add(new PersonInDatabaseOne { Name = "aaa" + i, Surname = "aaa" + i });
}
List<PersonInDatabaseTwo> peopleInDatabaseTwo = new List<PersonInDatabaseTwo>();
// Hear I generate objects but in real I take it from database:
for (int i = 0; i < 10000; i++)
{
peopleInDatabaseTwo.Add(new PersonInDatabaseTwo { FirstName = "aaa" + i, LastName = "aaa" + i });
}
for (int i = 0; i < 10000; i++)
{
peopleInDatabaseTwo.Add(new PersonInDatabaseTwo { FirstName = "bbb" + i, LastName = "bbb" + i });
}
List<PersonInDatabaseTwo> peopleInDatabaseTwoWhichNotExistInDatabaseOne = new List<PersonInDatabaseTwo>();
// BELOW CODE IS VERY SLOW:
foreach (PersonInDatabaseTwo personInDatabaseTwo in peopleInDatabaseTwo)
{
if (!peopleInDatabaseOne.Any(x => x.Name == personInDatabaseTwo.FirstName && x.Surname == personInDatabaseTwo.LastName))
{
peopleInDatabaseTwoWhichNotExistInDatabaseOne.Add(personInDatabaseTwo);
}
};
The fastest way is dependent on the number of entities, and what indexes you already have.
If there's a few entities, what you already have performs better because multiple scans of a small set takes less than creating HashSet objects.
If all of your entities fit in the memory, the best way is to build HashSet out of them, and use Except which is detailed nicely by #alex.feigin.
If you can't afford loading all entities in the memory, you need to divide them into bulks based on the comparison key and load them into memory and apply the HashSet method repeatedly. Note that bulks can't be based on the number of records, but on the comparison key. For example, load all entities with names starting with 'A', then 'B', and so on.
If you already have an index on the database on the comparison key (like, in your case, FirstName and LastName) in one of the databases, you can retrieve a sorted list from the database. This will help you do binary search (http://en.wikipedia.org/wiki/Binary_search_algorithm) on the sorted list for comparison. See https://msdn.microsoft.com/en-us/library/w4e7fxsh(v=vs.110).aspx
If you already have an index on the database on the comparison key on both databases, you can get to do this in O(n), and in a scalable way (any number of records). You need to loop through both lists and find the differences only once. See https://stackoverflow.com/a/161535/187996 for more details.
Edit: with respect to the comments - using a real model and a dictionary instead of a simple set:
Try hashing your list into a Dictionary to hold your people objects, as the key - try a Tuple instead of a name1==name2 && lname1==lname2.
This will potentially then look like this:
// Some people1 and people2 lists of models already exist:
var sw = Stopwatch.StartNew();
var removeThese = people1.Select(x=>Tuple.Create(x.FirstName,x.LastName));
var dic2 = people2.ToDictionary(x=>Tuple.Create(x.Name,x.Surname),x=>x);
var result = dic2.Keys.Except(removeThese).Select(x=>dic2[x]).ToList();
Console.WriteLine(sw.Elapsed);
I hope this helps.
I'm trying to optimize the following method by having it return only the data my program actually needs for its calculations. In the past the dataset hasn't been that large so I haven't needed to optimize this too much. More recently, my dataset grew from about 30k records to 700k records, so I'm trying to optimize this further.
public void readRawThresholdsInList(int inputtedESourceID, DateTime maxDateTimeVal, List<int> hashKey)
{
log.Info("Reading in raw thresholds from db");
using (FAI db= new FAI ())
{
rawThresholds = (from thr in db.Thresholds
where thr.CalculatedDate == maxDateTimeVal
where thr.ErrorSourceId == inputtedESourceID
where hashKey.Contains(thr.HashKey)
select thr).ToList();
}
log.Info("Read in " + rawThresholds.Count() + " threshold records for error source id: " + inputtedESourceID + ".");
}
I got the method to return about 200k rows from 700k by adding the hashKey.Contains(thr.HashKey), but I want to take this one step further by doing the contains on a combination of 2 fields instead of just 1. The catch is that this has to happen on the DB for it to improve my runtimes. There is already post processing that only acts on the rows the program will need.
I want to be able to give the method the following as an input and have the contains (or something similar) act on the new HashKeyHostId object:
public HashKeyHostId{public int hashKey; public int hostId;}
public void readRawThresholdsInList(int inputtedESourceID, DateTime maxDateTimeVal, List<HashKeyHostId> hashKeyHostIdPairs){
}
There's probably a very simple way of doing this that I'm not thinking of.
Edit:
In response to James's comment - I don't believe this would work. The hashkey variable would now be an object containing pairs of integers instead of just 1 integer. This alone would prevent you from doing a .contains because it's no longer a primitive. It'd be the "public HashKeyHostId{public int hashKey; public int hostId;}" object I posted above. The new requirement is that a hashkey/hostId combo have to match up to 1 record in the DB.
If you're suggesting I do what Janne said (give it a list of hash keys and a list of host ids), I'm fairly certain this would return all results where the hashkey belongs to any of the hostids in the second list (aka, any combination of the two lists). I need it to only return rows with the specified combinations, not combinations of anything in the two lists.
Edit2: Sample dataset:
Hashkeys = 100,101,102,103
HostIds = 1,2,3,4,5,6,...,10000
I'd give it a list like
List<HashKeyHostId> data = new List<HashKeyHostId>()
{new HashKeyHostId(100,1),new HashKeyHostId(101,5)}
I believe the query Janne/James are suggesting would return records for any of those combinations (100,1; 100,5; 101,1; 101,5;). I need it to only return records for 100,1 and 101,5.
edit3:
I tried doing a where hashkeyHostIdpair.Any(o=> o.hashkey==thr.HashKey && o.hostid==thr.HostId)", but that errored out with the same "Unable to create a constant value of type 'RTM_DB.HashKeyHostId'. Only primitive types are supported in this context." message. It doesnt look like you can do a .Any or a .contains on a list of non-primitive types. I even tried making my own .Any with a where clause and that threw the same exception. (where hashkeyHostIdpair.Where(o=>o.hostid==thr.HostId && o.hashkey==thr.HashKey).Count()>0))
edit4: Per Josh's suggestion I tried this:
rawThresholds=fai.Thresholds.Where(o=>o.CalculatedDate==maxDateTimeVal)
.Where(o=>o.ErrorSourceId==inputtedESourceID)
.Where(o=> hashkeyHostIdpair.Contains(new HashKeyHostId(){ hashkey=o.HashKey, hostid = o.HostId})).ToList();
but it errored out with {System.NotSupportedException: Unable to create a constant value of type 'RTM_DB.HashKeyHostId'. Only primitive types ('such as Int32, String, and Guid') are supported in this context
There's probably a very simple way of doing this that I'm not thinking of.
Yeah, there is
where hashKey.Contains(someValue) && hashKey.Contains(someOtherValue)
Something along this would maybe be what you want to do?
public void readRawThresholdsInList(int inputtedESourceID, DateTime maxDateTimeVal, List<int> hashKeys, List<int> hostIds)
{
log.Info("Reading in raw thresholds from db");
using (var db = new FAI())
{
var rths = (from thr in db.Thresholds
where thr.CalculatedDate == maxDateTimeVal
&& thr.ErrorSourceId == inputtedESourceID
select thr);
if (hashKeys != null && hashKeys.Count() > 0)
rths = rths.Where(rth => hashKeys.Contains(rth.HashKey))
if (hostIds != null && hostIds.Count() > 0)
rths = rths.Where(rth => hostIds.Contains(rth.HostId)) // FieldName?
rawThresholds = rths.ToList();
}
log.Info("Read in " + rawThresholds.Count() + " threshold records for error source id: " + inputtedESourceID + ".");
}
-- edit --
You could do something like this, but I wouldnt recommend it. Figure out a value which you can multiply the HashKey safely so HostId will always be in the last digits
var filters = new int[] { 100 * 100 + 1 , 101 * 100 + 5 }; // 10001 & 10105
var rths = (from rth in db.Thresholds
where rth.CalculatedDate == maxDateTimeVal
&& rth.ErrorSourceId == inputtedESourceID
&& filters.Contains(rth.HashKey * 100 + rth.HostId)
select rth).ToList();
If you have something like
List<HashKeyHostId> data = new List<HashKeyHostId>() {
new HashKeyHostId { hashKey = 100, hostId = 1 },
new HashKeyHostId { hashKey = 101, hostId = 5 }
}
You can use it in a contains like this:
<somequery>.Where(x => data.Contains(new HashKeyHostId { hashKey = x.HashKey, hostId = x.HostId }))
Note the use of the object initializer syntax instead of a constructor.
This should get translated to SQL with each item in the list being appended to the WHERE clause like:
WHERE ([t0].[HashKey] = #p0 AND [t0].[HostId] = #p1) OR
([t0].[HashKey] = #p2 AND [t0].[HostId] = #p3) OR ...
I've stored 30,000 SimpleObjects in my database:
class SimpleObject
{
public int Id { get; set; }
}
I want to run a query on DB4O that finds all SimpleObjects with any of the specified IDs:
public IEnumerable<SimpleObject> GetMatches(int[] matchingIds)
{
// OH NOOOOOOES! This activates all 30,000 SimpleObjects. TOO SLOW!
var query = from SimpleObject simple in db
join id in matchingIds on simple.Id equals id
select simple;
return query.ToArray();
}
How do I write this query so that DB4O doesn't activate all 30,000 objects?
I am not an expert on this, and it might be good to post on the DB4O forums about it, but I think I have a solution. It involves not using LINQ and using SODA.
This is what I did. I created a quick project that populates the database with 30000 SimpleObject based on your post's definition. I then wrote a query to grab all the SimpleObjects from the database:
var simpleObjects = db.Query<SimpleObject>(typeof(SimpleObject));
When I wrapped a StopWatch around it, that run takes about 740 milliseconds. I then used your code to search for a 100 random numbers between 0 and 2999. The response was 772 ms, so based on that number I am assuming that it is pulling all the objects out of the database. I am not sure how to verify that, but later I think I proved it with performance.
I then went lower. From my understanding the LINQ provider from the DB4O team is just doing a translation into SODA. Therefore I figured that I would write a SODA query to test, and what I found was that using SODA against a property is bad for performance because it took 19902 ms to execute. Here is the code:
private SimpleObject[] GetSimpleObjectUsingSodaAgainstAProperty(int[] matchingIds, IObjectContainer db)
{
SimpleObject[] returnValue = new SimpleObject[matchingIds.Length];
for (int counter = 0; counter < matchingIds.Length; counter++)
{
var query = db.Query();
query.Constrain(typeof(SimpleObject));
query.Descend("Id").Constrain(matchingIds[counter]);
IObjectSet queryResult = query.Execute();
if (queryResult.Count == 1)
returnValue[counter] = (SimpleObject)queryResult[0];
}
return returnValue;
}
So thinking about why this would be so bad, I decided to not use an auto-implemented property and define it my self because Properties are actually methods and not values:
public class SimpleObject
{
private int _id;
public int Id {
get
{ return _id; }
set
{ _id = value; }
}
}
I then rewrote the query to use the _id private field instead of the property. The performance was much better at about 91 ms. Here is that code:
private SimpleObject[] GetSimpleObjectUsingSodaAgainstAField(int[] matchingIds, IObjectContainer db)
{
SimpleObject[] returnValue = new SimpleObject[matchingIds.Length];
for (int counter = 0; counter < matchingIds.Length; counter++)
{
var query = db.Query();
query.Constrain(typeof(SimpleObject));
query.Descend("_id").Constrain(matchingIds[counter]);
IObjectSet queryResult = query.Execute();
if (queryResult.Count == 1)
returnValue[counter] = (SimpleObject)queryResult[0];
}
return returnValue;
}
Just to make sure that it is was not a fluke, I ran the test run several times and recieved similar results. I then added another 60,000 records for a total of 90,000, and this was the performance differences:
GetAll: 2450 ms
GetWithOriginalCode: 2694 ms
GetWithSODAandProperty: 75373 ms
GetWithSODAandField: 77 ms
Hope that helps. I know that it does not really explain why, but this might help with the how. Also the code for the SODA field query would not be hard to wrap to be more generic.
If you try to run this query using LINQ it'll run unoptimized (that means that db4o are going to retrieve all objects of type SimpleObject and delegate the rest to LINQ to objects)
The best approach would be to run n queries (since the id field is indexed, each query should run fast) and aggregate the results as suggested by "Mark Hall".
You can even use LINQ for this (something like)
IList<SimpleObject> objs = new List<SimpleObject>();
foreach(var tbf in ids)
{
var result = from SimpleObject o in db()
where o.Id = tbf
select o;
if (result.Count == 1)
{
objs.Add(result[0]);
}
}
Best
I haven't done much with db4o LINQ. But you can use the DiagnosticToConsole (or ToTrace) and add it to the IConfiguration.Diagnostic().AddListener. This will show you if the query is optimized.
You don't give a lot of details, but is the Id property on SimpleObject indexed?
Once diagnostics are turned on, you might try the query like so...
from SimpleObject simple in db
where matchingIds.Contains(simple.Id)
select simple
See if that gives you a different query plan.
You could 'build' a dynamic linq query. For example the API could look like this:
First parameter: a expression which tells on which property you search
The other parameters: the id's or whatever you're searching for.
var result1 = db.ObjectByID((SimpleObject t) => t.Id, 42, 77);
var result2 = db.ObjectByID((SimpleObject t) => t.Id, myIDList);
var result3 = db.ObjectByID((OtherObject t) => t.Name, "gamlerhart","db4o");
The implementation builds a dynamic query like this:
var result = from SimpleObject t
where t.Id = 42 || t.Id==77 ... t.Id == N
select t
Since everything is combined with OR the can be evaluated directly on the indexes. It doesn't need activation. Example-Implementations:
public static class ContainerExtensions{
public static IDb4oLinqQuery<TObjectType> ObjectByID<TObjectType, TIdType>(this IObjectContainer db,
Expression<Func<TObjectType, TIdType>> idPath,
params TIdType[] ids)
{
if(0==ids.Length)
{
return db.Cast<TObjectType>().Where(o=>false);
}
var orCondition = BuildOrChain(ids, idPath);
var whereClause = Expression.Lambda(orCondition, idPath.Parameters.ToArray());
return db.Cast<TObjectType>().Where((Expression<Func<TObjectType, bool>>) whereClause);
}
private static BinaryExpression BuildOrChain<TIdType, TObjectType>(TIdType[] ids, Expression<Func<TObjectType, TIdType>> idPath)
{
var body = idPath.Body;
var currentExpression = Expression.Equal(body, Expression.Constant(ids.First()));
foreach (var id in ids.Skip(1))
{
currentExpression = Expression.OrElse(currentExpression, Expression.Equal(body, Expression.Constant(id)));
}
return currentExpression;
}
}