I've created entity framework 6 boilerplate code from a database using code first and I can retrieve my data fine and see all my tables in list objects just fine, so I know retrieval is fine and any lazy loading is working.
However when returning the list from a webapi rest service it only works for my simple class (BikeStation below) which has no associated lists, when I try my classes which have references to other objects, it generates an exception to do with data contracts and seems to be linked to circular references
Here are my classes:
public partial class BikeStation
{
public int Id { get; set; }
public string CentreName { get; set; }
public string StationName { get; set; }
}
public partial class Bike
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Bike()
{
BikeStats = new HashSet<BikeStat>();
}
public string ShortBikeName { get; set; }
public bool Active { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<BikeStat> BikeStats { get; set; }
}
public partial class BikeStat
{
public int Id { get; set; }
public DateTime StatDate { get; set; }
public virtual Bike Bike { get; set; }
}
This works:
public IEnumerable<BikeStation> Get()
{
using (var db = new BikesContext())
{
var a = db.BikeStations.ToList();
return a;
}
}
However, changing to either of the other two gets the list fine but then fails to serialise the data:
public IEnumerable<Bike> Get()
{
using (var db = new BikesContext())
{
var a = db.Bikes.ToList();
return a;
}
}
If I remove the reference to the bike or bikestats in either of my other classes, it works just fine.
The error is this, and I don't really understand why it's trying to serialise as XML either. Any help would be appreciated.
<Error><Message>An error has occurred.</Message><ExceptionMessage>The 'ObjectContent`1' type failed to serialize the response body for content type 'application/xml; charset=utf-8'.</ExceptionMessage><ExceptionType>System.InvalidOperationException</ExceptionType><StackTrace /><InnerException><Message>An error has occurred.</Message><ExceptionMessage>Type 'System.Data.Entity.DynamicProxies.Bike_16DB1847327CF3674DCB6407C4CCB0E41EA43002E17C91F35F5B4716F57DAD1D' with data contract name 'Bike_16DB1847327CF3674DCB6407C4CCB0E41EA43002E17C91F35F5B4716F57DAD1D:http://schemas.datacontract.org/2004/07/System.Data.Entity.DynamicProxies' is not expected. Consider using a DataContractResolver if you are using DataContractSerializer or add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to the serializer.</ExceptionMessage><ExceptionType>System.Runtime.Serialization.SerializationException</ExceptionType><StackTrace> at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeAndVerifyType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, Boolean verifyKnownType, RuntimeTypeHandle declaredTypeHandle, Type declaredType)
at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithXsiType(XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle objectTypeHandle, Type objectType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle, Type declaredType)
at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerialize(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle)
at WriteArrayOfBikeToXml(XmlWriterDelegator , Object , XmlObjectSerializerWriteContext , CollectionDataContract )
at System.Runtime.Serialization.CollectionDataContract.WriteXmlValue(XmlWriterDelegator xmlWriter, Object obj, XmlObjectSerializerWriteContext context)
at System.Runtime.Serialization.XmlObjectSerializerWriteContext.WriteDataContractValue(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeAndVerifyType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, Boolean verifyKnownType, RuntimeTypeHandle declaredTypeHandle, Type declaredType)
at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithXsiTypeAtTopLevel(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle originalDeclaredTypeHandle, Type graphType)
at System.Runtime.Serialization.DataContractSerializer.InternalWriteObjectContent(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver)
at System.Runtime.Serialization.DataContractSerializer.InternalWriteObject(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver)
at System.Runtime.Serialization.XmlObjectSerializer.WriteObjectHandleExceptions(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver)
at System.Runtime.Serialization.DataContractSerializer.WriteObject(XmlWriter writer, Object graph)
at System.Net.Http.Formatting.XmlMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, HttpContent content)
at System.Net.Http.Formatting.XmlMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext, CancellationToken cancellationToken)
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Web.Http.WebHost.HttpControllerHandler.<WriteBufferedResponseContentAsync>d__1b.MoveNext()</StackTrace></InnerException></Error>
I then changed my code to explicitly convert the code using a custom class, which works but isn't ideal:
var newData = d.Select(x => new Bike()
{
Active = x.Active,
ShortBikeName = x.ShortBikeName,
BikeStats = (x.BikeStats.Select(item => new BikeStat()
{
Id = item.Id,
StatDate = item.StatDate
}).ToList() as ICollection<BikeStat>)
}).ToList();
This works, but is a bit pants. So I changed the serialisation to this:
var d = db.Bikes.Include("BikeStats").ToList();
string s = JsonConvert.SerializeObject(d,
Formatting.Indented,
new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, PreserveReferencesHandling=PreserveReferencesHandling.Objects });
return s;
Which works, but it doesn't. It adds "\r\n" as a string to the response, turns all quote marks into \", puts '$' in front of some values and Chrome still thinks it's XML.
I changed the class to have this and it all magically works. Why? and is there a better way so that I can still access Bike from BikeStat?
[JsonIgnore]
public Bike Bike { get; set; }
Try Changing your code to look like this
public class BikeStation
{
public int Id { get; set; }
public string CentreName { get; set; }
public string StationName { get; set; }
}
public class Bike
{
public string ShortBikeName { get; set; }
public bool Active { get; set; }
public IEnumerable<BikeStat> BikeStats { get; set; }
}
public class BikeStat
{
public int Id { get; set; }
public DateTime StatDate { get; set; }
public Bike Bike { get; set; }
}
Related
I created an EF Code First Entity for a view in my database. I setup a test, and the access code works in my test, but when I run the code outside of the test, it throws "Object Reference not set to an instance of an object". I am stuck, as I cannot understand what could be causing the issue. Any ideas on why it's throwing an error?
Here is my entity class:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace PhoenixData.Models.DB
{
public partial class VW_ICD10MappingWithUsage
{
[Key]
[Column(Order = 0)]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public long Id { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int ProviderID { get; set; }
[StringLength(20)]
public string DxKey { get; set; }
[StringLength(100)]
public string Icd9Description { get; set; }
[StringLength(10)]
public string Code { get; set; }
[StringLength(2000)]
public string Description { get; set; }
public int? USAGE { get; set; }
[Key]
[Column(Order = 1)]
[StringLength(20)]
public string Region { get; set; }
public long? ProviderCodeCount { get; set; }
[NotMapped]
public IList<CodeProviderUsage> ChildUsages { get; set; }
public VW_ICD10MappingWithUsage()
{
ChildUsages = new List<CodeProviderUsage>();
}
}
}
Here is access code that throws the error:
using (var dbContext = new MyDatabase(_config.ConnectionString))
{
res = dbContext.VW_ICD10MappingWithUsage
.Where(x => x.ProviderID == providerId)
.OrderBy(x => x.DxKey)
.Take(pageSize)
.ToList();
}
And here is the stack trace:
at System.Object.GetType()
at System.Data.Entity.Core.EntityKey.ValidateTypeOfKeyValue(MetadataWorkspace workspace, EdmMember keyMember, Object keyValue, Boolean isArgumentException, String argumentName)
at System.Data.Entity.Core.EntityKey.ValidateEntityKey(MetadataWorkspace workspace, EntitySet entitySet, Boolean isArgumentException, String argumentName)
at System.Data.Entity.Core.Objects.ObjectStateManager.CheckKeyMatchesEntity(IEntityWrapper wrappedEntity, EntityKey entityKey, EntitySet entitySetForType, Boolean forAttach)
at System.Data.Entity.Core.Objects.ObjectStateManager.AddEntry(IEntityWrapper wrappedObject, EntityKey passedKey, EntitySet entitySet, String argumentName, Boolean isAdded)
at System.Data.Entity.Core.Common.Internal.Materialization.Shaper.HandleEntityAppendOnly[TEntity](Func`2 constructEntityDelegate, EntityKey entityKey, EntitySet entitySet)
at lambda_method(Closure , Shaper )
at System.Data.Entity.Core.Common.Internal.Materialization.Coordinator`1.ReadNextElement(Shaper shaper)
at System.Data.Entity.Core.Common.Internal.Materialization.Shaper`1.SimpleEnumerator.MoveNext()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at PhoenixData.Core.ICD10.ICD10MappingRepository.SearchIcd10MappingWithUsages(Int32 pageSize, Int32 pageNumber, Int32 providerId, String orderByColumnName, String direction, String searchTerm, String searchColumns) in C:\Dev\PhoenixDriveServices\PhoenixData\Core\ICD10\ICD10MappingRepository.cs:line 149
The answer accepted by the OP is:
Is it possible then that one specific record causes the issue upon materialization? For example, one of your columns is not nullable in the model but it is nullable in the database and the actual value is null which confuses the materialization? In other words, try to narrow the issue to a specific row in the database.
It looks like that was it. One of the rows had null for one of composite key columns.
I am designing a web api where clients can retrieve information about a course.
In my model the entity course has several properties including the property instructor. Instructor is a class on it's own.
public class Course
{
public int Id { get; set; }
public string Title { get; set; }
public double? Duration { get; set; }
...
public Instructor Instructor { get; set; }
}
public class Instructor
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
...
}
My question is, should i return all of the instructors properties when calling the course controller, set instructor to null or don't i retrun it at all?
Or are there any other options?
I have no idea what best practice is in this case. When the child entity is a collection i usualy just return an empty array. But in this case there is only 1 so i can't return an empty array.
best practice is to serialize that child entity, only if it's needed on client side when returned from this request. otherwise, you're just transferring unnecessary data.
to make the application decide whether or not to serialize it, (using Newtonsoft) you can implement a custom data contract resolver and use it when serializing your entities:
public class CustomDataContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
if ((property.DeclaringType == typeof(Course) ||
property.DeclaringType.BaseType == typeof(Course)) &&
property.PropertyName == "Instructor")
{
property.ShouldSerialize =
instance =>
{
return true; // or false
};
}
return property;
}
}
I have a simple BreezeController that returns a unit of work repository object. The object is a DbSet entity object of the class below:
public int OrderId { get; set; }
public string Customer { get; set; }
public virtual ICollection<OrderLine> OrderLines { get; set; }
The Unit of Work class is as follows:
private readonly EFContextProvider<ESpaDBEntities> _contextProvider;
public UoW()
{
_contextProvider = new EFContextProvider<ESpaDBEntities>();
Orders = new Repository<Order>(_contextProvider.Context);
OrderLine = new Repository<OrderLine>(_contextProvider.Context);
Products = new Repository<Product>(_contextProvider.Context);
}
public IRepository<Order> Orders { get; set; }
public IRepository<OrderLine> OrderLine { get; set; }
public IRepository<Product> Products { get; set; }
public SaveResult Commit(JObject changeSet)
{
return _contextProvider.SaveChanges(changeSet);
}
The BreezeController action is as follows:
[HttpGet]
public IQueryable<Order> Orders()
{
return uow.Orders.All();
}
When I access this method from my browser the following Json object is returned:
$id: "1",$type: "KoDurandalBreeze.DomainModel.Order, KoDurandalBreeze",OrderId: 1,Customer: "Bob",OrderLines: [ ]
For whatever reason, orderlines are not populated even though virtual is specified. Does anyone have any ideas of why the JSON object would not contain any OrderLine objects?
You will need to either perform the equivalent of an EF 'Include' on the server or if this is an EF Queryable you can call 'extend' on your client side EntityQuery, i.e.
var query = EntityQuery.from("Orders").expand("OrderDetails");
var myEntityManager.executeQuery(query).then(...)
Stuck on a strange issue, that works using my get method but fails when returning iqueryable. Some limitation when using projection or iqueryable?
My code looks like below, simplified:
public class SimpleEntity
{
public long Id { get; set; }
public string Name { get; set; }
public virtual SimpleEntity Parent { get; set; }
public virtual ICollection<SimpleEntity> Children { get; set; }
public SimpleEntity()
{
Children = new List<SimpleEntity>();
}
}
public class SimpleEntityResponseDTO
{
public long Id { get; set; }
public string Name { get; set; }
public NameValueItem ParentReferral { get; set; }
public IEnumerable<NameValueItem> ChildReferrals { get; set; }
public NavigationFolderResponseDTO()
{
ChildReferrals = new List<NameValueItem>();
}
}
public class NameValueItem
{
public long Value { get; set; }
public string Name { get; set; }
}
The web api actions:
[HttpGet, Queryable]
public IQueryable<SimpleEntityResponseDTO> List()
{
//Generic crudservice returning an iqueryable based on Set<SimpleEntity>
return _crudService.QueryableList().Project().To<SimpleEntityResponseDTO>();
}
[HttpGet]
public HttpResponseMessage Get(long id)
{
SimpleEntity result = _crudSrv.Get(id);
if (result != null)
return Request.CreateResponse<SimpleEntityResponseDTO>(HttpStatusCode.OK, Mapper.Map<SimpleEntity , SimpleEntityResponseDTO>(result));
else
return Request.CreateResponse(HttpStatusCode.NotFound);
}
And now the mapping:
Mapper.CreateMap<SimpleEntity, SimpleEntityResponseDTO>()
.ForMember(to => to.ParentReferral, opt => opt.MapFrom(from => new NameValueItem { Name = from.Parent.Name, Value = from.Parent.Id }))
.ForMember(to => to.ChildReferrals, opt => opt.MapFrom(from => from.Children.Select(o => new NameValueItem {Name = o.Name, Value = o.Id}).ToList() ));
The parent mapping works no matter what. But the Children mapping is causing below issue.
When retrieving an object through the get method everything works, no matter wich entity i retrieve. When using List i get "Object reference not set to an instance of an object", "d__b.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n". Tried for example adding $filter=Id eq 5 (or whatever id) but results in same issue. Perhaps someone can hint me to what goes wrong here?
I keep coming across an i18n requirement where my data (not my UI) needs to be internationalized.
public class FooEntity
{
public long Id { get; set; }
public string Code { get; set; } // Some values might not need i18n
public string Name { get; set } // but e.g. this needs internationalized
public string Description { get; set; } // and this too
}
What are some approaches I could use?
Some things I've tried:-
1) Store a resource key in the db
public class FooEntity
{
...
public string NameKey { get; set; }
public string DescriptionKey { get; set; }
}
Pros: No need for complicated queries to get a translated entity. System.Globalization handles fallbacks for you.
Cons: Translations can't easily be managed by an admin user (have to deploy resource files whenever my Foos change).
2) Use a LocalizableString entity type
public class FooEntity
{
...
public int NameId { get; set; }
public virtual LocalizableString Name { get; set; }
public int NameId { get; set; }
public virtual LocalizableString Description { get; set; }
}
public class LocalizableString
{
public int Id { get; set; }
public ICollection<LocalizedString> LocalizedStrings { get; set; }
}
public class LocalizedString
{
public int Id { get; set; }
public int ParentId { get; set; }
public virtual LocalizableString Parent { get; set; }
public int LanguageId { get; set; }
public virtual Language Language { get; set; }
public string Value { get; set; }
}
public class Language
{
public int Id { get; set; }
public string Name { get; set; }
public string CultureCode { get; set; }
}
Pros: All localised strings in the same table. Validation can be performed per-string.
Cons: Queries are horrid. Have to .Include the LocalizedStrings table once for each localizable string on the parent entity. Fallbacks are hard and involve extensive joining. Haven't found a way to avoid N+1 when retrieving e.g. data for a table.
3) Use a parent entity with all the invariant properties and child entities containing all the localized properties
public class FooEntity
{
...
public ICollection<FooTranslation> Translations { get; set; }
}
public class FooTranslation
{
public long Id { get; set; }
public int ParentId { get; set; }
public virtual FooEntity Parent { get; set; }
public int LanguageId { get; set; }
public virtual Language Language { get; set; }
public string Name { get; set }
public string Description { get; set; }
}
public class Language
{
public int Id { get; set; }
public string Name { get; set; }
public string CultureCode { get; set; }
}
Pros: Not as hard (but still too hard!) to get a full translation of an entity into memory.
Cons: Double the number of entities. Can't handle partial translations of an entity - especially the case where, say, Name is coming from es but Description is coming from es-AR.
I have three requirements for a solution
Users can edit entities, languages, and translations at runtime
Users can supply partial translations with missing strings coming from a fallback as per System.Globalization
Entities can be brought into memory without running into e.g. N+1 issues
Why don't you take the best of both worlds?
Have a CustomResourceManager that handles the loading of resources and picking the right culture and use a CustomResourceReader that uses whatever backing store you like. A basic implementation could look like this, relying on convention of the Resourceky being Typename_PropertyName_PropertyValue. If for some reason the structure of the backingstore(csv/excel/mssql/table structure) need to change you only have the change the implementation of the ResourceReader.
As an added bonus I also got the real/transparent proxy going.
ResourceManager
class MyRM:ResourceManager
{
readonly Dictionary<CultureInfo, ResourceSet> sets = new Dictionary<CultureInfo, ResourceSet>();
public void UnCache(CultureInfo ci)
{
sets.Remove(ci):
}
protected override ResourceSet InternalGetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents)
{
ResourceSet set;
if (!sets.TryGetValue(culture, out set))
{
IResourceReader rdr = new MyRR(culture);
set = new ResourceSet(rdr);
sets.Add(culture,set);
}
return set;
}
// sets Localized values on properties
public T GetEntity<T>(T obj)
{
var entityType = typeof(T);
foreach (var prop in entityType.GetProperties(
BindingFlags.Instance
| BindingFlags.Public)
.Where(p => p.PropertyType == typeof(string)
&& p.CanWrite
&& p.CanRead))
{
// FooEntity_Name_(content of Name field)
var key = String.Format("{0}_{1}_{2}",
entityType.Name,
prop.Name,
prop.GetValue(obj,null));
var val = GetString(key);
// only set if a value was found
if (!String.IsNullOrEmpty(val))
{
prop.SetValue(obj, val, null);
}
}
return obj;
}
}
ResourceReader
class MyRR:IResourceReader
{
private readonly Dictionary<string, string> _dict;
public MyRR(CultureInfo ci)
{
_dict = new Dictionary<string, string>();
// get from some storage (here a hardcoded Dictionary)
// You have to be able to deliver a IDictionaryEnumerator
switch (ci.Name)
{
case "nl-NL":
_dict.Add("FooEntity_Name_Dutch", "nederlands");
_dict.Add("FooEntity_Name_German", "duits");
break;
case "en-US":
_dict.Add("FooEntity_Name_Dutch", "The Netherlands");
break;
case "en":
_dict.Add("FooEntity_Name_Dutch", "undutchables");
_dict.Add("FooEntity_Name_German", "german");
break;
case "": // invariant
_dict.Add("FooEntity_Name_Dutch", "dutch");
_dict.Add("FooEntity_Name_German", "german?");
break;
default:
Trace.WriteLine(ci.Name+" has no resources");
break;
}
}
public System.Collections.IDictionaryEnumerator GetEnumerator()
{
return _dict.GetEnumerator();
}
// left out not implemented interface members
}
Usage
var rm = new MyRM();
var f = new FooEntity();
f.Name = "Dutch";
var fl = rm.GetEntity(f);
Console.WriteLine(f.Name);
Thread.CurrentThread.CurrentUICulture = new CultureInfo("nl-NL");
f.Name = "Dutch";
var dl = rm.GetEntity(f);
Console.WriteLine(f.Name);
RealProxy
public class Localizer<T>: RealProxy
{
MyRM rm = new MyRM();
private T obj;
public Localizer(T o)
: base(typeof(T))
{
obj = o;
}
public override IMessage Invoke(IMessage msg)
{
var meth = msg.Properties["__MethodName"].ToString();
var bf = BindingFlags.Public | BindingFlags.Instance ;
if (meth.StartsWith("set_"))
{
meth = meth.Substring(4);
bf |= BindingFlags.SetProperty;
}
if (meth.StartsWith("get_"))
{
// get the value...
meth = meth.Substring(4);
var key = String.Format("{0}_{1}_{2}",
typeof (T).Name,
meth,
typeof (T).GetProperty(meth, BindingFlags.Public | BindingFlags.Instance
|BindingFlags.GetProperty).
GetValue(obj, null));
// but use it for a localized lookup (rm is the ResourceManager)
var val = rm.GetString(key);
// return the localized value
return new ReturnMessage(val, null, 0, null, null);
}
var args = new object[0];
if (msg.Properties["__Args"] != null)
{
args = (object[]) msg.Properties["__Args"];
}
var res = typeof (T).InvokeMember(meth,
bf
, null, obj, args);
return new ReturnMessage(res, null, 0, null, null);
}
}
Real/Transparent proxy usage
var f = new FooEntity();
f.Name = "Dutch";
var l = new Localizer<FooEntity>(f);
var fp = (FooEntity) l.GetTransparentProxy();
fp.Name = "Dutch"; // notice you can use the proxy as is,
// it updates the actual FooEntity
var localizedValue = fp.Name;
First one is worthy if you have static content in database. For example if you have categories that relatively are not going to be changed by user. You can change them at next deploy. I do not like this solution personally. I do not consider this as a nice solution. This is just an escape of the problem.
Second one is the best but can cause a problem when you have two or more localizable fields in one entity. You can simplify it a bit and hard code languages on it like this
public class LocalizedString
{
public int Id { get; set; }
public string EnglishText { get; set; }
public string ItalianText { get; set; }
public string ArmenianText { get; set; }
}
Third one is not a good one neither. From this structure I can't be sure that all nodes (literals, lines, strings etc.) translated in specific culture.
Do not generalize too much. Each problem is kind of specialized and it needs specialized solution too. Too much generalization makes unjustified issues.