Linq RemoveAll with ContainsList instead of AddArrayParameters - c#

How do I convert this SQL command query to Entity Framework Linq? Trying to remove a contains list.
Trying to utilize resource
public async Task<int> PurgeInventory(IEnumerable<int> ids)
{
var command = new SqlCommand("Update [dbo].[Inventory] set status = 0 where InventoryId in ({ids})");
command.AddArrayParameters("ids", ids);
return await UnitOfWork.ExecuteSqlCommandAsync(command);
}
My attempt - looking for fix syntax:
IEnumerable<int> ids = new IEnumerable<int>();
UnitOfWork.DBSet<Inventory>().Remove(x => Where(x => InventoryId.Contains(ids));
Resource: How to use parameters in Entity Framework in a "in" clause?

For large bulk delete operations it's generally better to do that as a raw SQL statement, however if you have a manageable number of rows to delete:
(fast)
IEnumerable<int> ids = getIdsToDelete();
var idRecords = ids.Select(x => new Inventory { InventoryId = x }).ToList();
using (var context = new MyDbContext())
{
foreach(var idRecord in idRecords)
{
context.Inventory.Attach(idRecord);
context.Inventory.Remove(idRecord);
}
context.SaveChanges();
}
This requires a new Context instance which will not have any Inventory records possibly loaded, and requires that the Inventory ID's selected to remove contain no duplicates. We convert the IDs into dummy Inventory entities then attach them to the DbContext and tell EF to remove them.
(thorough)
IEnumerable<int> ids = getIdsToDelete();
using (var context = new MyDbContext())
{
var idRecords = context.Inventory.Where(x => ids.Contains(x.InventoryId)).ToList();
foreach(var idRecord in idRecords)
context.Inventory.Remove(idRecord);
context.SaveChanges();
}
This one retrieves the entities from the context then removes them. In cases where you want to update related records or otherwise manage relationships, this along with remembering to .Include() those relationships would be the way.

Related

Send multiple queries to EF / SQL database

.NET 4.7.6 and Entity Framework 3.1.16
Is it possible to send multiple queries via EF or any other method when querying the database?
The problem I have is that I have to extract a lot of data from multiple tables with multiple queries for example (I have omitted unnecessary info)
var companyValues = GetCompanyValues(companyId); // DB query
var allMemberScores = GetAllMemberScores(companyId); // DB query
var currentPeriodMemberScores = GetAllMemberScores(companyId, timespan); // DB query
var previousPeriodMemberScores = GetAllMemberScores(companyId, previousTimespan); // DB query
var currentDepartments = allMemberScores.Select(x => x.Department).Distinct();
if (currentDepartments?.Any() == true)
{
foreach (var department in currentDepartments)
{
var current = currentPeriodMemberScores.Where(x => x.Department.Equals(department));
var previous = previousPeriodMemberScores.Where(x => x.Department.Equals(department));
var combinedCompanyValueScore = allMemberScores.Where(x => x.Department.Equals(department)).SelectMany(x => x.CompanyValueScores).Average(x => x.Score);
foreach (var companyValue in companyValues)
{
int? value = GetCompanyValueDifference(current, previous, companyValue); // DB query
}
int responseRate = GetResponseRate(surveysQuery, current); // DB query
}
}
but this will send a lot of requests depending on the number of departments and company values both of which are undefined in length, could be 10 could be 20+ etc.
This is an inherited project, so there is existing data etc. and redesigning the database is on the backseat.
I have previously used Elastic search in another project where I could define a search request that takes in multiple queries and give each query an id, and on the object that is returned pull it the response for each of those ids.
Is there anything like this that I could utilise? So could I build up a query and then process all the results?
Thanks in advance :)

How to get a List<anonymous type> generated by Linq outside of an using statement?

Got a query using the using statement, which means once it ends the object created gets disposed.
using (Context context = new())
{
var records = context.PoRecords
.Join(
context.PoStatuses,
record => record.PoStatus,
status => status.Id,
(record, status) => new
{
record.PoNumber,
record.PrNumber,
Status = status.Name,
}
).ToList();
}
How can I get that list outside of that using?
What I think I have to do is create a custom class for that query, declare a list of that type outside the using and fill it with the contents of the query, I've done that when I query the whole table, but, since this is joining tables I can't just declare List<CustomType> records = ...querywithjoins... because the list Linq is returning is of an anonymous type. I'd have to do that outside the using, kind of like this:
List<CustomType> records;
using (Context context = new()){
records = TheWholeQuery.ToList();
}
Another way I thought of doing it was, since I am listing the results of the query in a list view with a foreach, creating new objects of the custom type and add them into the new list, but, that means I'd have to iterate through each and every query I do, and I don't intend to do that everytime.
Is there a way to do this? Or a workaround??
You could use a tuple if you don't want to define a class although I have found in these circumstances it's usually ends up being easier to just define the class.
List<Tuple<int, int, string>> records = null;
using (Context context = new())
{
records = context.PoRecords
.Join(
context.PoStatuses,
record => record.PoStatus,
status => status.Id,
(record, status) => new Tuple<int, int, string>(
record.PoNumber,
record.PrNumber,
status.Name
)
).ToList();
}
If u don't want to remove using statement u can just convert your list with anonymous type to list of 'CustomType'
so..
List<CustomType> records;
using (Context context = new())
{
records = context.PoRecords
.Join(
context.PoStatuses,
record => record.PoStatus,
status => status.Id,
(record, status) => new CustomType()
{
PoNumber = record.PoNumber,
PrNumber = record.PrNumber,
Status = status.Name,
}
).ToList();
}

EF: Clear Collection Property without First Getting Data

I have a class called Facility. Facility has a collection property on it called Employees. I'm using the disconnected layer of EF. I want to clear the Employees collection from a specific facility, but I don't want to make two trips to the DB: (1) getting all the employees, and then (2) clearing the. How can I do this?
Here's what I've tried...
Facility f = new Facility()
{
Id = 4,
Employees = new List<Employee>()
};
context.Facilities.Attach(f);
context.Entry<Facility>(f).Collection(fac => fac.Employees).IsLoaded = true;
context.SaveChanges();
I think I'm close, but it doesn't work. Thanks for the advice.
If you want to use EF only, you're always going to need some roundtrip. In the end, EF needs to generate DELETE ... WHERE Id = x statements. How would it know the values for x without first grabbing them from the database?
But of course you can do this in a more efficient way than fetching the complete Employee objects. It's enough to get the Id values. Then you can use these Ids to create stub entities that you mark as Deleted:
var ids = context.Empoyees.Where(e => e.FacilityId == 4)
.Select(e => e.Id).ToArray();
foreach(int id in ids)
{
var emp = new Empoyee { Id = id }; // Stub entity
context.Entry(emp).State = System.Data.Entity.EntityState.Deleted;
}
context.SaveChanges();
This is pure EF. But you can also use EntityFramework.Extended. This allows you to execute a statement like
context.Empoyees.Where(e => e.FacilityId == 4)
.Delete();

Order query by int from an ordered list LINQ C#

So I am trying to order a query by an int var that is in an ordered list of the same int vars; e.g. the query must be sorted by the lists order of items. Each datacontext is from a different database which is the reason i'm making the first query into an ordered list of id's based on pet name order, only the pet id is available from the second query's data fields, Query looks like:
using (ListDataContext syndb = new ListDataContext())
{
using (QueryDataContext ledb = new QueryDataContext())
{
// Set the order of pets by name and make a list of the pet id's
var stp = syndb.StoredPets.OrderBy(x => x.Name).Select(x => x.PetID).ToList();
// Reorder the SoldPets query using the ordered list of pet id's
var slp = ledb.SoldPets.OrderBy(x => stp.IndexOf(x.petId)).Select(x => x);
// do something with the query
}
}
The second query is giving me a "Method 'Int32 IndexOf(Int32)' has no supported translation to SQL." error, is there a way to do what I need?
LINQ to SQL (EF) has to translate your LINQ queries into SQL that can be executed against a SQL server. What the error is trying to say, is that the .NET method of IndexOf doesn't have a SQL equivalent. You may be best to get your data from your SoldPets table without doing the IndexOf part and then doing any remaining ordering away from LINQ to SQL (EF).
Something like this should work:
List<StoredPet> storedPets;
List<SoldPet> soldPets;
using (ListDataContext listDataContext = new ListDataContext())
{
using (QueryDataContext queryDataContext= new QueryDataContext())
{
storedPets =
listDataContext.StoredPets
.OrderBy(sp => sp.Name)
.Select(sp => sp.PetId)
.ToList();
soldPets =
queryDataContext.SoldPets
.ToList();
}
}
List<SoldPets> orderedSoldPets =
soldPets.OrderBy(sp => storedPets.IndexOf(sp.PetId))
Note: Your capitalisation of PetId changes in your example, so you may wish to look at that.
LinqToSql can't transalte your linq statement into SQL because there is no equivalent of IndexOf() method. You will have to execute the linq statement first with ToList() method and then do sorting in memory.
using (ListDataContext syndb = new ListDataContext())
using (QueryDataContext ledb = new QueryDataContext())
{
var stp = syndb.StoredPets.OrderBy(x => x.Name).Select(x => x.PetID).ToList();
// Reorder the SoldPets query using the ordered list of pet id's
var slp = ledb.SoldPets.ToList().OrderBy(x => stp.IndexOf(x.petId));
}
You can use this, if the list size is acceptable:
using (ListDataContext syndb = new ListDataContext())
{
using (QueryDataContext ledb = new QueryDataContext())
{
var stp = syndb.StoredPets.OrderBy(x => x.Name).Select(x => x.PetID).ToList();
var slp = ledb.SoldPets.ToList().OrderBy(x => stp.IndexOf(x.petId));
// do something with the query
}
}

Detect entities which have the same children

I have two entities, Class and Student, linked in a many-to-many relationship.
When data is imported from an external application, unfortunately some classes are created in duplicate. The 'duplicate' classes have different names, but the same subject and the same students.
For example:
{ Id = 341, Title = '10rs/PE1a', SubjectId = 60, Students = { Jack, Bill, Sarah } }
{ Id = 429, Title = '10rs/PE1b', SubjectId = 60, Students = { Jack, Bill, Sarah } }
There is no general rule for matching the names of these duplicate classes, so the only way to identify that two classes are duplicates is that they have the same SubjectId and Students.
I'd like to use LINQ to detect all duplicates (and ultimately merge them). So far I have tried:
var sb = new StringBuilder();
using (var ctx = new Ctx()) {
ctx.CommandTimeout = 10000; // Because the next line takes so long!
var allClasses = ctx.Classes.Include("Students").OrderBy(o => o.Id);
foreach (var c in allClasses) {
var duplicates = allClasses.Where(o => o.SubjectId == c.SubjectId && o.Id != c.Id && o.Students.Equals(c.Students));
foreach (var d in duplicates)
sb.Append(d.LongName).Append(" is a duplicate of ").Append(c.LongName).Append("<br />");
}
}
lblResult.Text = sb.ToString();
This is no good because I get the error:
NotSupportedException: Unable to create a constant value of type 'TeachEDM.Student'. Only primitive types ('such as Int32, String, and Guid') are supported in this context.
Evidently it doesn't like me trying to match o.SubjectId == c.SubjectId in LINQ.
Also, this seems a horrible method in general and is very slow. The call to the database takes more than 5 minutes.
I'd really appreciate some advice.
The comparison of the SubjectId is not the problem because c.SubjectId is a value of a primitive type (int, I guess). The exception complains about Equals(c.Students). c.Students is a constant (with respect to the query duplicates) but not a primitive type.
I would also try to do the comparison in memory and not in the database. You are loading the whole data into memory anyway when you start your first foreach loop: It executes the query allClasses. Then inside of the loop you extend the IQueryable allClasses to the IQueryable duplicates which gets executed then in the inner foreach loop. This is one database query per element of your outer loop! This could explain the poor performance of the code.
So I would try to perform the content of the first foreach in memory. For the comparison of the Students list it is necessary to compare element by element, not the references to the Students collections because they are for sure different.
var sb = new StringBuilder();
using (var ctx = new Ctx())
{
ctx.CommandTimeout = 10000; // Perhaps not necessary anymore
var allClasses = ctx.Classes.Include("Students").OrderBy(o => o.Id)
.ToList(); // executes query, allClasses is now a List, not an IQueryable
// everything from here runs in memory
foreach (var c in allClasses)
{
var duplicates = allClasses.Where(
o => o.SubjectId == c.SubjectId &&
o.Id != c.Id &&
o.Students.OrderBy(s => s.Name).Select(s => s.Name)
.SequenceEqual(c.Students.OrderBy(s => s.Name).Select(s => s.Name)));
// duplicates is an IEnumerable, not an IQueryable
foreach (var d in duplicates)
sb.Append(d.LongName)
.Append(" is a duplicate of ")
.Append(c.LongName)
.Append("<br />");
}
}
lblResult.Text = sb.ToString();
Ordering the sequences by name is necessary because, I believe, SequenceEqual compares length of the sequence and then element 0 with element 0, then element 1 with element 1 and so on.
Edit To your comment that the first query is still slow.
If you have 1300 classes with 30 students each the performance of eager loading (Include) could suffer from the multiplication of data which are transfered between database and client. This is explained here: How many Include I can use on ObjectSet in EntityFramework to retain performance? . The query is complex because it needs a JOIN between classes and students and object materialization is complex as well because EF must filter out the duplicated data when the objects are created.
An alternative approach is to load only the classes without the students in the first query and then load the students one by one inside of a loop explicitely. It would look like this:
var sb = new StringBuilder();
using (var ctx = new Ctx())
{
ctx.CommandTimeout = 10000; // Perhaps not necessary anymore
var allClasses = ctx.Classes.OrderBy(o => o.Id).ToList(); // <- No Include!
foreach (var c in allClasses)
{
// "Explicite loading": This is a new roundtrip to the DB
ctx.LoadProperty(c, "Students");
}
foreach (var c in allClasses)
{
// ... same code as above
}
}
lblResult.Text = sb.ToString();
You would have 1 + 1300 database queries in this example instead of only one, but you won't have the data multiplication which occurs with eager loading and the queries are simpler (no JOIN between classes and students).
Explicite loading is explained here:
http://msdn.microsoft.com/en-us/library/bb896272.aspx
For POCOs (works also for EntityObject derived entities): http://msdn.microsoft.com/en-us/library/dd456855.aspx
For EntityObject derived entities you can also use the Load method of EntityCollection: http://msdn.microsoft.com/en-us/library/bb896370.aspx
If you work with Lazy Loading the first foreach with LoadProperty would not be necessary as the Students collections will be loaded the first time you access it. It should result in the same 1300 additional queries like explicite loading.

Categories