EF6 IN clause on specific properties - c#

Good morning,
I'm having trouble with a EF query. This is what i am trying to do.
First i am pulling a list of ID's like so (List of IDs are found in the included x.MappingAccts entity):
Entities.DB1.Mapping mapping = null;
using (var db = new Entities.DB1.DB1Conn())
{
mapping = db.Mappings.Where(x => x.Code == code).Include(x => x.MappingAccts).FirstOrDefault();
}
Later, i'm trying to do a query on a different DB against the list of Id's i pulled above (essentially a IN clause):
using (var db = new Entities.DB2.DB2Conn())
{
var accounts = db.Accounts.Where(mapping.MappingAccts.Any(y => y.Id == ?????????)).ToList();
}
As you can see i only got part way with this.
Basically what i need to do is query the Accounts table against it's ID column and pull all records that match mapping.MappingAccts.Id column.
Most of the examples i am finding explain nicely how to do this against a single dimension array but i'm looking to compare specific columns.
Any assist would be awesome.
Nugs

An IN clause is generated using a IEnumerable.Contains.
From the first DB1 context, materialize the list of Id's
var idList = mapping.MappingAccts.Select(m => m.Id).ToList();
Then in the second context query against the materialized list of id's
var accounts = db.Accounts
.Where(a => idList.Contains(a.Id))
.ToList();
The only problem you may have is with the amount of id's you are getting in the first list. You may hit a limit with the SQL query.

This will give the list of Accounts which have the Ids contained by MappingAccts
using (var db = new Entities.DB2.DB2Conn())
{
var accounts = db.Accounts.Where(s => mapping.MappingAccts.Any(y => y.Id == s.Id)).ToList();
}

Related

LINQ troubles in C# using Entity Framework

I have a few tables and this is what I need to achieve.
This gets all the rows from one table
var FRA = from prod in _cctDBContext.Fra
where prod.ActTypeId == 1
From within that, I get all the rows where ActTypeID.
Then I need to query another table from with the ID's get from that
foreach (var item in FRA)
{
var FRSA = _cctDBContext.Frsa
.Select(p => new { p.Fraid, p.Frsa1,
p.Frsaid, p.CoreId,
p.RelToEstId, p.ScopingSrc,
p.Mandatory })
.Where(p => p.Fraid == item.Fraid)
.ToList();
}
I then need to push each one of these to Entity Framework. I usually do it this way:
foreach (var item in FRA)
{
var FinanicalReportingActivity = new FinancialReportingActivity { FinancialReportingActivityId = item.Fraid, ScopingSourceType = item.ScopingSrc, Name = item.Fra1, MandatoryIndicator = item.Mandatory, WorkEffortTypeId = 0 };
_clDBContext.FinancialReportingActivity.AddRange(FinanicalReportingActivity);
}
But because I have used 2 for each loops, I cannot get the variables to work because I cannot find a way to get local variables as the entity context.
Can anyone think of a better way to code this?
Thanks
It looks like you can do this as a single join:
var query =
from prod in _cctDBContext.Fra
where prod.ActTypeId == 1
join p in _cctDBContext.Frsa on prod.Fraid equals p.Fraid
select new
{
p.Fraid,
p.Frsa1,
p.Frsaid,
p.CoreId,
p.RelToEstId,
p.ScopingSrc,
p.Mandatory
};
It looks like you are loading data from one set of entities from one database and want to create matching similar entities in another database.
Navigation properties would help considerably here. Frsa appear to be a child collection under a Fra, so this could be (if not already) wired up as a collection within the Fra entity:
Then you only need to conduct a single query and have access to each Fra and it's associated Frsa details. In your case you look to be more interested in the associated FRSA details to populate this ReportingActivity:
var details = _cctDBContext.Fra
.Where(x => x.ActTypeId == 1)
.SelectMany(x => x.Frsa.Select(p => new
{
p.Fraid,
p.Frsa1,
p.Frsaid,
p.CoreId,
p.RelToEstId,
p.ScopingSrc,
p.Mandatory
}).ToList();
though if the relationship is bi-directional where a Fra contains Frsas, and a Frsa contains a reference back to the Fra, then this could be simplified to:
var details = _cctDBContext.Frsa
.Where(x => x.Fra.ActTypeId == 1)
.Select(p => new
{
p.Fraid,
p.Frsa1,
p.Frsaid,
p.CoreId,
p.RelToEstId,
p.ScopingSrc,
p.Mandatory
}).ToList();
Either of those should give you the details from the FRSA to populate your reporting entity.

How effectively add not existing entities from input List to DB in Entity Framework

I have an entity User with primary key property UCO.
I would like to write a method AddUsers that adds all users from given list to the database. Problem is that when admin tries to add list in which is a user that already exists in the database, then an exception is thrown.
My solution that adds only Users that are not in database is here:
public void AddUsers(List<UserDTO> users)
{
using (var db = new AppDbContext())
{
var existingUsers = Mapper.Map<List<UserDTO>>(db.Users.ToList());
db.Users.AddRange(
Mapper.Map<List<User>>(users
.Where(user => !existingUsers
.Select(u => u.UCO)
.Contains(user.UCO))));
db.SaveChanges();
}
}
I would like to ask whether there is some more effective solution. If I had 10'000 users, then this would be very slow...
Thank you for answers :)
If the users list is not expected to be so big, you can avoid loading the potentially big existingUsers list at least in a two ways.
First, execute a single db query per each item in the users list (so totally users.Count queries):
using (var db = new AppDbContext())
{
db.Users.AddRange(Mapper.Map<List<User>>(users
.Where(user => !db.Users.Any(u => u.UCO == user.UCO))));
db.SaveChanges();
}
Second, execute a single db query for retrieving the subset of the UCO of the items from users list that exists in the database, then use it for excluding them from add:
using (var db = new AppDbContext())
{
var userUCOs = users.Select(u => u.UCO);
var existingUserUCOs = new HashSet<int>(db.Users
.Where(u => userUCOs.Contains(u.UCO))
.Select(u => u.UCO));
db.Users.AddRange(Mapper.Map<List<User>>(users
.Where(u => !existingUserUCOs.Contains(u.UCO))));
db.SaveChanges();
}
(if the UCO type is not int, just use HashSet<UCO_Type>)

Read most recently inserted rows by Date using linq to Entity framework

I have a log table in my db and wants to fetch only those records which are added most recently based on the column name RowCreateDate, this is how I am trying to achieve the records which is bringing the rows from the db but I feel may be there is a better way to achieve the same.
using (var context = new DbEntities())
{
// get date
var latestDate = context.Logs.Max(o => o.RowCreateDate);
if(latestDate!=null)
{
lastDate = new DateTime(latestDate.Value.Year, latestDate.Value.Month, latestDate.Value.Day,00,00,00);
logs = context.Logs.Where( o.RowCreateDate >= lastDate).ToList();
}
}
What i need to know I am doing right or there would another better way?
Yet another option:
context.Logs.Where(c => DbFunctions.TruncateTime(c.RowCreateDate) == DbFunctions.TruncateTime(context.Logs.Max(o => o.RowCreateDate)))
This reads explicitly like what you want (get all rows with date equals max date) and will also result in one query (not two as you might have expected).
You can't simplify this code because LINQ to Entities does not support TakeWhile method.
You can use
using (var context = new DbEntities())
{
// get date
var latestDate = context.Logs.Max(o => o.RowCreateDate);
if(latestDate!=null)
{
lastDate = new DateTime(latestDate.Value.Year, latestDate.Value.Month, latestDate.Value.Day,00,00,00);
logs = context.Logs
.OrderBy(o => o.RowCreateDate)
.AsEnumerable()
.TakeWhile(o => o.RowCreateDate >= lastDate);
}
}
BUT it takes all your data from DB, which is not very good and I do not recommend it.
I think this will do (if we assume you want to get top 3 most recent record):
var topDates = context.Logs.OrderByDescending(x=>x.RowCreateDate).Take(3)
First, I think that your code is fine. I don't see the problem with the two queries. But if you want to simplify it you use TruncateTime, like this:
IGrouping<DateTime?, Logs> log =
context.Logs.GroupBy(x => DbFunctions.TruncateTime(x.RowCreateDate))
.OrderByDescending(x => x.Key).FirstOrDefault();
It will return a grouped result with the logs created during the last day for RowCreateDate.

LINQ: Is there a way to combine these queries into one?

I have a database that contains 3 tables:
Phones
PhoneListings
PhoneConditions
PhoneListings has a FK from the Phones table(PhoneID), and a FK from the Phone Conditions table(conditionID)
I am working on a function that adds a Phone Listing to the user's cart, and returns all of the necessary information for the user. The phone make and model are contained in the PHONES table, and the details about the Condition are contained in the PhoneConditions table.
Currently I am using 3 queries to obtain all the neccesary information. Is there a way to combine all of this into one query?
public ActionResult phoneAdd(int listingID, int qty)
{
ShoppingBasket myBasket = new ShoppingBasket();
string BasketID = myBasket.GetBasketID(this.HttpContext);
var PhoneListingQuery = (from x in myDB.phoneListings
where x.phonelistingID == listingID
select x).Single();
var PhoneCondition = myDB.phoneConditions
.Where(x => x.conditionID == PhoneListingQuery.phonelistingID).Single();
var PhoneDataQuery = (from ph in myDB.Phones
where ph.PhoneID == PhoneListingQuery.phonePageID
select ph).SingleOrDefault();
}
You could project the result into an anonymous class, or a Tuple, or even a custom shaped entity in a single line, however the overall database performance might not be any better:
var phoneObjects = myDB.phoneListings
.Where(pl => pl.phonelistingID == listingID)
.Select(pl => new
{
PhoneListingQuery = pl,
PhoneCondition = myDB.phoneConditions
.Single(pc => pc.conditionID == pl.phonelistingID),
PhoneDataQuery = myDB.Phones
.SingleOrDefault(ph => ph.PhoneID == pl.phonePageID)
})
.Single();
// Access phoneObjects.PhoneListingQuery / PhoneCondition / PhoneDataQuery as needed
There are also slightly more compact overloads of the LINQ Single and SingleOrDefault extensions which take a predicate as a parameter, which will help reduce the code slightly.
Edit
As an alternative to multiple retrievals from the ORM DbContext, or doing explicit manual Joins, if you set up navigation relationships between entities in your model via the navigable join keys (usually the Foreign Keys in the underlying tables), you can specify the depth of fetch with an eager load, using Include:
var phoneListingWithAssociations = myDB.phoneListings
.Include(pl => pl.PhoneConditions)
.Include(pl => pl.Phones)
.Single(pl => pl.phonelistingID == listingID);
Which will return the entity graph in phoneListingWithAssociations
(Assuming foreign keys PhoneListing.phonePageID => Phones.phoneId and
PhoneCondition.conditionID => PhoneListing.phonelistingID)
You should be able to pull it all in one query with join, I think.
But as pointed out you might not achieve alot of speed from this, as you are just picking the first match and then moving on, not really doing any inner comparisons.
If you know there exist atleast one data point in each table then you might aswell pull all at the same time. if not then waiting with the "sub queries" is nice as done by StuartLC.
var Phone = (from a in myDB.phoneListings
join b in myDB.phoneConditions on a.phonelistingID equals b.conditionID
join c in ph in myDB.Phones on a.phonePageID equals c.PhoneID
where
a.phonelistingID == listingID
select new {
Listing = a,
Condition = b,
Data = c
}).FirstOrDefault();
FirstOrDefault because single throws error if there exists more than one element.

Inefficient entity framework queries

I have a following foreach statement:
foreach (var articleId in cleanArticlesIds)
{
var countArt = context.TrackingInformations.Where(x => x.ArticleId == articleId).Count();
articleDictionary.Add(articleId, countArt);
}
Database looks like this
TrackingInformation(Id, ArticleId --some stuff
Article(Id, --some stuff
what I want to do is to get all the article ids count from TrackingInformations Table.
For example:
ArticleId:1 Count:1
ArticleId:2 Count:8
ArticleId:3 Count:5
ArticleId:4 Count:0
so I can have a dictionary<articleId, count>
Context is the Entity Framework DbContext. The problem is that this solution works very slow (there are > 10k articles in db and they should rapidly grow)
Try next query to gather grouped data and them add missing information. You can try to skip Select clause, I don't know if EF can handle ToDictionary in good manner.
If you encounter Select n + 1 problem (huge amount of database requests), you can add ToList() step between Select and ToDictionary, so that all required information will be brought into memory.
This depends all your mapping configuration, environment, so in order to get good performance, you need to play a little bit with different queries. Main approach is to aggregate as much data as possible at database level with few queries.
var articleDictionary =
context.TrackingInformations.Where(trackInfo => cleanArticlesIds.Contains(trackInfo.ArticleId))
.GroupBy(trackInfo => trackInfo.ArticleId)
.Select(grp => new{grp.Key, Count = grp.Count()})
.ToDictionary(info => "ArticleId:" + info.Key,
info => info.Count);
foreach (var missingArticleId in cleanArticlesIds)
{
if(!articleDictionary.ContainsKey(missingArticleId))
articleDictionary.add(missingArticleId, 0);
}
If TrackingInformation is a navigatable property of Article, then you can do this:
var result=context.Article.Select(a=>new {a.id,Count=a.TrackingInformation.Count()});
Putting it into a dictionary is simple as well:
var result=context.Article
.Select(a=>new {a.id,Count=a.TrackingInformation.Count()})
.ToDictionary(a=>a.id,a=>a.Count);
If TrackingInforation isn't a navigatable property, then you can do:
var result=context.Article.GroupJoin(
context.TrackingInformation,
foo => foo.id,
bar => bar.id,
(x,y) => new { id = x.id, Count = y.Count() })
.ToDictionary(a=>a.id,a=>a.Count);

Categories

Resources