I have a Entity Framework model where I have a user which has the following relationships:
User 1-* Test
Each Test has the following relationships:
Test 1-1 Course
Test 1-* TestEvent
I have a service that returns a user and at different points in my application I want to eagerly fetch various relationships. I am fetching all the relationships at present:
var result = (from appUser in context.AppUsers.Include("Tests.Course")
.Include("Tests.TestEvents")
where appUser.Login.ToLower() == loginName.ToLower() &&
appUser.IsDeleted == false
select appUser).FirstOrDefault();
I do not want to always return the Course linked to a Test or the TestEvents. How can I build an ObjectQuery and dynamically add the Include statements as required rather than having a series of if else statements.
Well, since Include returns an IQueryable<T> (LINQ chainable), i would use a simple extension method:
public static IQueryable<T> WithIncludes<T>(this IQueryable<T> source, string[] associations)
{
var query = (ObjectQuery<T>)source;
foreach (var assoc in associations)
{
query = query.Include(assoc);
}
}
Then your query looks like this:
var inclusions = new[] { "Tests.Course", "Tests.TestEvents" };
var result = ctx.AppUsers
.WithIncludes(inclusions)
.Where(x => x.Login.ToLower() == loginName.ToLower())
.Where(x => !x.IsDeleted)
.FirstOrDefault();
To get some strong-typing into the otherwise magic-string nature of include, i have specialized enums for all associations, so i pass an [] of those through and convert the enum to the string include.
The thing is, your methods should define what inclusions are required. So first thing you do in the method is declare what associations are required, then pass that to your extension method.
Is that what your after?
Related
I have an ASP.NET MVC app using Entity Framework from our SQL Server backend.
Goal is to create ~18 WPackage entries via a foreach loop:
foreach (var dbitem in dbCList)
The code works for a single WPackage entry, but we have a request from the customer to create 300+ WPackages, so trying to use the Entity Framework code for a single "Add" and loop to create 300+ adds.
The T-SQL would be very challenging as there are many keys created on the fly/at row creation, so for activities >> resources, we'd have to insert the activity, grab or remember the activity key, then add resources with that newly created activity key.
Each WPackage (this is the main parent table) could have one or more of the following child table entries:
1+ activities
each activity would have 1+ resource
1+ budgets
1+ Signatures
1+ CostCodes
Our schema or model diagram would be:
WPackage
--Activities
-----Resources (child of Activities)
--CostCodes
--Budgets
--Signatures
The following code fails on:
dbContextTransaction.Commit();
with an error:
The transaction operation cannot be performed because there are pending requests working on this transaction.
[HttpPost]
public ActionResult Copy([Bind(Include = "ID,WBSID,...***fields excluded for brevity")] Package model)
{
if (ModelState.IsValid)
{
try
{
using (var dbContextTransaction = db.Database.BeginTransaction())
{
var dbCList = db.Packages.Join(db.WBS,
*expression omitted for brevity*)
// this dbClist will build about 18 items in the collection for below loop
foreach (var dbitem in dbCList)
{
int testWPID = dbitem;
WPackage prvWP = db.WPackages.Find(dbitem);
int previousWPID = dbitem;
WPackage previousWP = db.WPackages.Find(dbitem);
model.ID = dbitem;
db.WPackages.Add(model);
db.SaveChanges();
var budgets = db.Budgets.Where(i => i.WPID == previousWPID);
foreach (Budget budget in budgets)
{
budget.WPID = model.ID;
db.Budgets.Add(budget);
}
var costCodes = db.CostCodes.Where(i => i.WPID == previousWPID);
foreach (CostCode costCode in costCodes)
{
costCode.WPID = model.ID;
db.CostCodes.Add(costCode);
}
var activities = db.Activities.Where(i => i.WPID == previousWPID);
// *code excluded for brevity*
var previousActivityID = activity.ID;
db.Activities.Add(activity);
db.SaveChanges();
var resources = db.Resources.Where(i => i.ActivityID == previousActivityID);
foreach (Resource resource in resources)
{
resource.WPID = model.ID;
resource.ActivityID = activity.ID;
resource.ActivityNumber = activity.ActivityNumber;
db.Resources.Add(resource);
db.SaveChanges();
}
}
var signatures = db.RolesAndSigs
.Where(i => i.KeyId == previousWPID && i.Type == "WPL")
.OrderBy(i => i.Role)
.OrderBy(i => i.Person);
foreach (RolesAndSig signature in signatures)
{
db.RolesAndSigss.Add(signature);
}
db.SaveChanges();
dbContextTransaction.Commit();
}
}
}
}
I've also tried to have the Commit() run outside the foreach dbitem loop like:
db.SaveChanges();
//dbContextTransaction.Commit();
}
dbContextTransaction.Commit();
...but this returns error of:
[EXCEPTION] The property 'ID' is part of the object's key information and cannot be modified.
The code you posted has some issues that don't make sense, and probably aren't doing what you think they are doing. The crux of the issue you are facing is that Entity Framework tracks all references to entities it loads and associates:
Firstly this code:
int testWPID = dbitem;
WPackage prvWP = db.WPackages.Find(dbitem);
int previousWPID = dbitem;
WPackage previousWP = db.WPackages.Find(dbitem);
prvWP and previousWP will be pointing to the exact same reference, not two copies of the same entity. Be careful when updating either or any other reference retrieved or associated with that same ID. They all point to the same instance. If you do want a stand-alone snaphot reference you can use AsNoTracking().
Next, when you do something like this in a loop:
model.ID = dbitem;
db.WPackages.Add(model);
In the first iteration, "model" is not an entity. It is a deserialized block of data with the Type of the Package entity. As soon as you call .Add(model) that reference will now be pointing to a newly tracked entity reference. In the next loop you are telling EF to change that tracked entity reference's ID to a new value, and that is illegal.
What it looks like you want to do is create a copy of this model for each of the 18 expected iterations. For that what you want to do would be something more like:
foreach (var dbitem in dbCList)
{
var newModel = new WPackage
{
ID = dbItem,
WBSID = model.WBSID,
/// copy across all relevant fields from the passed in model.
};
db.WPackages.Add(newModel);
// ...
}
It would be quite worthwhile to leverage navigation properties for the related entities rather than using explicit joins and trying to scope everything in an explicit transaction with multiple SaveChanges() calls. EF can manage all of the FKs automatically rather than essentially using it as a wrapper for individual ADO CRUD operations.
You will need to be explicit between when you want to "clone" an object reference vs. "copy" a reference. For example, if I have a Customer that has an Address, and Addresses have a Country reference, when I clone a Customer, I will want to clone a new Address record for that Customer, however ensure that the Country reference is copied across. If I have a record for Jack at an 123 Apple Street, London in England, and go to clone Jack to make a record for Jill at the same address, they might be at the same location now, but not always, so I want them to point at different Address records in case Jill moves out. Still, there should only be one record for "England". (Jill may move to a different country, but her address record would just point at a different Country Id)
Wrong:
var jill = context.Customers.Single(c => c.Name == "Jack");
jill.Name = "Jill";
context.Customers.Add(jill);
This would attempt to rename Jack into Jill, then "Add" the already tracked instance, resulting in an exception.
Will work, but still Wrong:
var jack = context.Customers.AsNoTracking().Single(c => c.Name == "Jack");
var jill = jack;
jill.Name = "Jill";
context.Customers.Add(jill);
This would technically work by loading Jack as an untracked entity, and would save Jill as a new record with a new Id. However this is potentially very confusing. Depending on how the AddressId/Address is referenced we could end up with Jack and Jill referencing the same single Address record. Bad if you want Jack and Jill to have different addresses.
Right:
var jack = context.Customers
.Include(c => c.Address)
.ThenInclude(a => a.Country)
.Single(c => c.Name == "Jack");
var jill = new Customer
{
Name = "Jill",
// copy other fields...
Address = new Address
{
StreetNumber = jack.Address.StreetNumber,
StreetName = jack.Address.StreetName,
Country = jack.Address.Country
}
};
context.Customers.Add(jill);
The first detail is to ensure when we load Jack that we eager load all of the related details we will want to clone or copy references to. We then create a new instance for Jill, copying the values from Jack, including setting up a new Address record. The Country reference is copied across as there should only be ever a single record for "England".
Edit: For something like a roll-over scenario if you have a package by year, let's use the example of a Package class below:
public class Package
{
[Key]
public int PackageId { get; set; }
[ForeignKey("PackageType")]
public int PackageTypeId { get; set; }
public int Year { get; set; }
// .. More package related details and relationships...
public virtual PackageType PackageType { get; set; }
}
A goal might be to make a new Package and related data for Year 2022 from the data from 2021, and apply any changes from a view model passed in.
Find is a poor choice for this because Find wants to locate data by PK. If you're method simply passes an entity to be copied from (I.e. the data from 2021) then this can work, however if you have modified that data from 2021 to represent values you want for 2022 that could be dangerous or misleading within the code. (We don't want to update 2021's data, we want to create a new record set for 2022) To make a new Package for 2022 we just need the updated data to make up that new item, and a way to identify a source for what to use as a template. That identification could be the PK of the row to copy from (ProductId), or derived from the data passed in. (ProductTypeId, and Year-1) In both cases if we want to consider related data with the "copy from" product then it would be prudent to eager load that related data in one query rather than going back to the database repeatedly. Find cannot accommodate that.
For instance if I want to pass data to make a new product I pass a ProductTypeId, and a Year along with any values to use for the new structure. I can attempt to get a copy of the existing year to use as a template via:
var existingProduct = context.Products
.Include(x => x.Activities) // Eager load related data.
.Include(x => x.CostCodes)
// ...
.Single(x => x.ProductTypeId == productTypeId && x.Year = year - 1);
or if I passed a ProductId: (such as if I could choose to copy the data from a selected year like 2020 instead)
var existingProduct = context.Products
.Include(x => x.Activities)
.Include(x => x.CostCodes)
// ...
.Single(x => x.ProductId == copyFromProductId);
Both of these examples expect to find one, and only one existing product. If the request comes in with values that it cannot find a row for, there would be an exception which should be handled. This would fetch all of the existing product information that we can copy from, alongside any data that was passed into the method to create a new Product.
This question already has answers here:
Working with C# Anonymous Types
(8 answers)
Closed 8 years ago.
I am using ASP.NET mvc 5. I have one class that holds all the LINQ which can access to another class. now i convert the LINQ query to list variable Query and returning as IList... the i create object of this class--> call the method and get result.
now i can see in debugging object names but i can't see in foreach loop. my list hold mix data types, plus result is merging from different tables...
public IList GetAllFeeZonesForFeeSchemeByID(int FeeSchemeID)
{
using (var db = new QualificationContext())
{
var Query = from a in db.FeeScheme
join b in db.FeeZoneSchema.Where(c => c.FeeSchemeID == 1) on a.FeeSchemeID equals b.FeeSchemeID
join c in db.FeeZone on b.FeeZoneID equals c.FeeZoneID
select new
{
FeeScheme = a.FeeSchemeID,
FeeZone = b.FeeZoneID,
FeeZone_Description = c.FeeZoneDescription
};
return Query.ToList();
}
}
in controller class...
foreach(var item in obj1.GetAllFeeZonesForFeeSchemeByID(1))
{
item.???? (can't access the object here....
}
many thanks
You should return Generic IList of concrete (not anonymous class):
public IList<FeeSchemeModel> GetAllFeeZonesForFeeSchemeByID(int FeeSchemeID)
{
using (var db = new QualificationContext())
{
var Query = from a in db.FeeScheme
join b in db.FeeZoneSchema.Where(c => c.FeeSchemeID == 1) on a.FeeSchemeID equals b.FeeSchemeID
join c in db.FeeZone on b.FeeZoneID equals c.FeeZoneID
select new FeeSchemeModel
{
FeeScheme = a.FeeSchemeID,
FeeZone = b.FeeZoneID,
FeeZone_Description = c.FeeZoneDescription
};
return Query.ToList();
}
}
public class FeeSchemeModel
{
public int FeeScheme{get;set;}
public int FeeZone{get;set;}
public string FeeZone_Description{get;set;}
};
But I recommend to use IEnumerable<T> instead of IList<T> and use ToArray() method instead of ToList() method if you don't use special features of List<T> (such as method Add())
IList is non-generic interface, it contains only non-generic IEnumerable definition, which enumerates objects. So type of item will be object. That's why you can see only members of System.Object class.
You should either cast item to appropriate type or use generic collection parametrized with appropriate type. But you can't use neither of these approaches while you are using anonymous objects, because you don't know anonymous type name. So, you need to create some class which you will be able to cast to:
foreach(Foo item in obj1.GetAllFeeZonesForFeeSchemeByID(1))
Or use as parameter of method return type:
public IList<Foo> GetAllFeeZonesForFeeSchemeByID(int FeeSchemeID)
One more option is usage of dynamic type, which will resolve operations on object at runtime. You still will not be able to use IntelliSense but your code will work:
foreach(dynamic item in obj1.GetAllFeeZonesForFeeSchemeByID(1))
{
// use item.FeeScheme
}
so I have a form with several fields which are criteria for searching in a database.
I want to formulate a query using LINQ like so:
var Coll = (from obj in table where value1 = criteria1 && value2 = criteria2...)
and so on.
My problem is, I don't want to write it using If statements to check if every field has been filled in, nor do I want to make separate methods for the various search cases (criteria 1 and criteria 5 input; criteria 2 and criteria 3 input ... etc.)
So my question is: How can I achieve this without writing an excessive amount of code? If I just write in the query with comparison, will it screw up the return values if the user inputs only SOME values?
Thanks for your help.
Yes, it will screw up.
I would go with the ifs, I don't see what's wrong with them:
var query = table;
if(criteria1 != null)
query = query.Where(x => x.Value1 == criteria1);
if(criteria2 != null)
query = query.Where(x => x.Value2 == criteria2);
If you have a lot of criteria you could use expressions, a dictionary and a loop to cut down on the repetitive code.
In an ASP.NET MVC app, chances are your user input is coming from a form which is being POSTed to your server. In that case, you can make use of strongly-typed views, using a viewmodel with [Required] on the criteria that MUST be provided. Then you wrap your method in if (ModelState.IsValid) { ... } and you've excluded all the cases where the user hasn't given you something they need.
Beyond that, if you can collect your criteria into a list, you can filter it. So, you could do something like this:
filterBy = userValues.Where(v => v != null);
var Coll = (from obj in table where filterBy.Contains(value1) select obj);
You can make this more complex by having a Dictionary (or Lookup for non-unique keys) that contains a user-entered value along with some label (an enum, perhaps) that tells you which field they're filtering by, and then you can group them by that label to separate out the filters for each field, and then filter as above. You could even have a custom SearchFilter object that contains other info, so you can have filters with AND, NOT and OR conditions...
Failing that, you can remember that until you trigger evaluation of an IQueryable, it doesn't hit the database, so you can just do this:
var Coll = (from obj in table where value1 == requiredCriteria select obj);
if(criteria1 != null)
{
query = query.Where(x => x.Value1 == criteria1);
}
//etc...
if(criteria5 != null)
{
query = query.Where(x => x.Value5 == criteria5);
}
return query.ToList();
That first line applies any criteria that MUST be there; if there aren't any mandatory ones then it could just be var Coll = table;.
That will add any criteria that are provided will be applied, any that aren't will be ignored, you catch all the possible combinations, and only one query is made at the end when you .ToList() it.
As I understand of your question you want to centralize multiple if for the sake of readability; if I were right the following would be one of some possible solutions
Func<object, object, bool> CheckValueWithAnd = (x, y) => x == null ? true : x==y;
var query = from obj in table
where CheckValue(obj.value1, criteria1) &&
CheckValue(obj.value2, criteria2) &&
...
select obj;
It ls flexible because in different situations or scenarios you can change the function in the way that fulfill your expectation and you do not need to have multiple if.
If you want to use OR operand in your expression you need to have second function
Func<object, object, bool> CheckValueWithOr = (x, y) => x == null ? false : x==y;
I'm designing an interface where the user can join a publicaiton to a keyword, and when they do, I want to suggest other keywords that commonly occur in tandem with the selected keyword. The trick is getting the frequency of correlation alongside the properties of the suggested keywords.
The Keyword type (EF) has these fields:
int Id
string Text
string UrlString
...and a many-to-many relation to a Publications entity-set.
I'm almost there. With :
var overlappedKeywords =
selectedKeyword.Publications.SelectMany(p => p.Keywords).ToList();
Here I get something very useful: a flattened list of keywords, each duplicated in the list however many times it appears in tandem with selectedKeyword.
The remaining Challenge:
So I want to get a count of the number of times each keyword appears in this list, and project the distinct keyword entities onto a new type, called KeywordCounts, having the same fields as Keyword but with one extra field: int PublicationsCount, into which I will populate the count of each Keyword within overlappedKeywords. How can I do this??
So far I've tried 2 approaches:
var keywordCounts = overlappingKeywords
.Select(oc => new KeywordCount
{
KeywordId = oc.Id,
Text = oc.Text,
UrlString = oc.UrlString,
PublicationsCount = overlappingKeywords.Count(ok2 => ok2.Id == oc.Id)
})
.Distinct();
...PublicationsCount is getting populated correctly, but Distinct isn't working here. (must I create an EqualityComarer for this? Why doesn't the default EqualityComarer work?)
var keywordCounts = overlappingKeywords
.GroupBy(o => o.Id)
.Select(c => new KeywordCount
{
Id = ???
Text = ???
UrlString = ???
PublicationsCount = ???
})
I'm not very clear on GroupBy. I don't seem to have any access to 'o' in the Select, and c isn't comping up with any properties of Keyword
UPDATE
My first approach would work with a simple EqualityComparer passed into .Distinct() :
class KeywordEqualityComparer : IEqualityComparer<KeywordCount>
{
public bool Equals(KeywordCount k1, KeywordCount k2)
{
return k1.KeywordId== k2.KeywordId;
}
public int GetHashCode(KeywordCount k)
{
return k.KeywordId.GetHashCode();
}
}
...but Slauma's answer is preferable (and accepted) because it does not require this. I'm still stumped as to what the default EqualityComparer would be for an EF entity instance -- wouldn't it just compare based on primary ids, as I did above here?
You second try is the better approach. I think the complete code would be:
var keywordCounts = overlappingKeywords
.GroupBy(o => o.Id)
.Select(c => new KeywordCount
{
Id = c.Key,
Text = c.Select(x => x.Text).FirstOrDefault(),
UrlString = c.Select(x => x.UrlString).FirstOrDefault(),
PublicationsCount = c.Count()
})
.ToList();
This is LINQ to Objects, I guess, because there doesn't seem to be a EF context involved but an object overlappingKeywords, so the grouping happens in memory, not in the database.
I'm sticking on how to best present some data that's being dynamically generated from two different tables.
Given my query:
var assets = assetRepo.Find(x => x.LoginId == User.Identity.Name);
var accounts = repository.Find(x => x.AccStatus == "A" && x.LoginId == User.Identity.Name);
var query = from asst in assets
join acct in accounts on asst.AccountId equals acct.AccountId
select new
{
Account = acct.AccountNumber,
Status = acct.AccStatus,
Make = asst.Make,
Model = asst.Model,
Submodel = asst.SubModel,
Registration = asst.Registration,
Balance = acct.BalanceOutstanding,
NextPayment = acct.NextPayment,
Date = String.Format("{0:dd MMM yyyy}", acct.NextPaymentDate),
Due = acct.ArrearsBal
};
What would be the best (i.e. cleanest) way to bind this to the view? Would a custom class be required or is there a way to specify and iterate over a collection of anonymous types?
Creating custom class can give you additional benefits. You can use DisplayAttribute to set column headers and order. Then you can create view (or template to use with DisplayFor) that takes list of objects of any type and uses reflection to read annotations and display view nicely.
class Report {
[Display(Name="Account",Order=1)]
public string Account {get; set;}
[Display(Name="Next payment",Order=2)]
public Date NextPayment {get; set;}
}
It looks also clean. You will be able to use this annotations not only for grid, but also for excel exports or other data operations.