How to avoid to load collection property in Entity Framework - c#

When I select artile, it select user, but user has a collection of article, so article select user again. May be recursive cause out of memory ,
The calling processing is :
article=>user=>article=>user...
ef entities is :
public partial class article
{
public int id { get; set; }
public string title { get; set; }
public string cont { get; set; }
public Nullable<int> uid { get; set; }
public System.DateTime addtime { get; set; }
public Nullable<int> colid { get; set; }
public virtual user user { get; set; }
public virtual column column { get; set; }
}
public partial class user
{
public user()
{
this.roleusers = new HashSet<roleuser>();
this.articles = new HashSet<article>();
}
public int id { get; set; }
public string email { get; set; }
public string uname { get; set; }
public string upass { get; set; }
public virtual ICollection<roleuser> roleusers { get; set; }
public virtual ICollection<article> articles { get; set; }
}
mysql EF operation class is :
public class ArtDao
{
readonly crmEntities _ent = new crmEntities();
public List<article> PageArts(int start, int limit, out int total)
{
var ll =
_ent.articles.OrderByDescending(o => o.id)
.Skip(start)
.Take(limit)
.ToList();
total = _ent.articles.Count();
return ll;
}
}
How to avoid to eager load the collection property roleusers and articles ?

You need to set LazyLoad on you edmx properties, and manually load only first level childs with Include() method when selecting:
public List<article> PageArts(int start, int limit, out int total)
{
var ll =
_ent.articles.OrderByDescending(o => o.id)
.Skip(start)
.Take(limit)
.Include(o => o.user)
.ToList();
total = _ent.articles.Count();
return ll;
}
You need to implement it in another class, wich can be a partial class.

Related

Lookup for an Entity Column ID

I have an Entity Framework Core project that uses generic repositories and UnitOfWork and is working as expected.
The database is one to many and related by IDs.
The RTCTrials entity contains a FK CourseID related to RTCCourses PK. When loading trials I am trying to get the course name in the datagrid and only achieved by using a union. Is this inefficient and a simpler approach. Ideally I would add a dropdownlist column populated with RTCCourses in the trials grid template and the CourseID in the trials table would select the correct id and show the ValueMember course name.
This is what the current method looks like:
using (var context = new RTCContext())
{
var factory = new EntityFrameworkUnitOfWorkFactory(context);
var unit = factory.Create();
var festivals = unit.RTCFestivals.All().ToList();
var trials = unit.RTCTrials.All().ToList();
var courses = unit.RTCCourses.All().ToList();
var trialcourses = trials.Join(courses, courses => courses.CourseID, trials => trials.CourseID, (trials, courses) => new
{
TrialID = trials.TrialID,
FestivalID = trials.FestivalID,
CourseID = trials.CourseID,
Trial = trials.Trial,
Course = courses.CourseName,
TrialGrade = trials.TrialGrade,
TrialDistance = trials.TrialDistance,
TrialAge = trials.TrialAge,
TrialHurdles = trials.TrialHurdles,
TrialAllowances = trials.TrialAllowances,
TrialMonth = trials.TrialMonth,
TrialActualDate = trials.TrialActualDate,
TrialActualTime = trials.TrialActualTime,
TrialRaceCard = trials.TrialRaceCard,
TrialQualifiers = trials.TrialQualifiers
}).ToList();
this.radGridViewFestivalDestinations.DataSource = festivals;
this.radGridViewFestivalDestinations.Templates[0].DataSource = trialcourses;
foreach (GridViewDataColumn column in radGridViewFestivalDestinations.MasterTemplate.Columns)
{
column.BestFit();
}
foreach (GridViewDataColumn column in radGridViewFestivalDestinations.Templates[0].Columns)
{
column.BestFit();
}
}
RTCTrial Entity
public partial class RTCTrial {
public RTCTrial()
{
this.RTCResults = new List<RTCResult>();
this.RTCWeathers = new List<RTCWeather>();
OnCreated();
}
public virtual int TrialID { get; set; }
public virtual int FestivalID { get; set; }
public virtual int CourseID { get; set; }
public virtual string Trial { get; set; }
public virtual string TrialGrade { get; set; }
public virtual string TrialDistance { get; set; }
public virtual string TrialAge { get; set; }
public virtual int? TrialHurdles { get; set; }
public virtual string TrialAllowances { get; set; }
public virtual string TrialMonth { get; set; }
public virtual DateTime? TrialActualDate { get; set; }
public virtual TimeSpan? TrialActualTime { get; set; }
public virtual string TrialRaceCard { get; set; }
public virtual int TrialQualifiers { get; set; }
public virtual RTCCourse RTCCourse { get; set; }
public virtual RTCFestival RTCFestival { get; set; }
public virtual IList<RTCResult> RTCResults { get; set; }
public virtual IList<RTCWeather> RTCWeathers { get; set; }
#region Extensibility Method Definitions
partial void OnCreated();
#endregion
}
RTCCourse Entity
public partial class RTCCourse {
public RTCCourse()
{
this.RTCTrials = new List<RTCTrial>();
OnCreated();
}
public virtual int CourseID { get; set; }
public virtual string CourseName { get; set; }
public virtual string CourseCountry { get; set; }
public virtual string CourseDirection { get; set; }
public virtual string CourseCharacteristics { get; set; }
public virtual string CourseAlternateName { get; set; }
public virtual double CourseLat { get; set; }
public virtual double CourseLong { get; set; }
public virtual IList<RTCTrial> RTCTrials { get; set; }
#region Extensibility Method Definitions
partial void OnCreated();
#endregion
}
Regards, Neil
Suggestion would be on the returned courses you would want each course to have its associated trials. In the unit of work that returns all courses - possibly have an option to include them. Your dropdown would bind to each course and your grid would bind to the list of trials in the selected course.
public IEnumerable<RTCCourse> All(bool includeTrials = false)
{
var q = context.RTCCourses;
if (includeTrials)
{
q = q.Include(c => c.RTCTrials)//.ThenInclude(t => t.RTCResults)
;
}
return q.AsEnumerable(); // assuming that is the returned type
}
That should allow your courses to have the list of trials set. Then there is no need to get all trials. And you can bind to courses (and list of trials within each) directly instead of doing the join and binding to the anonymous.
Of 'course' -- this is merely a suggestion ;)

LINQ Order by column x and if empty take column y

I'm building a simple messaging system with the following data model:
public partial class Conversation
{
public Conversation()
{
this.Messages = new HashSet<Message>();
this.Customers = new HashSet<Customer>();
this.MessagingHubConnections = new HashSet<MessagingHubConnection>();
}
public int Id { get; set; }
public int BoatId { get; set; }
public System.DateTime TimeCreated { get; set; }
public virtual ICollection<Message> Messages { get; set; }
public virtual Boat Boat { get; set; }
public virtual ICollection<Customer> Customers { get; set; }
public virtual ICollection<MessagingHubConnection> MessagingHubConnections { get; set; }
}
public partial class Message
{
public int Id { get; set; }
public int ConversationId { get; set; }
public string Text { get; set; }
public bool IsRead { get; set; }
public System.DateTime TimeSend { get; set; }
public int CustomerId { get; set; }
public virtual Conversation Conversation { get; set; }
public virtual Customer Customer { get; set; }
}
When a customer opens his account dashboard, I want to display a list of all the conversations. This should be ordered according to the following rule: The first conversation in the list is the one with a message with the latest Message.TimeSent. If the conversation has no messages, it has to pick the Conversation.TimeCreated.
The code below is what I have right now but this is obviously not working when a Conversation has no messages. The variable conversations in the code below is an IQueryable<Conversation>.
var orderedConversations = conversations.OrderByDescending(c => c.Messages.Max(m => m.TimeSend));
Anyone who can help me out?
By projecting the TimeSend into DateTime? before Max()-ing it, you can obtain a (DateTime?)null when the collection is empty instead of getting an InvalidOperationException. And then, you can null-coalesce this result with TimeCreated:
var orderedConversations = conversations
.OrderByDescending(c =>
c.Messages
.Select<Message, DateTime?>(x => x.TimeSend)
.OrderByDescending(x => x)
.FirstOrDefault() ??
c.TimeCreated);

ASP.NET MVC How to access Entity Framework generated foreign key?

Song has a many to many relationship with it's self. I store these ids in a class called SimilarVersion with both id columns.
public class Song
{
public int Id { get; set; }
public string AudioName { get; set; }
public string ArtistName { get; set; }
...
public virtual ICollection<SimilarVersion> SimilarVersions { get; set; } = new List<SimilarVersion>();
}
public class SimilarVersion
{
public int Id { get; set; }
public int? Song_Id1 { get; set; }
}
View Models:
public class SongDto
{
public int Id { get; set; }
public string AudioName { get; set; }
public string ArtistName { get; set; }
...
public ICollection<SimilarSongDto> SimilarSongDtos { get; set; } = new List<SimilarSongDto>();
}
public class SimilarSongDto
{
public int Id { get; set; }
public string AudioName { get; set; }
public string ArtistName { get; set; }
...
}
The SimilarVersion table in my database now has Id,Song_Id,Song_Id1, as EF has generated Song_Id. How do I get to use that EF generated column in my code though?
_similarVersionService.GetSimiliarVersion().Song_Id will give me an error because there is no property in the class called that. I could manually add it to the class like I have done with Song_Id1 and remove the collection from the other class but I think I must be doing something wrong. Also please tell me if there is a better way of mapping this.
I tried adding public int Song_Id { get; set; } and it just made another column in my table called Song_Id2.
public ActionResult Song(int id)
{
//Map the domainModel to songViewModel
var songDto = Mapper.Map<Song, SongDto>(_songService.GetSong(id));
//Get all of the songs where the id == the Song_Id column in similar version table
var songs = _songService.GetSongs().ToList()
.Where(x => x.SimilarVersions.Any(z => z.Song_Id == songDto.Id))
.ToList(); //z.Song_Id no definition found
//Map the Song domain to SimilarSong ViewModel and assign it to the songDto to be passed to the view
songDto.SimilarSongDtos = Mapper.Map<ICollection<Song>, ICollection<SimilarSongDto>>(songs);
return View(songDto);
}
Edit. Trying to add to a row based on Admir answer:
var songToUpload = new Song
{
AudioName = uploadSongDtos[i].AudioName.Trim(),
ArtistName = uploadSongDtos[i].ArtistName,
};
foreach (var compareAgainstString in _songService.GetSongs().ToDictionary(x => x.Id, x => x.AudioName))
{
var score = SearchContext.Levenshtein.iLD(songToUpload.AudioName, compareAgainstString.Value);
//Don't add the current song
if (score < 50 && songToUpload.Id != compareAgainstString.Key)
songToUpload.SimilarVersionsWhereSimilar.Add(new SimilarVersion { SimilarId = compareAgainstString.Key });
}
Both OriginalId and SimilarId are assigned to whatever the id of songToUpload.Id is given the relationship we defined in models, which is correct for OriginalId but it is also overriding my custom set SimilarId above. How can I stop this?
You can take this approach:
public class Song
{
public int Id { get; set; }
public string ArtistName { get; set; }
public virtual IList<Similarity> SimilaritiesWhereOriginal { get; set; }
public virtual IList<Similarity> SimilaritiesWhereSimilar { get; set; }
}
public class Similarity
{
public int Id { get; set; }
public int OriginalId { get; set; }
public virtual Song Original { get; set; }
public int SimilarId { get; set; }
public virtual Song Similar { get; set; }
}
public class ApplicationDbContext : DbContext
{
public DbSet<Song> Songs { get; set; }
public DbSet<Similarity> Similarities { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Song>().HasMany(x => x.SimilaritiesWhereOriginal).WithRequired(x => x.Original).WillCascadeOnDelete(false);
modelBuilder.Entity<Song>().HasMany(x => x.SimilaritiesWhereSimilar).WithRequired(x => x.Similar).WillCascadeOnDelete(false);
base.OnModelCreating(modelBuilder);
}
}
Similarity class shows relationship between "original" song and "similar" song. This class replaces EF auto-generated table with your own many-2-many relationship table that you can access from the code.
It is likely the ID is actually generated by your Store. Call Context.SaveChanges() to create it, then read the ID.

In MVC Model, how do I lookup a field in a different table?

So I have a model:
[Table("Site")]
public class Store : MPropertyAsStringSettable {
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public string Name { get; set; }
public int CompanyID { get; set; }
public bool IsActive { get; set; }
public string Address { get; set; }
public string City { get; set; }
[Display(Name = "Province")]
public int ProvinceID { get; set; }
public string Postal { get; set; }
public string Phone { get; set; }
public int StoreNumber { get; set; }
public bool visible { get; set; }
public DateTime lastShift { get; set; }
}
The field lastShift is from a different table called "Shifts", how do I get it from that table?
EDIT: The lookup will have to be something like this:
select top 1 shiftDate as lastShift from [Shifts] where SiteID = Store.ID
This is how I load my data:
public class MyDbContext: DbContext {
public MyDbContext()
: base("name=DefaultConnection") {
}
public DbSet<UserAccount> UserAccounts { get; set; }
public DbSet<Company> Companies { get; set; }
public DbSet<Store> Stores { get; set; }
public DbSet<ProvinceModel> Provinces { get; set; }
}
And this is how I use it:
MyDbContext database = new MyDbContext();
var myStores = from database.Stores select ID;
EDIT according to your last edit, this is not the case
It will need to be a "navigation property" which means that you'll need to have an explicit (Foreing Key) relationship between Site and Ship
Then you'll have something like this
Store.Shift.LastShift
But if it is a one to many relationship (and LastShift field is not part of Shift table) then
you'll need to do it manually, and use a view model or a custom property that it is not mapped directly to the table and do the assignment somewhere in your code
If you're using a Repository, then you'll need to add a method there to get the last shift
Then (or if you are using ORM directly) you use the code that #Cristi posted, just remember to add the sorting
public ActionResult MyAction(){
var store = db.Stores.Where(x => x.ID == objId).Select(x => new StoreModel(){
Name = x.Name,
ID = x.ID,
lastShift = db.Shifts.FirstOrDefault(y => y.SiteID == x.ID).OrderByDescending(shift => shift.Date);
}).FirstOrDefault();
return View(store);
}
Here is how I solved the problem.
TL,DR: I shared my dbcontext in my controller so I have access to it in my models!
I manually load the lastShiftTime in my Store Model with the following code:
public class Store : MPropertyAsStringSettable {
.......
public DateTime lastShiftTime {
get{
MyDbContext curContext = MyWebProject.Controllers.MyBaseController.database;
if (curContext != null) {
return (from shift in curContext.Shifts where shift.SiteID == ID select shift.ShiftDate).First();
} else {
return new DateTime(0);
}
}
}
}
Here is my Shift Model I created for this, very standard:
[Table("Shifts")]
public class Shift : MPropertyAsStringSettable {
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public int SiteID { get; set; }
public string ShiftID_In_POS { get; set; }
public DateTime ShiftDate { get; set; }
}
And here is how I am sharing the context in controller:
public class MyBaseController : Controller {
.........
private static MyDbContext _database;
public static MyDbContext database {
get {
if (_database == null) {
_database = new MyDbContext();
}
return _database;
}
}
}

LINQ query return Grandparent, Parent, Grandchild where Grandchild has value

I'm building a system for producing surveys and handling the responses, I have a viewmodel SurveyTakeViewModel
namespace Survey.ViewModel
{
public class SurveyTakeViewModel
{
public Models.Survey Survey { get; set; }
public bool TakenBefore { get; set; }
}
}
namespace Survey.Models
{
public class Survey
{
[Key]
public int SurveyId { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
public string MessageStart { get; set; }
public string MessageEnd { get; set; }
public virtual ICollection<Question> Question { get; set; }
}
}
namespace Survey.Models
{
public class Question
{
public virtual int SurveyId { get; set; }
[Key]
public int QuestionId { get; set; }
public int DisplayOrder { get; set; }
public string QuestionText { get; set; }
public string QuestionNote { get; set; }
public int QuestionTypeId { get; set; }
public virtual QuestionType QuestionType { get; set; }
public virtual ICollection<QuestionAnswerOption> QuestionAnswerOption{ get; set; }
public virtual ICollection<QuestionAnswer> QuestionAnswer { get; set; }
}
}
namespace Survey.Models
{
public class QuestionAnswer
{
public virtual int QuestionId { get; set; }
[Key]
public int QuestionAnswerId { get; set; }
public int SurveyId { get; set; }
public string UserId { get; set; }
public string QuestionActualResponse { get; set; }
}
}
It all works fine for the questionnaire, however when a user revisits a questionnaire they have previously answered I want to populate the QuestionAnswer part of the viewmodel with answers only by a specific userid, at the moment I get everyanswer. I have tried loads of different Linq queries, initially my ICollections<> were List<>, which I am told could cause all records to be returned.
at the moment I am using
Survey = db.Survey.FirstOrDefault(s => s.SurveyId == 1)
which returns all QuestionAnswer
I have tried things like
Survey = db.Survey.FirstOrDefault(a => a.Question
.Any(b => b.QuestionAnswer
.Any(c => c.UserId == userId)));
but it still returns all QuestionAnswer for every userId
It seems like you know which survey you want, but when you access the various properties, they are populating with extra information... you'd like fewer grandchild records.
I don't know enough LinqToEntities to limit the loading in the way that is needed (LinqToSql would use DataLoadOptions.LoadsWith and DataLoadOptions.AssociateWith). Instead, I offer this manual shaping of the data - after loading. Perhaps it will help an expert understand the question and then they can express it in a LinqToEntities query.
int surveyId = GetSurveyId();
int userId = GetUserId();
Survey mySurvey = db.Survey.FirstOrDefault(s => s.SurveyId == surveyId);
ILookup<int, QuestionAnswer> qaLookup = db.QuestionAnswer
.Where(qa => qa.SurveyId == surveyId && qa.UserId == userId)
.ToLookup(qa => qa.QuestionId);
foreach(Question q in mySurvey.Questions)
{
//limit q.QuestionAnswer to only this user's answers.
q.QuestionAnswer = qaLookup[q.QuestionId].ToList();
}

Categories