Selecting & Updating Many-To-Many in Entity Framework 4 - entity-framework

I am relatively new to the EF and have the following entity model above which consists of an Asset and a Country. An Asset can belong in many countries and thus has a many-to-many relationship with country (has a join table in the database with two fields both as primary keys).
I want to be able to do the following:
Firstly when I retrieve an asset (or assets) from the model I want to get the respective countries that its associated with. I would then like to be able to bind the countries list to an IEnumerable. Retrieving the countries in this way provides me with an EntityCollection of country objects which has extension method for ToList(). Therefore not sure If I am going down the right avenue with this one. Here is my GetAll method:
public IEnumerable<Asset> GetAll()
{
using (var context = CreateAssetContext())
{
var assetEntities = context.Assets.Include("Countries").ToList();
return AssetMapper.FromEntityObjects(assetEntities);
}
}
Secondly I want to be able to select a list of countries where the AssetId == some value.
Finally I want to be able to update the list of countries for a given Asset.
Many thanks.

Firstly when I retrieve an asset (or assets) from the model I want to
get the respective countries that its associated with. I would then
like to be able to bind the countries list to an IEnumerable.
Not sure if I understand that correctly, but EntityCollection<T> implements IEnumerable<T>, so you don't have to do anything special, you just can use Asset.Countries after you have loaded the assets including the countries.
Secondly I want to be able to select a list of countries where the
AssetId == some value.
using (var context = CreateAssetContext())
{
var countries = context.Countries
.Where(c => c.Assets.Any(a => a.AssetId == givenAssetId))
.ToList();
}
Or:
using (var context = CreateAssetContext())
{
var countries = context.Assets
.Where(a => a.AssetId == givenAssetId)
.Select(a => a.Countries)
.SingleOrDefault();
}
The second option is OK (not sure if it's better than the first from SQL viewpoint) because AssetId is the primary key, so there can be only one asset. For querying by other criteria - for example Asset.Name == "XYZ" - where you could expect more than one asset I would prefer the first option. For the second you had to replace Select by SelectMany and SingleOrDefault by ToList and use Distinct to filter out possible duplicated countries. The SQL would probably be more complex.
Finally I want to be able to update the list of countries for a given
Asset.
This is more tricky because you need to deal with the cases: 1) Country has been added to asset, 2) Country has been deleted from asset, 3) Country already related to asset.
Say you have a list of country Ids ( IEnumerable<int> countryIds ) and you want to relate those countries to the given asset:
using (var context = CreateAssetContext())
{
var asset = context.Assets.Include("Countries")
.Where(a => a.AssetId == givenAssetId)
.SingleOrDefault();
if (asset != null)
{
foreach (var country in asset.Countries.ToList())
{
// Check if existing country is one of the countries in id list:
if (!countryIds.Contains(country.Id))
{
// Relationship to Country has been deleted
// Remove from asset's country collection
asset.Countries.Remove(country);
}
}
foreach (var id in countryIds)
{
// Check if country with id is already assigned to asset:
if (!asset.Countries.Any(c => c.CountryId == id))
{
// No:
// Then create "stub" object with id and attach to context
var country = new Country { CountryId = id };
context.Countries.Attach(country);
// Then add to the asset's country collection
asset.Countries.Add(country);
}
// Yes: Do nothing
}
context.SaveChanges();
}
}
Edit
For the price of a second roundtrip to the database you can probably use this simpler code:
using (var context = CreateAssetContext())
{
var asset = context.Assets.Include("Countries")
.Where(a => a.AssetId == givenAssetId)
.SingleOrDefault();
if (asset != null)
{
// second DB roundtrip
var countries = context.Countries
.Where(c => countryIds.Contains(c.CountryId))
.ToList();
asset.Countries = countries;
context.SaveChanges();
}
}
EF's change detection should recognize which countries have been added or deleted from the asset's country list. I am not 100% sure though if the latter code will work correctly.

Whats the specific question here? Are you not being able to do that?
do you want to select the countries in an asset or the countries that have a certain asset?
to update its simple, just change stuff and then context.SaveChanges() will commit to the database.

Related

EF6 can I update model/table after lambda querying?

I am lambda querying models (I make projection with other classes-GameBankVM, GameCouponBankVM) and at the end, I would like to loop throuh query result and update the model field. But I am getting The entity or complex type 'EPINMiddleWareAPI.Models.GameBankVM' cannot be constructed in a LINQ to Entities query.
Here is my sample code:
var gameBankResult = await (context.GameBanks.Where(g => g.productCode == initiate.productCode)
.Take(initiate.quantity)
.Select(g => new GameBankVM
{
quantity = g.quantity,
currency = g.currency,
initiationResultCode = g.initiationResultCode,
productCode = g.productCode,
productDescription = g.productDescription,
referenceId = g.referenceId,
responseDateTime = g.responseDateTime,
unitPrice = g.unitPrice,
totalPrice = g.totalPrice,
coupons = g.coupons.Select(c => new GameCouponBankVM
{
Pin = c.Pin,
Serial = c.Serial,
expiryDate = c.expiryDate
}).ToList()
})).ToListAsync();
if (gameBankResult.Count() != 0)
{
foreach (var item in gameBankResult)
{
item.referenceId = initiate.referenceId;
context.Entry(item).State = System.Data.Entity.EntityState.Modified;
}
await context.SaveChangesAsync();
return Ok(gameBankResult);
}
How can I update referenceId on my GameBank model/table?
In this scenario, your data won't be updated because your query is returning a List of GameBankVM and not a List of GameBank, now technically speaking, you are breaking SRP, you should either update your data or query your data not both in the same method, you may want to refactor your method like this :
1.- Create a private method for data update, in this case, you query directly GameBank iterate thru list entries, make your changes and save them to the database, this same method can return List of GameBank to avoid another database roundtrip.
2.- In the controller after you call your new method, you can run the transformation query to convert List of GameBank to List of GameBankVM and return it to the view.
There are many other ways to do this, I'm just recommending this as a less impact way to make your controller work. But if you are willing to make things better, you can create a business layer where you resolve all your business rules, or you can use patterns like CQS or CQRS.

Get data from SQL table by linq-to-sql using data from collection

I'm using entity framework to connect to database from my application. I have table in SQL, named Orders. It contains such fields as: TransactionId, ParticipantId and is linked to Transactions table which has one to many connection to Participants table. I need to get data from it using List of classes with such properties: TransactionId, ParticipantId, OrganizationId. Linq must meet such conditions: (orders.TransactionId == TransactionId && orders.ParticipantId == ParticipantId && orders.Transaction.Participants.Any(x=> x.Id == OrganizationId)). This should be done by one query, not by multiple, so, please don't recommend foreach or smth like that.
Like #NetMage said, generally we need examples. Assuming that you've got a dbcontext set up, the ask is pretty simple:
public static void GetData(int transactionId, int participantId, int organizationId)
{
using (var db = new MyDbContext())
{
var query =
(
from t in db.Transactions
from o in db.Orders
.Where(w => w.TransactionId == t.TransactionId)
from p in db.Participants
.Where(w => w.TransactionId == t.TransactionId)
where t.TransactionId = transactionId &&
o.ParticipantId = participantId
select new { Order = o, Transaction = t, Participant = p}
);
}
}
Again since we don't have a lot of information here it's hard to do more. You should be able to take it from there. I know I didn't use the organizationId filter, but since I don't know the target shape of the data I'm not sure what the best path would be

Nested Where on 1-to-many in LINQ2Entity

I'm using EF4. Having 2 entities:
Person { Name }
Hobbys { Person.Name, IsCoolHobby }
1 Person can have several hobbys.
I now have
IQueryable<Person> p;
p = container.PersonSet.Include("Hobbys").AsQueryable();
p = p.Where(x => x ?????);
List<Person> tmp = p.ToList();
How can i return only those Persons who have cool hobbys (IsCoolHobby == true)? I tried join but i was not able to load them into the list (select can only return Person, Hobby or new Type - but how to map them to entity objects again?)
Thanks
How can i return only those Persons who have cool hobbys (IsCoolHobby
== true)?
List<Person> tmp = container.PersonSet.Include("Hobbys")
.Where(p => p.Hobbys.Any(h => h.IsCoolHobby))
.ToList();
This will load the people who have at least one cool hobby but the Hobbys collection for those people will always contain all hobbys, also the uncool hobbys.
Edit
Unfortunately filtering and sorting children during eager loading (Include) is currently not supported. There is a request on the EF feature suggestion page for this feature. The request has status "Under review", so there is a little hope that it might get implemented in the future. (Probably far future: At least the first docs about EF 5 (beta) on MSDN say explicitly that eager loading with filtering/sorting is still not implemented.)
For now there are only two workarounds. The first is to use a projection:
var projectedData = container.PersonSet
.Where(p => p.Hobbys.Any(h => h.IsCoolHobby))
.Select(p => new
{
Person = p,
CoolHobbys = p.Hobbys.Where(h => h.IsCoolHobby)
})
.ToList();
The result is a collection of anonymous objects which contain a user who has cool hobbys and a collection of those cool hobbys. If you don't disable change tracking (by using the NoTracking option for the query) the person's hobbys collection should be filled with the result automatically.
The second option is to use "explicit" loading with CreateSourceQuery:
List<Person> tmp = container.PersonSet
.Where(p => p.Hobbys.Any(h => h.IsCoolHobby))
.ToList();
foreach (var person in tmp)
{
person.Hobbys.Attach(person.Hobbys.CreateSourceQuery()
.Where(h => h.IsCoolHobby).ToList());
}
Two things to note here:
CreateSourceQuery is only available on EntityCollections, i.e. if you are using EntityObject derived entities. It's not available for POCO entities in EF 4.0. (EF >= 4.1/DbContext has the option for explicit loading also for POCOs -> Query() method.)
The above code represents 1+N roundtrips to the database: The first for the person collection without the hobbys and then one additional query per person to load the cool hobbys.

ADO.NET Entity Framework multiple statement in where clause or a grouped lambda expression

I am new to the ADO.NET Entity Framework Model. Using DotNet Framework 3.5
I have two tables :
viz. customers and city
The customers table refers to a cityname column in the table city (foreign key relationship)
While creating a win c# form i am giving the user to filter customers based on his search choices (viz. name, city, number, etc.) Here is my structure
using(DataContext dc = new DataContext())
{
IEnumerable cust = dc.customers;
if(name != null) {
cust = cust.Where<customers>(c => c.name == name);
}
if(mobile != null) {
cust = cust.Where<customers>(c => c.mobile == mobile);
}
if(city != null) {
cust = cust.Where<customers>(c => c.city.cityname == city); //ERROR HERE
}
}
I get a NullPointerException, since the EntityReference.Load method is not called upon. Quite logical point and I agree to it.
I would like some advice on how to either call the load method in the current architechure. Is it possible to somehow do this :
c.cityReference.Load();
c.city.cityname == city
Or possibly some lambda expression (I am new to it) which induces both the statements? Ne suggestions?
I am ready to change the current architecture if anyone has a better advice.
Try
IEnumerable cust = dc.customers.Include("city");
Check out this post for reference.

LINQ to Entites: Doing a count over one-to-many relationships

I have a couple of tables where there are one to many relationships. Let's say I have a Country table, a State table with a FK to Country, and a City table with a FK to State.
I'd like to be able to create a count of all Cities for a given country, and also a count of cities based on a filter - something like:
foreach( var country in Model.Country ) {
total = country.State.All().City.All().Count() ;
filtered = country.State.All().City.Any(c=>c.field == value).Count();
}
Obviously, this doesn't work - is there any way to do this?
Update:
I can iterate thru the objects:
foreach (var item in Model ) {
... other stuff in here ...
int tot = 0;
int filtered = 0;
foreach (var state in item.State)
{
foreach (var city in state.City)
{
tot++;
if (city.Field == somevalue)
filtered ++;
}
}
... other stuff in here ...
}
but that doesn't seem very elegant.
Update: #AD has a couple of suggestions, but what worked to solve the problem was:
int tot = item.States.Sum(s=>s.City.Count);
int filtered = item.States.Sum(s=>s.City.Where(c=>c.Field == somevalue).Count());
You can try, assuming you already have the givenCountry and value variable populated:
int total = EntityModel.CitySet.Where( it => it.State.Country.ID == givenCountry.ID ).Count();
Above, you take your entire set of cities (EntityMode.CitySet). This set contains all the cities in all the states in all the countries. The problem becomes: what subset of those cities are in country 'givenCountry'? To figure it out, you apply the Where() to the entire set and you compare the countries id to see if they are the same. However, since the city only knows which state it is in (and not the country) you first have to reference its state (it.State). it.State references the state object and that object has a Country property that will reference the country. Then it.State.Country references the country 'it' is in and 'it' is the city, creating a link between the city and the country.
Note that you could have done this is reverse as well with
int total = givenCountry.Sum( c => c.States.Sum( s.Cities.Count() ) )
However, here you will have to make sure that givenCountry has its States collection loaded in memory and also that each State has its Cities collection loaded. That is because you are using Linq-to-Entities on a loaded object and not on an Entity Framework instance object has was the case in the first example. There is a way to craft the last query to use the entity framework object however:
int total = EntityModel.CountrySet.Where( c => c.ID == givenCountry.ID ).Sum( c => c.States.Sum( s.Cities.Count() ) )
As for the number of cities with a specific field, you take a similar approach with a Where() call:
int filtered = EntityModel.CitySet.Where( it => it.field == value ).Count();
Why dont you reverse it?
foreach( var country in Model.Country ) {
var city = Model.State.Where(x=>x.StateID==country.State.StateID).Select(x=>City)
total = city.Count();
filtered = city.All(c=>c.field == value).Count();
}
You have to explicitly load children in the Entity Framework. If you load all the children then you can get counts just fine.
IEnumberable<Country> countries = Model.Country.Include("State");
total = countries[i].State.Count();
Assuming of course that the iteration through all countries is important. Otherwise why not just query against City filtered by State and Country?
In your state foreach you should just be able to do
tot += state.City.Where(x=> x.Field == value).Count();