Comparing sql with c# list in query? - c#

I have a problem with comparing a value in my sql database with a list of object in my code.
The object does not exist in DB
Example of an object in the list:
{
public long CompareId
public bool HasAccess
}
I'm using SQLBuilder in c#
And then I want to make a query that says something like this:
In made up code
SELECT * FROM EntityPermission
WHERE EntityPermission.HasAccess = listOfObjects.Where(obj => obj.CompareId == EntityPermission.CompareId).HasAccess
In more made up code but with sql builder
query.WHERE("(EntityPermission.HasAccess = {0})", listOfObjects.Where(obj => obj.CompareId == EntityPermission.CompareId).HasAccess)
I'm fully aware of that I'm mixing c# and sql here, but it was the best way I could explain what I want to accomplish.
In words
I want to find the EntityPermission where the HasAccess column is equal to the HasAccess property of the object where they have the same Id.
Really thankful for all help!

I want to find the EntityPermission where the HasAccess column is equal to the HasAccess property of the object where they have the same Id.
So you have a table EntityPermissions. Every EntityPermission in this table has at least a Boolean property HasAccess, and a primary key in long property Id
Furthermore you have a list of objects, where every object has at least an CompareId and a HasAccess.
If I read your requirement correctly, you want all EntityPermissions with Id that is also a CompareId in your list, and that have equal HasAccess value.
So if your list has values:
{10, false}, {11, true}, {12, false},
And you have EntityPermissiont:
Id HasAccess
09 true don't want this one, Id is not in the list
10 true don't want this one, Id is in the list, but HasAccess incorrect
11 true I want this one: Id is in the list, HasAccess is correct
12 false I want this one: Id is in the list, HasAccess is correct
Normally you would use Where(x => y.Contains(x)) for this. The problem is that with this you can only select on one property.
var checkValues = new
{
new {CompareId = 10, HasAccess = false},
new {CompareId = 11, HasAccess = true},
new {CompareId = 12, HasAccess = false},
}
var result = dbContext.EntityPermissions.Select(entityPermission => new
{
ValueToCompare = new
{
CompareId = entityPermission.Id,
HasAccess = entityPermission.HasAccess,
},
Original = entityPermission,
})
// keep only those selected items that have a ValueToCompare in CheckValues
.Where(selectedItem => checkValues.Contains(selectedItem.ValueToCompare)
// from the remaining items, extract the original EntityPermission
.Select(selectedItem => selectedItem.Original);

What you're looking for is the SQL WHERE...IN () syntax.
If you're using a tool that produces SQL, what you want to do is something like this:
1) get the list of values you want to compare
2) Create a string like the following from them:
"('value1','value2','value3')"
3) then produce a query that looks like this:
SELECT * FROM EntityPermission
WHERE EntityPermission.HasAccess
IN ('value1','value2','value3')
for an ORM like Entity Framework, NHibernate, etc, you can do the following:
var results = db.EntityPermissions
.Where(x => listOfObjects
.Where(obj => obj.CompareId == EntityPermission.CompareId)
.Select(y => y.HasAccess)
.Contains(x.HasAccess))

You could do this with a table-valued-parameter and user-defined-type, then inner join:
SELECT * FROM EntityPermission ep
INNER JOIN #foo f ON f.Id = ep.Id AND f.HasAccess = ep.HasAccess
However: UDTs and TVPs are really very awkward to work with; frankly, I'd be tempted to just create two concatenated strings:
string with = string.Join(",", list.Where(x => x.HasAccess).Select(x => x.Id));
string without = string.Join(",", list.Where(x => !x.HasAccess).Select(x => x.Id));
and pass that down as parameters to use with string_split:
SELECT *
FROM EntityPermission
WHERE (Id in (select value from string_split(#with, ',')) and HasAccess = 1)
OR (Id in (select value from string_split(#without, ',')) and HasAccess = 0)

You can first get your sql query in a result table then use LINQ to get your intended values. I know it is not most effective way but it could work.
public virtual List<YOUR_DTO> ExampleOperation(YOUR_DTO dto)
{
sqlText="SELECT * FROM EntityPermission ";
dbComm = db.GetSqlStringCommand(sqlText);
DataTable table = this.Database.ExecuteDataSet(dbComm).Tables[0];
List<YOUR_DTO> result = new List<YOUR_DTO>();
foreach (DataRow row in table.Rows)
{
result.Add(new YOUR_DTO()
{
...
});
}
//LINQ
result = result.Where(obj => obj.CompareId == EntityPermission.CompareId).HasAccess;
return result;
}

Related

foreach first list and see if it exists in the 2nd list and if it does then get other values from 2nd list

foreach first list by using ID and ID_SCH and see if it exists in the 2nd list and if it does then get other values from 2nd list.
string getRecords = "SELECT .....";
List <Records> firstList = ReadAll(getRecords, reader => {
return new Records(
reader.ReadByName(NameRecord.ID, string.Empty),
reader.ReadByName(NameRecord.ID_SCH, string.Empty)
);
});
string getAllRecords = "SELECT .....";
List <Records> secondList = ReadAll(getAllRecords, reader => {
return new Records(
reader.ReadByName(NameRecord.ID, string.Empty),
reader.ReadByName(NameRecord.ID_SCH, string.Empty),
reader.ReadByName(NameRecord.BSID, string.Empty),
reader.ReadByName(NameRecord.BSID_SCH, string.Empty),
);
});
// currently I am able to use id only. But I would like to include `id` and `id_sch` as well in the below statement and then get the value of `BSID` and `BSID_SCH`.
var aa= data.Select(l1 => l1.Id).Intersect(secondList .Select(l2 => l2.Id)).ToList();
Acceptance criteria
1.foreach test in the first list see if it exists in the 2nd list. some how I managed to use idto get the result but I would like to useid_sch` as well.
if it does, get the tests that are excluded from 2nd list like BSID and BSID_SCH
after getting the BSID and BSID_SCH value from acceptance criteria 2, need to check if these BSID and BSID_SCH value exist in firstlist
If it exists in the first list then how to get the value of id idsch from first list.
You can use tuples to combine the two values. In a first step we add the values of the first list into a HashSet<T>, so that we can test whether an item exists fast and easily.
var l1Exclude = data
.Select(l1 => (l1.Id, l1.id_sch))
.ToHashSet();
var l1Include = data
.Select(l1 => (l1.BSID, l1.BSID_SCH))
.ToHashSet();
Now, you can use this result to filter the second list with of all its properties
IEnumerable<Records> result = secondList
.Where(l2 => l1Include.Contains((l2.BSID, l2.BSID_SCH)) &&
!l1Exclude.Contains((l2.Id, l2.id_sch)));
But a fundamental question is, whether it would not be easier and faster to perform this logic in SQL directly yielding the expected result. Something like this
SELECT b.*
FROM
Table2 b
INNER JOIN Table1 a
ON b.BSID = a.BSID AND b.BSID_SCH = a.BSID_SCH
WHERE
NOT EXISTS (SELECT *
FROM Table1 aa
WHERE aa.Id = b.Id AND aa.IdSch = b.IdSch)

C# list sort with OR condition

I'm trying to sort a list that comes from my database. If all fields in my OrderFunds column are null, I want to sort by another column. Can anyone tell me how to do this?
This is my code:
List<Fund> funds = await fundSupervisor.List().Where(fund => fund.IsActive)
.OrderBy(fund => fund.OrderFunds)
Or, my OrderBy clause can allow null values. That would help me too.
This is the solution
List<Fund> funds = fundSupervisor
.Where(w => w.IsActive)
.OrderBy(w => w.OrderFunds)
.ThenBy(w => w.OtherField).ToList();
ThenBy() allows you specify other fields to sort your list.
If you need to sort totally different if, and only if all columns are null, you need to check that in advance.
List<Fund> funds = await fundSupervisor.List().Where(fund => fund.IsActive)
if(funds .Any(item => item.OrderFunds != null))
funds = funds.OrderBy(fund => fund.OrderFunds);
else
funds = funds.OrderBy(fund => fund.SomethingElse);
Otherwise, if you want to sort by another value if OrderFunds is null, then you can simply use a condition inside the OrderBy.
var orderedFunds = funds.OrderBy(fund => fund.OrderFunds ?? SomeOtherValue);
If you want to sort by another value as secondary sorting, use a ThenBy. That sorts all values by the ThenBy-value if there are multiple same values in the OrderBy-operation.
var orderedThenByFunds = funds.OrderBy(fund => fund.OrderFunds).ThenBy(fund => fund.SomeOtherValue);
Consider that these lines are different:
Say you have 3 items (assumed OrderFunds is int?:
Fund1 { OrderFunds = 1, SomeOtherValue = 8 }
Fund2 { OrderFunds = 3, SomeOtherValue = 6 }
Fund3 { OrderFunds = null, SomeOtherValue = 4 }
Fund4 { OrderFunds = null, SomeOtherValue = 2 }
Then orderedFunds would return Fund1(1), Fund4(2[SomeOtherValue]), Fund2(3), Fund3(4[SomeOtherValue]) and orderedThenByFunds would return Fund1(1,8), Fund2(3,8), Fund4(null,2), Fund3(null,2). The values in braces are the values ordered by.

Operation Intersect with linq

Sorry for strange title of the question, but I don't know how to formulate it more short. If you know how to formulate it better, I will be glad if you edit my question.
So, I have the following table:
I'm tolking about CustomerId and EventType fields. The rest is not important. I think you understand that this table is something like log by customers events. Some customer make event - I have event in the table. Simple.
I need to choice all customers events where each customer had event with type registration and type deposit. In other words, customer had registration before? The same customer had deposit? If yes and yes - I need to select all events of this customer.
How I can do that with the help of LINQ?
So I can write SQL like
select *
From "CustomerEvents"
where "CustomerId" in (
select distinct "CustomerId"
from "CustomerEvents"
where "EventType" = 'deposit'
intersect
select distinct "CustomerId"
from "CustomerEvents"
where "EventType" = 'registration'
)
It works, but how to write it on LINQ?
And second question. SQL above works, but not it is not universal. What if tomorrow I will need to show events of customers who have registration, deposit and - new one event - visit? I have to write new one query. Like:
select *
From "CustomerEvents"
where "CustomerId" in (
select "CustomerId"
from "CustomerEvents"
where "EventType" = 'deposit'
intersect
select distinct "CustomerId"
from "CustomerEvents"
where "EventType" = 'registration'
intersect
select distinct "CustomerId"
from "CustomerEvents"
where "EventType" = 'visit'
)
Uncomfortable :(
As source data, I have List with event types. Is there some way to make it dynamically? I mean, I have new one event in the list - I have new one intersect.
P.S I use Postgres and .NET Core 3.1
Update
I pine here a scheme
I haven't tested to see if this will translate to SQL correctly, but if we assume ctx.CustomerEvents is DbSet<CustomerEvent> you could try this:
var targetCustomerIds = ctx
.CustomerEvents
.GroupBy(event => event.CustomerId)
.Where(grouped =>
grouped.Any(event => event.EventType == "deposit")
&& grouped.Any(event => event.EventType == "registration"))
.Select(x => x.Key)
.ToList();
and then select all events for these customers:
var events = ctx.CustomerEvents.Where(event => targetCustomerIds.Contains(event.CustomerId));
To get targetCustomerIds dynamically with a variable number of event types, you could try this:
// for example
var requiredEventTypes = new [] { "deposit", "registration" };
// First group by customer ID
var groupedByCustomerId = ctx
.CustomerEvents
.GroupBy(event => event.CustomerId);
// Then filter out any grouping which doesn't satisfy your condition
var filtered = GetFilteredGroups(groupedByCustomerId, requiredEventTypes);
// Then select the target customer IDs
var targetCustomerIds = filtered.Select(x => x.Key).ToList();
// Finally, select your target events
var events = ctx.CustomerEvents.Where(event =>
targetCustomerIds.Contains(event.CustomerId));
You can define the GetFilteredGroups method like this:
private static IQueryable<IGrouping<int, CustomerEvent>> GetFilteredGroups(
IQueryable<IGrouping<int, CustomerEvent>> grouping,
IEnumerable<string> requiredEventTypes)
{
var result = grouping.Where(x => true);
foreach (var eventType in requiredEventTypes)
{
result = result.Where(x => x.Any(event => event.EventType == eventType));
}
return result;
}
Alternatively, instead of selecting the target customer IDs, you can try to directly select your target events from the filtered groupings:
// ...
// Filter out any grouping which doesn't satisfy your condition
var filtered = GetFilteredGroups(groupedByCustomerId, requiredEventTypes);
// Select your events here
var results = filtered.SelectMany(x => x).Distinct().ToList();
Regarding the inability to translate the query to SQL
Depending on your database size and particularly on the size of CustomerEvents table, this solution may or may not be ideal, but what you could do is load the optimized collection to memory and perform the grouping there:
// for example
var requiredEventTypes = new [] { "deposit", "registration" };
// First group by customer ID, but load into memory
var groupedByCustomerId = ctx
.CustomerEvents
.Where(event => requiredEventTypes.Contains(event.EventType))
.Select(event => new CustomerEventViewModel
{
Id = event.Id,
CustomerId = event.CustomerId,
EventType = event.EventType
})
.GroupBy(event => event.CustomerId)
.AsEnumerable();
// Then filter out any grouping which doesn't satisfy your condition
var filtered = GetFilteredGroups(groupedByCustomerId, requiredEventTypes);
// Then select the target customer IDs
var targetCustomerIds = filtered.Select(x => x.Key).ToList();
// Finally, select your target events
var events = ctx.CustomerEvents.Where(event =>
targetCustomerIds.Contains(event.CustomerId));
You will need to create a type called CustomerEventViewModel like this (so you don't have to load the entire CustomerEvent entity instances to memory):
public class CustomerEventViewModel
{
public int Id { get; set; }
public int CustomerId { get; set; }
public string EventType { get; set; }
}
And change the GetFilteredGroups like this:
private static IEnumerable<IGrouping<int, CustomerEvent>> GetFilteredGroups(
IEnumerable<IGrouping<int, CustomerEvent>> grouping,
IEnumerable<string> requiredEventTypes)
{
var result = grouping.Where(x => true);
foreach (var eventType in requiredEventTypes)
{
result = result.Where(x => x.Any(event => event.EventType == eventType));
}
return result;
}
It should now work fine.
Thank you for #Dejan Janjušević. He is excpirienced developer. But it seems EF can't translate him solution to SQL (or just my hands grow from wrong place). I publish here my solution for this situation. It's simple stuped. So. I have in the table EventType. It is string. And I have from the client the following filter request:
List<string> eventType
Just list with event types. So, in the action I have the following code of the filter:
if (eventType.Any())
{
List<int> ids = new List<int>();
foreach (var e in eventType)
{
var customerIdsList =
_context.customerEvents.Where(x => x.EventType == e).Select(x => x.CustomerId.Value).Distinct().ToList();
if (!ids.Any())
{
ids = customerIdsList;
}
else
{
ids = ids.Intersect(customerIdsList).ToList();
}
}
customerEvents = customerEvents.Where(x => ids.Contains(x.CustomerId.Value));
}
Not very fast, but works.

convert rows to column in entity framwork

how can i convert rows to column in entity framework!?
i have a result like this:
and i want this result:
my entity code i this :
(from loanPerson in context.LoanPersons.AsParallel()
join warranter in context.Warranters.AsParallel() on loanPerson.Id equals warranter.LoanPersonId
where loanPerson.Id == 84829
select new
{
loanPersonId = loanPerson.Id,
waranterId = warranter.WarranterPersonID,
}).ToList();
and number of the row always less than 3 and i want to have 3 column.
please let me know your answer.
tanks.
This query will return the only one row, where waranterIds will contain, at this particular case, three WarranterPersonID values, also this field is of List<int> type, because it's quantity not known at compile time:
var answer = (from loanPerson in context.LoanPersons.Where(x => x.Id == 84829)
join warranter in context.Warranters
on loanPerson.Id equals warranter.LoanPersonId
group warranter by loanPerson.Id into sub
select new
{
loanPersonId = sub.Key,
waranterIds = sub.Select(x => x.LoanPersonId).ToList()
//if you sure, that quantity equals 3,
//you can write this code instead of waranterIds:
//zamen1 = sub.Select(x => x.LoanPersonId).First(),
//zamen2 = sub.Select(x => x.LoanPersonId).Skip(1).First(),
//zamen3 = sub.Select(x => x.LoanPersonId).Skip(2).First()
}).ToList();

Linq Select Clause w/ Unknown Number of Fields

I have a linq query in which I need to be able to select an variable number of fields from a datatable. I do know all of the fields that could be included, but only two will for sure be in the datatable. I also will know which fields are included in the datatable (it will just be different depending on the user's selections). Right now I set up something like this:
var query = from item in dt.AsEnumerable()
group item by item.Field<string>("ID") into g
select new
{
ID = g.Key, //required
Status = g.Min(i => dostuff(i,"Status")), //not required
Disc = g.Min(i => dostuff(i,"Disc")), //not required
Loc = String.Join<string>(",", from i in g select i.Field<string>("Loc")) //required
};
dostuff(DataRow i,string field)
{
try
{
return i.Field<string>(field);
}
catch
{
return null;
}
}
So dostuff basically is just checking whether or not that field exists in the dataset, and then I would just need to ignore the non-existant fields when working with the query results, which would not be too difficult. However, it seems like there is probably a better way to do this, but I've had a tough time finding anything via Google about using a dynamic select clause.
You could do it with dynamic type (nb, I did not test so this might have typos.):
var query =dt.AsEnumerable().GroupBy(item => item.Field<string>("ID"))
.Select(g => {
dynamic t = new System.Dynamic.ExpandoObject();
if (g.Table.Columns.Any(c => c.ColumnName == "Status"))
t.Status = g.Field<string>("Status");
if (g.Table.Columns.Any(c => c.ColumnName == "Disc"))
t.Disc = g.Field<string>("Disc");
t.ID = g.Key;
t.Loc = String.Join<string>(",",g.Select(i => i.Field<string>("Loc")));
return t;
}

Categories