I am having some issues avoiding serialization of certain ICollection properties in Web Api. It seems the common suggestion to avoiding serialization is to add IgnoreDataMember or JsonIgnore. I don't want to serialize the one-to-many and one-to-one properties.
I have the following model:
public class Player
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key, Column(Order=0)]
public Guid PlayerId { get; set; }
[IgnoreDataMember]
public virtual ICollection<Player> Friends { get; set; }
[IgnoreDataMember]
[Required]
public string Password { get; set; }
[MaxLength(100)]
[Index(IsUnique = true)]
public string Username { get; set; }
[IgnoreDataMember]
public virtual ICollection<WordChallenge> IssuedChallenges { get; set; }
[IgnoreDataMember]
public virtual ICollection<WordChallenge> ReceivedChallenges { get; set; }
}
However when I do a POST to the endpoint I'm getting the following exception:
The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.
In this case it should only be attempting to serialize PlayerId and Username
Since it was requested, the following manages the dbContext:
public async Task<IEnumerable<Player>> GetFriends(Guid playerId)
{
//Handles common exceptions and manages the dbcontext. In this case context is disposed off after the interaction is done.
return await DbInteraction(
async dbModel =>
{
var player = await GetPlayerById(playerId, dbModel);
return player.Friends.ToList();
});
}
Controller:
// GET: api/Friend/5
public async Task<ReturnObject<IEnumerable<Player>>> Get(Guid token, Guid id)
{
var playerService = new PlayerService(base._wordModelFactory);
var fields = await playerService.GetFriends(id);
return ReturnData(fields);
}
Instead of marking members you don't want to be serialized with IgnoreDataMember, in your case (and in general) it's better to mark class with DataContact and mark members you want to be serialized with DataMember.
Still you might wonder why you observe such behavior with IgnoreDataMember and navigation properties. My guess would be - to support lazy loading when you access navigation property, EF might create dynamic proxy class for your POCO class. Note that your navigation properties are marked with "virtual". That is for EF to be able to override them in that dynamic proxy class and add lazy loading behavior. IgnoreDataMember attribute is not inherited, and so in that inherited proxy class navigation properties are not longer marked with it, and so serializer will try to include them.
Related
I have two entity classes that have a one-to-many relationship.
public class Call : IEntity
{
public int Id { get; set; }
public int UserId { get; set; }
public virtual User User { get; set; }
}
public class User : IEntity
{
public int Id { get; set; }
public string Username { get; set; }
public virtual ICollection<Call> Calls { get; set; }
}
And I have a view model for 'Call' operations on the web layer.
public class CallVm : IViewModel
{
public string Id { get; set; }
public string UserFullname { get; set; }
}
And I use a method to convert my 'Call' object to 'CallVm' object.
This method is briefly as follows.
public CallVm MapCallVm(Call call)
{
return call == null ? null : new CallVm { Id = call.Id, UserFullname = call.User?.Fullname };
}
When I read the 'Call' entity from the database, I sometimes include 'User' and sometimes I don't. When I do not include it, there is no User property definition in the Call object because it is lazy loading. Therefore, I get the following error in MapCallVm method.
The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.
Is there a way to check this? I just want to assign UserFullname = call.User?.Fullname when there is a eager load.
The only solution I can think of is controlling with try-catch. Is there a different solution?
You can use DbReferenceEntry.IsLoaded Property.
Gets or sets a value indicating whether the entity has been loaded
from the database.
if (_dbContext.Entry(Call).Reference(e => e.User).IsLoaded)
Updated
If you are getting value without dbContext, you should force the query to Eager loading instead.
Read the following post to have a better understanding.
Should we disable lazy loading of Entity Framework in web apps?
As #Phong's answer - avoid passing DbContext. Normally, your repository class should map DB entities to simple POCO/DTO objects.
I suggest to introduce mapper class. This will help you to unit test your logic
// Interface to inject to repository
public interface ICallMapper
{
CallVm Map(Call call);
}
public class CallMapper : ICallMapper
{
public CallVm Map(Call call)
{
return call == null ? null : new CallVm { Id = call.Id, UserFullname = call.User?.Username };
}
}
Pass mapper to repository and ensure that your objects are not connected with DB anymore
public class CallRepository : ICallRepository
{
private readonly ICallMapper _callMapper;
public CallRepository(ICallMapper callMapper)
{
_callMapper = callMapper;
}
public IList<CallVm> GetList()
{
// Call DB and get entities
var calls = GetCalls();
// Map DB entities to plain model
return calls.Select(_callMapper.Map).ToList();
}
}
This lets you to get rid of your error. And makes your program more structable and testable.
I am using EF 6. I have a table in db for which the auto-generated class looks like this:
public partial class tblPreparation
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public tblPreparation()
{
this.tblPreparationItem = new HashSet<tblPreparationItem>();
}
public int id { get; set; }
public string name { get; set; }
public System.DateTime date { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<tblPreparationItem> tblPreparationItem { get; set; }
}
In my code, I want this class to extend another class EntityObject, which is in the namespace System.Data.Entity.Core.Objects.DataClasses (and implement another interface). So I created wrote this partial class:
public partial class tblPreparation : EntityObject, IMyInterface
{
}
It doesn't throw a syntax error but when I run the application I get runtime error: "The type 'tblPreparation' was not mapped. Check that the type has not been explicitly excluded by using the Ignore method or NotMappedAttribute data annotation. Verify that the type was defined as a class, is not primitive or generic, and does not inherit from EntityObject." What am I missing?
I assum it's because they are not in the same namespace. Is there a way to fix this?
I may have misunderstood your comment on the namespace, but for clarity, a Partial class is only actually a partial class when it is in the same namespace of the corresponding partial class, otherwise what you have are just two separate single classes with the same name claiming to be partial. If this is the case, the fix is simple. Put them in same namespace.
However, it is more likely due to adding the EntityObject to the class hierarchy, as oerkelens mentioned. EF 6 creates proxies of your POCOs, for this reason your classes must have parameterless constructors. Adding another class may prevent the db context from creating proxies of your objects.
Remove just that class from the hierarchy, check whether you can materialise these entities to verify or rule it out.
Edit - No, it definitely is due to EntityObject.
I reproduced this by first having my entity implement some interface in a partial class. That worked great. Then I had partial class inherit from EntityObject that failed with your error.
After reproducing this error, I created a class called MyStupidClass and replaced EntityObject with MyStupidClass and I could still materialise entities (even with the top level properties of EntityObject).
So it depends on the class you introduced to the hierarchy.
class Program
{
static void Main(string[] args)
{
using (var db = new schedulerEntities())
{
var schedules = db.Schedules.ToArray();
foreach (var schedule in schedules)
{
Console.WriteLine($"{schedule.Cron} - {schedule.FriendlyDescription}");
}
}
Console.ReadLine();
}
}
public partial class Schedule: MyStupidClass, IScheduler
{
public string FirstName { get; set; }
}
public class MyStupidClass
{
public EntityKey EntityKey { get; set; }
public EntityState State { get; set; }
}
interface IScheduler
{
long Id { get; set; }
string Name { get; set; }
string Cron { get; set; }
}
Using EF 6, Lazy Loading Enabled is set to True in the model. Here's an example of my problem:
var agent = context.AgentDetail.Where(a => a.Agent.GroupCode == "1234");
Running that will return 5 results. If after that I run (for the purpose of testing only)
var code = agent.FirstOrDefault().Agent.GroupCode;
I get a null reference exception because Agent is null.
Here are my entities:
public partial class AgentDetail : Entity<int>
{
public Nullable<System.DateTime> Date { get; set; }
public string Name { get; set; }
public decimal Balance { get; set; }
...
public virtual Agent Agent { get; set; }
}
public partial class Agent : Entity<int>
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Agent()
{
this.AgentAspNetUsers = new HashSet<AgentAspNetUsers>();
this.AgentDetail = new HashSet<AgentDetail>();
}
public string GroupCode { get; set; }
...
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<AgentAspNetUsers> AgentAspNetUsers { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<AgentDetail> AgentDetail { get; set; }
}
How could it give me 5 results in the first query, then? I can't figure out what's wrong here, any help would be appreciated.
From Requirements for Creating POCO Proxies
A custom data class must be declared with public access.
A custom data class must not be sealed
A custom data class must not be abstract
A custom data class must have a public or protected constructor that does not have parameters. Use a protected constructor without parameters if you want the CreateObject method to be used to create a proxy for the POCO entity. Calling the CreateObject method does not guarantee the creation of the proxy: the POCO class must follow the other requirements that are described in this topic.
The class cannot implement the IEntityWithChangeTracker or IEntityWithRelationships interfaces because the proxy classes implement these interfaces.
The ProxyCreationEnabled option must be set to true.
Each navigation property must be declared as public, virtual (Overridable in Visual Basic), and not sealed (NotOverridable in Visual Basic) get accessor. The navigation property defined in the custom data class must have a corresponding navigation property in the conceptual model. For more information, see Loading Related POCO Entities.
Check this points on your classes. In your pasted code AgentDetail havent public/protected constructor.
Try to define relationship between entities. It should work if your lazy loading has enabled.
I'm using MVC5 with EF6 .I'm getting the below conversion Error
Cannot implicitly convert type
System.Collections.Generic.List<TreaceabilitySystem.GLB_M_PROFITCENTER>
to
System.Collections.Generic.List<TreaceabilitySystem.Models.Profitcenter>
private TSEntities db = new TSEntities();
// GET: Profitcenter
public ActionResult Index()
{
List<Profitcenter> profitcenter = new List<Profitcenter>();
profitcenter = db.GLB_M_PROFITCENTER.ToList(); //Error coming up here
return View(profitcenter.ToList());
}
My models are here:
This Model created through EF when i add table in .edmx
public partial class GLB_M_PROFITCENTER
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public GLB_M_PROFITCENTER()
{
this.GLB_M_USERMASTER = new HashSet<GLB_M_USERMASTER>();
}
public string PROFITCENTER_CODE { get; set; }
public string PROFITCENTER_NAME { get; set; }
public string DESCRIPTION { get; set; }
public bool ISACTIVE { get; set; }
public int CREATEDBY { get; set; }
public System.DateTime CREATED_DATE { get; set; }
public Nullable<int> UPDATEDBY { get; set; }
public Nullable<System.DateTime> UPDATED_DATETIME { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<GLB_M_USERMASTER> GLB_M_USERMASTER { get; set; }
}
And I have created the below model for change the display name and validation purposes
[MetadataType(typeof(Profitcenter))]
public partial class GLB_M_PROFITCENTER { }
public class Profitcenter
{
[Required(ErrorMessage = "*")]
[DisplayName("Profitcenter Code")]
public string PROFITCENTER_CODE { get; set; }
[Required(ErrorMessage = "*")]
[DisplayName("Profitcenter Name")]
public string PROFITCENTER_NAME { get; set; }
[DisplayName("Description")]
public string DESCRIPTION { get; set; }
[DisplayName("Is Active")]
public bool ISACTIVE { get; set; }
[DisplayName("Created By")]
public int CREATEDBY { get; set; }
[DisplayName("Created Timestamp")]
public System.DateTime CREATED_DATE { get; set; }
[DisplayName("Upated by")]
public Nullable<int> UPDATEDBY { get; set; }
[DisplayName("Updated Timestamp")]
public DateTime UPDATED_DATETIME
{
get; set;
}
}
both models are exactly same , Am I missing anything ?
How do I fix this?
both models are exactly same
That doesn't mean you can just assign the one to the other. For this code to work:
Foo foo = new Foo();
Bar bar = foo;
Bar must be a base type of Foo. This isn't the case here, both your Bar and Foo just happen to have the same property names.
You need to map from one to the other:
public Profitcenter Map(GLB_M_PROFITCENTER input)
{
return new Profitcenter
{
PROFITCENTER_CODE = input.PROFITCENTER_CODE,
...
};
}
You can do the mapping of the entire list with Select():
List<Profitcenter> profitcenter = new List<Profitcenter>();
profitcenter = db.GLB_M_PROFITCENTER.Select(Map).ToList();
An automated way of doing this could be using AutoMapper, which works especially well if all properties on both sides are named identically.
That only answers your question partially though. You have two types: GLB_M_PROFITCENTER, an Entity Framework-generated class that represents a database table, and Profitcenter, where you have added attributes that can be used for input validation using the MetadataType attribute.
I'm not a fan of the latter, because you're then using Entity Framework models as viewmodels for your UI layer. You shouldn't, and you can just remove the MetadataType attribute from the partial class definition.
So you can either use the MetadataType, but then never really instantiate that type (after all, it is a metadata type):
List<GLB_M_PROFITCENTER> profitcenter = db.GLB_M_PROFITCENTER.ToList();
return View(profitcenter);
And make your view #model IEnumerable<GLB_M_PROFITCENTER>. Then MVC will read the MetadataType attribute for GLB_M_PROFITCENTER, and apply the metadata (DisplayName, ...) as applied to Profitcenter (but you shouldn't).
Or you can simply apply mapping, thereby decoupling your view model from your entity model (and thus your database), with all additional benefits.
Some programming languages, unlike C# and other C-like languages, allow for what is called "duck typing", which would let you assign from different types if they both "quack the same way".
In C#, however, you can only assign an instance of a class to a variable of the same type, or of a base type (a class which your class extends, or an interface which it implements). Even if you had two classes which looked exactly the same, you wouldn't be able to assign from one of them to the other. .NET prior to version 4.0 didn't even support proper generic covariance and contravariance, meaning you couldn't even assign a IEnumerable<Tderived> to IEnumerable<Tbase> even if Tderived is derived from Tbase.
The solution could be to:
use a tool which will map from one class to the other (i.e. copy between equally named properties), like AutoMapper, or
redesign your app to have a separate assembly which contains common entities to be shared between other assemblies type (not a bad idea either), or
extract an interface so that you can assign to this base interface.
It is not uncommon to use mapping to resolve this issue, since you often want to have plain data transfer objects for moving data between tiers, so using an automated tool for this is ok, but if you can keep all entities in a separate assembly which is referenced by both DAL and business layer, but doesn't know anything about them, then it's an even better approach because it avoids any runtime mapping issues.
GLB_M_PROFITCENTER and Profitcenter are not same types, you just share metadata for sharing of attributes from viewmodel to entity model. You should use linq projection for conversion of one type to other
db.GLB_M_PROFITCENTER.select(e => new Profitcenter() {
/* props mapping*/
}).ToList()
you can also use mapping engine for example AutoMapper
If the member names are the same - use auto mapper - it will automatically convert each type.
Mapper.CreateMap<SourceType, DestinationType>()
Then you can call
Mapper.Map<DestinationType>(instanceofSourceType);
I have DataContract class which has property of type List<AnotherObject>. AnotherObject is also marked with DataContract. For some reason this property comes from wcf service as null, althought I fill it at the server. Is that by design?
Here you go. Class definitions:
[DataContract]
public class UserInfo
{
[DataMember]
public decimal UserID
{
get;
protected internal set;
}
[DataMember]
public string UserName
{
get;
protected internal set;
}
[DataMember]
public string Pswd
{
get;
protected internal set;
}
[DataMember]
public List<decimal> RoleID
{
get;
protected internal set;
}
List<UserRole> userRolesTable = new List<UserRole>();
[DataMember]
public List<UserRole> UserRoles
{
get
{
return userRolesTable;
}
protected internal set { }
}
}
[DataContract]
public class UserRole
{
[DataMember]
public decimal ROLEID { get; internal set; }
[DataMember]
public string ROLE_CODE { get; internal set; }
[DataMember]
public string ROLE_DESCRIPTION { get; internal set; }
[DataMember]
public decimal FORMID { get; internal set; }
[DataMember]
public string FORMCODE { get; internal set; }
[DataMember]
public string FORMNAME { get; internal set; }
}
UserRoles property comes as null.
Why are you letting the RoleId property be auto-implemented but not UserRoles? The code as-is won't work because you have an empty setter. You should probably just use an auto-property for it:
[DataMember]
public List<UserRole> UserRoles
{
get; set;
}
Or at least provide a meaningful setter. You setter does nothing, hence the de-serializer can't populate the value.
List<UserRole> userRolesTable = new List<UserRole>();
[DataMember]
public List<UserRole> UserRoles
{
get
{
return userRolesTable;
}
protected internal set { }
}
Your setter is empty. Put some
userRolesTable = value;
Another thing, your DataContract properties should have public setters.
Your Setter on the UserRoles property is set to internal. Because the WCF framework will be setting the property, it gives up assigning the value because it is listed as internal.
http://connect.microsoft.com/data/feedback/details/625985/wcf-client-entities-with-internal-setters-and-internalsvisibletoattribute-on-asmbly-fail
You can do what this link suggests, using the InternalsVisibleToAttribute attribute on that property, but I have never used it.
update
What I am trying to say is that I bet the Serialization works fine, the WCF framework is unable to insert the deserialized value into the host code because based upon the data contract, the internal Setter section of the property is inaccessible. use the InternalVisibleTo attribute to inform the WCF serialization framework access to the setter of the client version of your data contract object.
You need to Implement the setter...
protected internal set { userRolesTable = value; }
Basically, its a serialization problem. I had this problem in my code in the past, but it has been a while, so bear with me.
First, we need to find out if the object relations are null before the WCF call, so put a debug before and after.
If the object is being returned as null before the call, you have a few options:
You can explicitly use .Include("AnotherObject") on your DbContext to get the object. I used this by having my Read method take an array of strings which I used to include all the necessary objects. This is more ideal than automatically taking all objects because during serialization, if you take everything, you could fairly easily end up with your entire database being serialized, which introduces performance and security issues, among other things.
Another option is to use a dynamic proxy by adding the keyword virtual in front of your list. The DataContractSerializer, though, has a problem serializing dynamic proxies, so you will need to implement an attribute that uses the ProxyDataContractResolver instead of DataContractResolver. This attribute needs to be applied on all OperationContracts that can pass a dynamic proxy. This will automatically take ALL object references, which is probably bad coding practice, so I do recommend the above method.
public class ApplyDataContractResolverAttribute : Attribute, IOperationBehavior
{
public ApplyDataContractResolverAttribute() { }
public void AddBindingParameters(OperationDescription description, BindingParameterCollection parameters) { }
public void ApplyClientBehavior(OperationDescription description, System.ServiceModel.Dispatcher.ClientOperation proxy)
{
DataContractSerializerOperationBehavior dataContractSerializerOperationBehavior = description.Behaviors.Find<DataContractSerializerOperationBehavior>();
dataContractSerializerOperationBehavior.DataContractResolver = new ProxyDataContractResolver();
}
public void ApplyDispatchBehavior(OperationDescription description, System.ServiceModel.Dispatcher.DispatchOperation dispatch)
{
DataContractSerializerOperationBehavior dataContractSerializerOperationBehavior = description.Behaviors.Find<DataContractSerializerOperationBehavior>();
dataContractSerializerOperationBehavior.DataContractResolver = new ProxyDataContractResolver();
}
public void Validate(OperationDescription description) { }
}
Edit: Also I think you can have setters in Data Contracts not be public, because I do it and it works fine :). But I would try making your setter public first, then solving the problem, then reverting to a protected setter, just so that you are dealing with as few variables at a time as possible.