I've got three classes.
Event > Workshop > Workshop Times
I'm currently looking for best way of inserting records into the Workshop Times, this is running through code first using ICollections.
Looking for something along the lines of this, but I know it doesn't work:
//Create connection
var db = new Context();
var Event = db.Events
.Include("Workshops")
.Include("Workshops.Times")
.Where(ev => ev.GUID == EventGUID).FirstOrDefault();
Event.Workshops.Add(new Workshop
{
Name = tbWorkshopName.Text,
Description = tbWorkshopDescription.Text,
Times.Add(new WorkshopTime{
//Information for times
})
});
db.SaveChanges();
Chopped down classes:
public class Workshops{
public int id { get; set; }
public string name { get; set; }
public ICollection<WorkshopTimes> Times{get;set;}
}
public class Events {
public int id { get; set; }
public string name { get; set; }
public ICollection<Workshops> WorkShops { get; set; }
}
public class WorkshopTimes {
public int id { get; set; }
public DateTime time { get; set; }
}
You are definitely on the right track with your query, however your include statements appear incorrect. From your model I would expect:
var Event = db.Events
.Include("WorkShops")
.Include("WorkShops.events")
.Where(ev => ev.GUID == EventGUID).FirstOrDefault();
Note this uses the property names not the types. This will ensure that the entities in the listed nav properties will be included in the result.
In addition you can use a lambda to do the same thing (but its typesafe)
Check out here for how to do a very similar scenario to yours:
EF Code First - Include(x => x.Properties.Entity) a 1 : Many association
or from rowan miller (from EF team)
http://romiller.com/2010/07/14/ef-ctp4-tips-tricks-include-with-lambda/
And make sure you are using System.Data.Entities for lambda based includes ( Where did the overload of DbQuery.Include() go that takes a lambda? )
Related
Fairly new to EF.Core and I'm having some issues as my tables start getting more complex. Here's an example of what I have defined for my classes. Note ... there are many more columns and tables than what I have defined below. I've paired them down for brevity.
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public bool Active { get; set; }
}
Followed by
public class JournalEntry
{
public int Id { get; set; }
public int UserId { get; set; }
public string Details { get; set; }
public DateTime DateEntered { get; set; }
public virtual User User { get; set; }
}
I want to be able to issue the following query and INCLUDE the User Table so that I can then populate a ViewModel with columns from the User Table without having to do another lookup and also to sort the data while retrieving it:
public IQueryable<JournalEntry> GetByUser(int userId)
{
return _DbContext.JournalEntries.Where(j => j.UserId == userId)
.Include(u => u.User)
.OrderBy(u=> u.User.FirstName)
.ThenBy(j => j.DateEntered);
}
My controller would then have something similar to the following:
public IActionResult List(int userId)
{
var journalEntries = new _dbRepository.GetByUser(userId);
var myViewModel = new MyViewModel();
myViewModel.UserName = ($"{journalEntries.User.FirstName} {journalEntries.User.LastName}");
myViewModel.Entries = journalEntries;
etc ....
return View(myViewModel);
}
I'm loading the user's first and last name in the View Model and whatever other attributes from the various tables that are referenced. The problem that I'm having is that I'm getting errors on the Migration creation "Foreign key constraint may cause cycle or multiple cascade paths." And of course, if I remove the line reading public virtual User User { get; set; } from the JournalEntry class then the problem goes away (as one would expect).
I believe that the way I'm doing the models is incorrect. What would be the recommended way that I should code these models? I've heard of "lazy loading". Is that what I should be moving towards?
Thanks a bunch.
--- Val
Your query returns an IQueryable<JournalEntry> not a JournalEntry.
Change the code to get the user details from the first object:
var myViewModel.UserName = ($"{journalEntries.First().User.FirstName} {journalEntries.First().User.LastName}");
In the line above I'm calling First() on your journal entries collection and that would have a User. Then I can access FirstName and LastName.
Also, don't bother with LazyLoading since you are learning. It could cause select n+1 issues if used incorrectly
I just started my first project using asp.net core and for the first time I'm gonna use the code repository for my project in C# and VS 2019.
I create a new Model and it called Comment. This Table can save all of the comments on the project, That mean is user comments in POSTS, SOCIALMEDIA, and etc Areas saved in this table
[Key]
public int CommentId { get; set; }
public eTable Table { get; set; }
public int ContentId { get; set; }
[StringLength(1500)]
public string Notes { get; set; }
public virtual AnalysedMarket AnalysedMarket { get; set; }
ContentID is my foreign key, And my eTable enum type is like bellow:
public enum eTable
{
AnalysedMarket,
Blog,
News,
Migration,
}
I created a new class for AnalysedMarket as well to save Users data from our social media area.
[Key]
public int AnalysedMarketId { get; set; }
[StringLength(255)]
public string Images { get; set; }
public int Hits { get; set; }
public string Notes { get; set; }``
Now I created a method in my code repository for extract data using EF and LINQ to get list of AnalysedMarket data but I can't Include my result with Comment table and result of my code repository in the comment section is null always.
public async Task<IEnumerable<AnalysedMarket>> List(int? page, int? perPage, eStatus? status, string userId)
{
var query = _db.AnalysedMarkets.Select(a => a);
if (status.HasValue)
query = query.Where(m => m.Status.Equals(status));
if (!string.IsNullOrEmpty(userId))
query.Where(m => m.CreatedBy.Equals(userId));
query.Include(a => a.Comments.Where(c => c.Table.Equals(eTable.AnalysedMarket) && c.ContentId == a.AnalysedMarketId));
if (page.HasValue && perPage.HasValue)
return await query.OrderBy(a => a.AnalysedMarketId).ToPagedListAsync(page.Value, perPage.Value);
else
return await query.OrderBy(a => a.AnalysedMarketId).ToListAsync();
}
Actually my question is how can I get list of AnalysedMarket data included by Comment data.
And it has a condition and it says include comment if ContentId is equal to AnalysedMarketId and eTable is Table.AnalysedMarket.
I read the articles about conditional Include but I didn't get any thing of them.
Example 1
Example 2
You need to add a reference from AnalysedMarket to comment like this in your AnalysedMarket-Class:
ICollection<Comment> Comments { get; set; }
And then include them while querying your AnalysedMarkets like this:
var query = _db.AnalysedMarkets.Include(c => c.Comments);
/Edit:
Regarding your comment - for this you would need kind of an hierarchy/inheritance structure. It seems to be supported by EfCore and something like this should work:
public class CommentableItem {
ICollection<Comment> Comments {get;set;}
}
public class Comment {
CommentableItem CommentableItem {get;set;}
}
public class AnalysedMarket : CommentableItem {
}
Than you should be able to use the include for each item inheriting from CommentableItem. I did not use the inheritance feature yet (as far as I know this is quite new for EF Core), so for further instructions check the documentation
Using EntityFramework context i need to search with many fields.
The EntityDBContext includes
public class Brand
{
public int BrandID { get; set; }
public string BrandName { get; set; }
public string BrandDesc { get; set; }
public string BrandUrl { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
public class Product
{
public string Name {get;set;}
public DateTime CreatedDate {get;set;}
public DateTime ExpiryDate {get;set;}
//The product class also contains many fields.(not shown here)
}
var context = new EntityDBContext();
I would like to search the brand with using the field in Product.
The fields of the product are only known at run time.
How can i build the expression to search the brand using the product fields.
Please see the screenshot.
Thanks,
Binod
First off, I'm a bit unclear on this part of your question:
the fields of the product are only known at run time.
How so? Can you elaborate on this, because I don't see a working implementation of this using EF. What is your database table (Products presumably) set up for? What properties are in that class?
We need to know that before we can give you an accurate answer. However, I'll give you a more general example, maybe that helps you in understanding.
var all_brands_that_sell_shoes = /* But not always ONLY shoes */
myDataContext.Brands
.Where(brand =>
brand.Products.Any(product =>
product.Type == "Shoe")
)
.ToList();
Edit
If I re-read your question, you don't mean that the Product class' properties are not know until runtime; but that you don't know in advance which filters need to be applied and which need to be skipped?
This second answer assumes that is what you want. Again, I don't know your class' properties since you didn't post them, so I'm inventing my own fields for the sake of example.
First, make an object like below. This will be used to aggregate all filters you wish to apply to the selection:
public class MySearchCriteria
{
public string ProductName_Contains { get; set; }
public int ProductWeight_LessThan { get; set; }
public int ProductType_Id { get; set; }
}
When you want to filter the list, you pass a MySearchCriteria object to the method:
public List<Brand> GetFilteredList(MySearchCriteria filters)
{
var brands = myDataContext.Brands; //All brands (for now)
if(filters.ProductType_Id > 0)
{
//IF the filter has a value, filter the list accordingly:
brands = brands.Where(brand => brand.Products.Any(product => product.TypeId == filters.ProductType_Id);
}
if(filters.ProductWeight_LessThan > 0)
{
//IF the filter has a value, filter the list accordingly:
brands = brands.Where(brand => brand.Products.Any(product => product.Weight < filters.ProductWeight_LessThan));
}
if(!String.IsNullOrWhiteSpace(filters.ProductName_Contains))
{
//IF the filter has a value, filter the list accordingly:
brands = brands.Where(brand => brand.Products.Any(product => product.Name.Contains(filters.ProductName_Contains)));
}
return brands.ToList();
}
This method makes sure that the list was filtered according to the SearchCriteria you provided.
If you didn't use the field filters.ProductName_Contains for example, it would be an empty string, and the if-evaluation would've prevented you from filtering based on an empty string. In the end, you would not have applied a name-based filter.
I hope this is the answer you were looking for? If not, please elaborate more as I'm having trouble understanding what it is you want then.
I am running into a very odd error with EF6. I have uploaded ~38K records on my first pass. Then on my second round, I query the table with a conditional linq statement. That line of code takes about 4 mins to run. This are my entities.
[Table("RAW_ADWORDS")]
public class AdWord
{
[Key]
public int ID { get; set; }
public bool Processed { get; set; }
public string Client { get; set; }
public long ClientID { get; set; }
public bool Active { get; set; }
public bool ProcessedAllFile { get; set; }
public DateTime LastTimeRun{ get; set; }
public DateTime? LastDateTimeProcessed { get; set; }
public virtual List<AdWordCampaign> Campaigns { get; set; }
}
[Table("foobar")]
public class AdWordCampaign
{
[Key]
public int ID { get; set; }
public string Campaign { get; set; }
public long CampaignID { get; set; }
public string Day { get; set; }
public long Clicks { get; set; }
public string CampaignStatus { get; set; }
public long Cost { get; set; }
public long Impressions { get; set; }
public double CTR { get; set; }
public long AvgCPC { get; set; }
public double AvgPosition { get; set; }
public DateTime DownloadDate { get; set; }
}
}
First I run this:
AdWord objAdWord = adwordsContext.AdWords.Where(c => c.ClientID == iCampaignID).FirstOrDefault();
Then
AdWordCampaign objAdWordCampaign = objAdWord.Campaigns.Where(c => c.CampaignID == iElementCampaignID && c.Day == sElementDate).FirstOrDefault();
The line above seems to load ALL the records first before it does the query. Also it still takes 4 mins if I add a Take(5) in the query.
I hope this info will be useful.
Try to add indexes to fields of your table you are including to WHERE in LINQ.
You can always can create extra Views and add it into EF model and do LINQ to them. It will reduce time as well.
If you expect 1 record always try to use SingleOrDefault
Try:
objAdWord.Campaigns.FirstOrDefault(c => c.CampaignID == iElementCampaignID && c.Day == sElementDate)
.Where is an O(n) operation, I'm not sure if the Where then FirstOrDefault clause would be optimized but if it's not you're wasting a lot of time using Where. To improve performance further, ensure that CampaignID is indexed
You need to watch the queries that are generated & executed on the server and make sure they're optimized.
If you're using MS SQL Server, you want to run the SQL Server Profiler tool. Put breakpoints in your code before you call the method that executes the query. Clear the profiler's display, then execute the method. You can capture the SQL from there, then put it into SSMS and view the plan. If the query doesn't use indexes, you need to add indexes that it will use the next time it runs.
I've only ever used Database First, not Code First, so I don't know how you tell Entity Framework to create indexes in the Code First scenario, sorry. But you still need to optimize all of your queries.
I've seen this before with EF when referencing linked objects through a "primary object" - i.e. when you do
AdWordCampaign objAdWordCampaign = objAdWord.Campaigns.Where(...).FirstOrDefault();.
Quite simply it iterates all records one-by-one - and hence the slow query.
If you change to the following, you should get an almost instant response:
AdWord objAdWord = adwordsContext.AdWords.Where(c => c.ClientID == iCampaignID).FirstOrDefault();
AdWordCampaign objAdWordCampaign = <adwordsContext>.Campaigns
.Where(c => <c.AdwordId = objAdWord.Id> && c.CampaignID == iElementCampaignID && c.Day == sElementDate)
.FirstOrDefault();
I've put changes in angular brackets and I'm not sure which property within an AdWordCampaign marks the Id of the AdWord for the relationship from a glance at your model, but I'm sure you get the idea - go direct to Capaigns table via the context, using the AdWord as an addiitonal Where clause, rather than via the AdWord's Campaigns collection.
Using the following code, assume I have 5 different types that I might receive in the variable type. Instead of writing 5 conditional statements, is there a way to write one and use the variable "type" to dictate what the model is, in this case "CommentVote?" Or is this more a deficiency in the way I've designed the data model with each of those 5 things having a "vote" model?
if (type == "comment")
{
CommentVote voteObj = db.CommentVotes
.Where(x => x.UserID == UserID && x.CommentID == id)
.SingleOrDefault();
if (voteObj != null)
{
voteObj.Vote = vote;
db.SaveChanges();
}
else
{
CommentVote c = new CommentVote {
CommentID = id, UserID = UserID, Vote = vote, DateCreated = DateTime.Now
};
db.CommentVotes.Add(c);
db.SaveChanges();
}
count = (db.CommentVotes.Count(x => x.CommentID == id && x.Vote == true) - db.CommentVotes.Count(x => x.CommentID == id && x.Vote == false));
}
Magic Code: The stuff I would love to be able to do.
var modelName = "";
var modelOtherName = "";
if (type == "comment") {
modelName = CommentVote;
modelOtherName = CommentVotes;
}
modelName voteObj = db.modelOtherName
.Where(x => x.UserID == UserID && x.CommentID == id)
.SingleOrDefault();
Update: I'm beginning to think my model may be crap based on some of the reading referenced bellow. So I am including some of that as a reference. Let me know if that's the problem I should be trying to solve.
public class CommentVote
{
public int CommentVoteID { get; set; }
public bool Vote { get; set; }
public DateTime DateCreated { get; set; }
public int UserID { get; set; }
public virtual User User { get; set; }
public int CommentID { get; set; } //This row changes from model to model
public virtual Comment Comment { get; set; } //This row changes from model to model
}
I have a handful of models that are almost identical.
As I understand you question, it more database architecture-related.
If those kind of votes are not very different from each other (in terms of properties) I woldn't use different tables for them. Instead create one Vote table with Type column and (as in the example you provided) nullable column for CommentID.
Then you can use class inheritance to reflect your votes (Vote base class and CommentedVote child class).
Table Per Hierarchy Inheritance in Entity Framework
Update:
Best is not to repeat the same propertieses in all classes. You just use inharitence like this:
public abstract class Vote
{
public int VoteID { get; set; }
public bool isVote { get; set; }
public DateTime DateCreated { get; set; }
public int UserID { get; set; }
public virtual User User { get; set; }
public int VoteType { get; set;} //this property specifies type of vote (e.g. VoteType=1 for CommentedVote )
}
public class CommentVote : Vote
{
public int CommentID { get; set; }
public virtual Comment Comment { get; set; }
}
public class OtherVote : Vote
{
public int OtherID { get; set; }
public virtual Other Other { get; set; }
}
In this very good blog post you can find all possible approches. The one I'm writing about is called Table per Hierarchy (TPH).
You can absolutely reduce the code to a single statement assuming that you perform the same actions and set the same data. In this case, you should have an interface that contains the common actions and data and an object factory to instantiate the correct object based on the type.
You could do it if you implement the Factory pattern with reflection, a very basic example is shown here.
In a nutshell what you do is this: Since you have 5 different types that it could be, you would make 5 different classes that each implement a specific interface. You then create the factory class to use reflection to grab the class that is the most appropriate for your situation (be it with a straight-up class name, like in the example, or with an Attribute over the class, such as here). The factory returns an instance of that interface, which you would then just invoke the exposed method from the interface to do all of this for you.
The best part of this is that if you ever need to make another type, all you'd have to do is add another class with that attribute/name that you would be searching for in the factory. None of your other code would need to be affected, thus making you compliant with the Open/Closed Principle.