Asp.net MVC 4 Code First Return Specific Fields from Navigation Property - entity-framework

Been stuck on this for a while so i thought i would ask. I am sure there is something simple i am missing here. Trying to learn Asp.net mvc 4 on my own by building a simple app.
Here is the model:
public class Category
{
public int Id { get; set; }
[Required]
[StringLength(32)]
public string Name { get; set; }
//public virtual ICollection<Note> Notes { get; set; }
private ICollection<Note> notes;
public ICollection<Note> Notes
{
get
{
return this.notes ?? (this.notes = new List<Note>());
}
}
}
public class Note
{
public int Id { get; set; }
[Required]
public string Content { get; set; }
[Required]
[StringLength(128)]
public string Topic { get; set; }
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
public virtual IEnumerable<Comment> Comments { get; set; }
public virtual ICollection<Tag> Tags {get; set;}
public Note()
{
Tags = new HashSet<Tag>();
}
}
public class Tag
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Note> Notes { get; set; }
public Tag()
{
Notes = new HashSet<Note>();
}
}
I call this method in a repository from the controller:
public IQueryable<Note> GetAll()
{
var query = _db.Notes.Include(x => x.Category).Include(y => y.Tags);
return query;
}
On the home controller i am trying to return a list of all the notes and wanted to include the category name that it belongs to as well as the tags that go with the note. At first the did not show up so i read some tutorials about eager loading and figured out how to get them to show.
However, my method is not that efficient. The mini-profiler is barking at me for duplicate queries because the navigation properties for category and tags are sending queries for the notes again. IS there any way to just return the properties i need for the category and tag objects?
I have tried several methods with no luck. I was hoping i could do something like this:
var query = _db.Notes.Include(x => x.Category.Name).Include(y => y.Tags.Name);
But i get an error: Cannot convert lambda expression to type 'string' because it is not a delegate type
I have seen that error before that was caused by some missing using statements so i already double checked that.
Any suggestions? Thanks for the help

Related

Where should I do the mapping stuff? Repository or Service Layer?

Well, I have this DB Model "Book"
public class Book {
public int Id { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public bool IsSubmitted { get; set; }
public bool IsCompleted { get; set; }
public bool IsDeleted { get; set; }
}
And I have implemented repository pattern where my GetBook(int id) method returns a Book which looks like this:
public Book GetBook(int id) {
return db.Books.Find(id);
}
However, my BookViewModel needs to query some other things as well. It looks like this:
public class BookViewModel
{
public int Id { get; set; }
public string Title { get; set; }
public string AuthorName { get; set; }
public int CommentsCount { get; set; }
public int FeedbacksCount { get; set; }
public int ViewsCount { get; set; }
}
Currently, my service layer is mapping binding models to DB models and passing them to repository.
Now my question is where should I query this additional (view-specific) data? Should I write separate repository methods for CommentsCount, FeedbacksCount, ViewsCount etc. and call them from my service layer to prepare my view model or should I write a new repository method with return type BookViewModel where I query all the required data in a single query?
Any help is highly appreciated.
The repository methods should map and return or recive DTO's, DAL layer should not know about MVC project, it should only know about DTO's.

How do i check whether a collection has been eager loaded with Entity Framework?

I recently used the following code
var errorCount =
split.Profiles.SelectMany(p => p.Logs)
.Count(l => l.LogTypeId == (int)LogType.Error);
errorCount returned zero because I forgot to include my logs table when I built the split entity.
How can I detect whether the split.Profiles.Logs collection has been eager loaded?
I am using Model First.
the class for Profile is
public partial class Profile
{
public Profile()
{
this.Log = new HashSet<Log>();
}
public int ProfileId { get; set; }
public int SplitId { get; set; }
public string Filename { get; set; }
public System.DateTime StartTime { get; set; }
public System.DateTime EndTime { get; set; }
public virtual ICollection<Log> Log { get; set; }
public virtual SplitUpload SplitUpload { get; set; }
}
#Hopeless you pointed me in the right direct. First I had to check the first collection was loaded. Then I needed to check that if it had any members then their child collection was also loaded

If Exists Dont Add Data Entity Framework Many-To-Many

I have these two Models the logic is here One Post can have multiple Categories.
public class Post
{
public Post()
{
this.Categories = new HashSet<Category>();
}
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string ShortDescription { get; set; }
public string PostImage { get; set; }
public string Thumbnail { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime? PublishedDate { get; set; }
public string CreatedBy { get; set; }
public virtual ICollection<Category> Categories { get; set; }
}
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
I have three static categories.
When I am trying to add new post its multiplexing CategoryTable creating new categories with same name ,And Mapping Them in to CategoryPostsTable.
The problem is here i want to map that data with existing categories. I dont want to add new category with same name.
I am using Repository Pattern how should i control that ? Is EF has some solution for that ?
I assume you have code like:
var post = new Post();
post.Categories.Add(cat);
context.Posts.Add(post);
...where cat is a Category object representing an existing category.
The latter Add method (DbSet.Add) doesn't only mark the received entity as Added but all entities in its object graph that are not attached to the context. So cat will also be marked as Added if it wasn't attached yet.
What you can do is
context.Entry(cat).State = System.Data.Entity.EntityState.Unchanged;
Now EF will only create the association to the category, but not insert a new category.

Entity Framework Navigation Property Error

I am getting this error in my .Net MVC 4 web application:
The property 'Username' cannot be configured as a navigation property. The
property must be a valid entity type and the property should have a non-abstract
getter and setter. For collection properties the type must implement
ICollection<T> where T is a valid entity type.
I am very new to Entity Framework and I can't seem to get around this issue. Here is some code:
//DB Context
public class EFDbContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().HasMany(u => u.Roles).WithMany(r => r.Users).Map(x => x.MapLeftKey("Username").MapRightKey("RoleName").ToTable("Users_Roles"));
}
}
//Entity Classes
public class User
{
[Key]
public string Username { get; set; }
public string Password { get; set; }
public string Email { get; set; }
public string Comment { get; set; }
public int Level { get; set; }
public string PasswordQuestion { get; set; }
public string PasswordAnswer { get; set; }
public bool IsApproved { get; set; }
public DateTime LastActivityDate { get; set; }
public DateTime LastLoginDate { get; set; }
public DateTime LastPasswordChangedDate { get; set; }
public DateTime CreationDate { get; set; }
public bool IsOnLine { get; set; }
public bool IsLockedOut { get; set; }
public DateTime LastLockedOutDate { get; set; }
public int FailedPasswordAttemptCount { get; set; }
public DateTime FailedPasswordAttemptWindowStart { get; set; }
public int FailedPasswordAnswerAttemptCount { get; set; }
public DateTime FailedPasswordAnswerAttemptWindowStart { get; set; }
[InverseProperty("RoleName")]
public virtual ICollection<Role> Roles { get; set; }
public override string ToString()
{
return this.Username;
}
}
public class Role
{
[Key]
public string RoleName { get; set; }
public int Level { get; set; }
[InverseProperty("Username")]
public virtual ICollection<User> Users { get; set; }
public override string ToString()
{
return this.RoleName;
}
}
//Repository
public class EFUsersRepository : IUsersRepository
{
private EFDbContext context = new EFDbContext();
public IQueryable<User> Users
{
get { return context.Users; }
}
public User GetUser(string username)
{
return context.Users.Find(username); //THIS IS WHERE THE CRASH OCCURS
}
}
//DB Setup
Table Users, Role and Users_Role. Users_Role is a simple linking table with [username, role] columns both of type varchar.
The database tables columns & types match the two classes above (User,Role).
I inherited this project which was unfinished but I can't get it to run successfully. Any help understanding what the issue is would be helpful. Thanks!
It might be that Entity Framework is updated. Easiest way will be to recreate the DataModel.
Even if the previous programmer did not use Entity Data Mode, you can at least copy the auto generated code such as EFDbContext, Users and Roles classes.
It turns out, after commenting out enough items all day long, the the following lines are what caused this error for me:
[InverseProperty("RoleName")] //In file User.cs (as shown above)
[InverseProperty("UserName")] //in file Role.cs (as shown above)
I am still learning Entity Framework and I don't know why this was the solution, but it stopped the error which I reported above.
I hope that this helps someone else and if anyone wants to help me understand what the issue was in detail, please feel free. I am eager to learn.

Circular Reference error when serializing objects in ASP.NET Web API

I'm writing a Web API project in C# that uses Entity Framework to pull data from a DB, serialize it and send it to a client.
My project has 2 classes, Post and Comment (foreign key from Post).
These are my classes.
Post class:
public partial class Post
{
public Post()
{
this.Attachment = new HashSet<Attachment>();
this.Comment = new HashSet<Comment>();
}
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public System.DateTime Created { get; set; }
public Nullable<System.DateTime> Modified { get; set; }
public virtual ICollection<Attachment> Attachment { get; set; }
public virtual ICollection<Comment> Comment { get; set; }
}
Comment class:
public partial class Comment
{
public int CommentId { get; set; }
public string Content { get; set; }
public System.DateTime Posted { get; set; }
public bool Approved { get; set; }
public int AnswersTo { get; set; }
public int PostId { get; set; }
public virtual Post Post { get; set; }
}
My problem is that when I try to get via Web API a Post, it spits me the following error:
Object graph for type 'APIServer.Models.Comment' contains cycles and cannot be serialized if reference tracking is disabled.
And when I try to get a Comment via Web API, the error is as follows:
Object graph for type 'System.Collections.Generic.HashSet`1[[APIServer.Models.Comment, APIServer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]' contains cycles and cannot be serialized if reference tracking is disabled.
If I annotate the Comment class with
[DataContract(IsReference = true)]
the errors disappear, but the serialization only returns the ID of the comment and ignores the other fields.
Any suggestions on how to solve this?
Thanks in advance,
Léster
Here are 2 solutions
Solution #1:
I had this same problem and so I decorated my class with DataContract and the members with DataMember like you mention. HOWEVER, I don't like editing auto-generated code directly because I have to redo it every time I regenerate the file. In order to get around this, I used the MetadataType attribute. In your case, it would look like this...
First, you will keep the auto generated entity as is:
public partial class Comment
{
public int CommentId { get; set; }
public string Content { get; set; }
public System.DateTime Posted { get; set; }
public bool Approved { get; set; }
public int AnswersTo { get; set; }
public int PostId { get; set; }
public virtual Post Post { get; set; }
}
Next, in another file, you will create another partial class and decorate it like this:
[MetadataType(typeof(Metadata))]
[DataContract(IsReference = true)]
public partial class Comment
{
private class Metadata
{
[DataMember]
public int CommentId { get; set; }
[DataMember]
public string Content { get; set; }
[DataMember]
public System.DateTime Posted { get; set; }
[DataMember]
public bool Approved { get; set; }
[DataMember]
public int AnswersTo { get; set; }
[DataMember]
public int PostId { get; set; }
[DataMember]
public virtual Post Post { get; set; } // you can remove "virtual" if you wish
}
}
MetadataType will essentially add the attributes from the Metadata buddy class to the ones with the same name in Comment (not directly, but for our purposes, it's close enough... that's a topic for a different post). Of course, if your Comment entity changes, you'll need to update this accordingly.
Solution #2:
Having to edit your second file every time you make a change is only a slight improvement from directly editing auto-generated files. Fortunately, there is another approach that is much easier to maintain. Details can be found here but as a summary, all you need to do is decorate your OperationContract that is consuming Comment with an additional attribute, ReferencePreservingDataContractFormat. Note that there is a slight error in the code provided on that page that would cause infinite recursion. As noted in this post, the fix is quite simple: instead of recursing at all, just create a new DataContractSerializer
The advantage to this approach is that no matter how much you change Comment, you still don't need update anything.
As an example for your code, let's say you are using Comment as follows:
[OperationContract]
Comment FindComment(string criteria);
All you need to do is add
[OperationContract]
[ReferencePreservingDataContractFormat]
Comment FindComment(string criteria);
And then somewhere else you need to define ReferencePreservingDataContractFormat which will look like this:
//From http://blogs.msdn.com/b/sowmy/archive/2006/03/26/561188.aspx and https://stackoverflow.com/questions/4266008/endless-loop-in-a-code-sample-on-serialization
public class ReferencePreservingDataContractFormatAttribute : Attribute, IOperationBehavior
{
public void AddBindingParameters(OperationDescription description, BindingParameterCollection parameters)
{
}
public void ApplyClientBehavior(OperationDescription description, System.ServiceModel.Dispatcher.ClientOperation proxy)
{
IOperationBehavior innerBehavior = new ReferencePreservingDataContractSerializerOperationBehavior(description);
innerBehavior.ApplyClientBehavior(description, proxy);
}
public void ApplyDispatchBehavior(OperationDescription description, System.ServiceModel.Dispatcher.DispatchOperation dispatch)
{
IOperationBehavior innerBehavior = new ReferencePreservingDataContractSerializerOperationBehavior(description);
innerBehavior.ApplyDispatchBehavior(description, dispatch);
}
public void Validate(OperationDescription description)
{
}
}
class ReferencePreservingDataContractSerializerOperationBehavior : DataContractSerializerOperationBehavior
{
public ReferencePreservingDataContractSerializerOperationBehavior(OperationDescription operationDescription) : base(operationDescription) { }
public override XmlObjectSerializer CreateSerializer(Type type, string name, string ns, IList<Type> knownTypes)
{
return new DataContractSerializer(type, name, ns, knownTypes,
0x7FFF, //maxItemsInObjectGraph
false, //ignoreExtensionDataObject
true, //preserveObjectReferences
null //dataContractSurrogate
);
}
public override XmlObjectSerializer CreateSerializer(Type type, XmlDictionaryString name, XmlDictionaryString ns, IList<Type> knownTypes)
{
return new DataContractSerializer(type, name, ns, knownTypes,
0x7FFF, //maxItemsInObjectGraph
false, //ignoreExtensionDataObject
true, //preserveObjectReferences
null //dataContractSurrogate
);
}
}
And that's it!
Either method will work just fine--pick the one that works for you.
You can disable Lazy Loading on your Comment class by removing virtual from the Post property definition...
public partial class Comment
{
public int CommentId { get; set; }
public string Content { get; set; }
public System.DateTime Posted { get; set; }
public bool Approved { get; set; }
public int AnswersTo { get; set; }
public int PostId { get; set; }
public Post Post { get; set; }
}
This should sort out the circular reference exception.