Is it possible to associate a POCO entity with a standard entity? - c#

Let's say I have to entities a POCO entity named SalesOrders and a regular entity generated from a SQL database called SalesOrderLines. I would like to create an association from SalesOrders to SalesOrderLines as in the following code but I keep getting the exception below the code. Does anyone know if this is possible?
[DataServiceKey("SalesOrderNumber")]
public class SalesOrder
{
[Key]
public int SalesOrderNumber { get; set; }
public string StockCode { get; set; }
public string Description { get; set; }
[Include]
[Association("SalesOrder_SalesOrderLine", "SalesOrderNumber", "SalesOrderNumber")]
public IQueryable<SalesOrderLine> SalesOrderLines
{
get
{
SalesOrderLineEntities oSalesOrderLineEntities = new SalesOrderLineEntities();
var soLines = from line in oSalesOrderLineEntities.SalesOrderLines
where line.OrderNumber == SalesOrderNumber.ToString() &&
line.StockCode == StockCode
select line;
return soLines;
}
set
{
}
}
}
'The property 'SalesOrderLines' on type 'ServiceName.NameSpace.SalesOrder' is not a valid property. Properties whose types are collection of primitives or complex types are not supported.'

It turns out that all I needed to to do was add the [Key] attribute to the generated metadata class.
[MetadataTypeAttribute(typeof(SalesOrderLines.SalesOrderLinesMetadata))]
public partial class SalesOrderLines
{
internal sealed class SalesOrderLinesMetadata
{
private SalesOrderLinesMetadata()
{
}
public DateTime DateStamp { get; set; }
public string OrderNumber { get; set; }
[Key]
public int SalesOrderLineID { get; set; }
public string SerialNumber { get; set; }
public string StockCode { get; set; }
}
}

Related

Handling Nested Objects in Entity Framework

I am struggling a bit to wrap my head around Entity Framework and It's driving me crazy. I have an target object that I'd like to populate:
public class ApiInvitationModel
{
public int Id { get; set; }
public EventModel Event { get; set; }
public UserModel InvitationSentTo { get; set; }
public UserModel AttendingUser { get; set; }
}
The schemas of the above models are:
public class EventModel {
public int Id? { get; set; }
public string Name { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set }
public OrganizationModel HostingOrganization { get; set; }
public Venue Venue { get; set; }
public string Price { get; set; }
}
public class UserModel {
public int Id? { get; set; }
public string Name { get; set; }
public string PhoneNumber { get; set; }
public string MobileNumber { get; set; }
public List<OrganizationModel> Organizations { get; set; }
}
public class OrganizationModel {
public int Id? { get; set; }
public stirng Name { get; set; }
public string Address { get; set; }
public UserModel PrimaryContact { get; set; }
}
The above schemas are simplified for the purpose of the question and are the models we intend to return via API.
The problem is the origin schemas in the database is very different and I'm trying to map the database objects to these objects via Entity Framework 6.
My attempted solution was to try and nest the models via a query but that didn't work and I'm not sure where to go from here besides making numerous calls to the database.
public List<ApiInvitationModel> GetInvitations(int userId) {
using (var entities = new Entities()) {
return entities.EventInvitations
.Join(entities.Users, invitation => invitiation.userId, user => user.id, (invitation, user) => new {invitation, user})
.Join(entities.Events, model => model.invitation.eventId, ev => ev.id, (model, ev) => new {model.invitation, model.user, ev})
.Join(entities.organization, model => model.user.organizationId, organization => organization.id, (model, organization) => new ApiInvitationModel
{
Id = model.invitation.id,
Event = new EventModel {
Id = model.event.id,
Name = model.event.name,
StartDate = model.event.startDate,
EndDate = model.event.endDate,
HostingOrganization = new OrganizationModel {
Id = model.invitation.hostingId,
Name = model.event.venueName,
Address = model.event.address,
PrimaryContact = new UserModel {
Name = model.event.contactName,
PhoneNumber = model.event.contactNumber,
}
}
...
},
InvitedUser = {
}
}
).ToList();
}
}
As you can see above, there's quite a bit of nesting going on but this doesn't work in Entity Framework 6 as far as I am aware. I keep getting the following errors:
"The type 'Entities.Models.API.UserModel' appears in two structurally incompatible initializations within a single LINQ to Entities query. A type can be initialized in two places in the same query, but only if the same properties are set in both places and those properties are set in the same order.",
Based on the above error, I assumed that each of the model initiatilizations would need to be the same (i.e. initializing the values as the same ApiInvitationModel in each join in the same order) but that produces the same error.
What would be the best approach to handling this, keepign in mind the source database doesn't have foreign keys implemented?

Entity Framework query with simple join

I am using Entity Framework 6 in a project and am having trouble creating a query.
Say my classes are defined like:
public class MyContext : DbContext
{
public MyContext(string connectionString) : base(connectionString)
{
}
public DbSet<EntityXXX> XXXSet { get; set; }
public DbSet<EntityYYY> YYYSet { get; set; }
}
public class EntityXXX
{
public string XXXName { get; set; }
public int id { get; set; }
public int YYYid { get; set; }
}
public class EntityYYY
{
public string YYYName { get; set; }
public int id { get; set; }
}
The YYYid property of EntityXXX is the 'id' of the EntityYYY instance that it relates to.
I want to be able to fill a Grid with rows where the first Column is XXXName and the second column is YYYName (from its related EntityYYY), but I can't see how to do this?
I'm sure it's really simple, but I'm new to EF.
You need to put a virtual navigation property on your EntityXXX
public virtual EntityYYY YYY { get; set; }
Then you can do a projection:
db.XXXSet
.Select(x => new { x.XXXName, YYYName = x.YYY.YYYName })
.ToList();
Which will get you the list you need.

Converting infinitely nested objects in .NET Core

EDIT: I originally worded this question very poorly, stating the problem was with JSON serialization. The problem actually happens when I'm converting from my base classes to my returned models using my custom mappings. I apologize for the confusion. :(
I'm using .NET Core 1.1.0, EF Core 1.1.0. I'm querying an interest and want to get its category from my DB. EF is querying the DB properly, no problems there. The issue is that the returned category has a collection with one interest, which has one parent category, which has a collection with one interest, etc. When I attempt to convert this from the base class to my return model, I'm getting a stack overflow because it's attempting to convert the infinite loop of objects. The only way I can get around this is to set that collection to null before I serialize the category.
Interest/category is an example, but this is happening with ALL of the entities I query. Some of them get very messy with the loops to set the relevant properties to null, such as posts/comments.
What is the best way to address this? Right now I'm using custom mappings that I wrote to convert between base classes and the returned models, but I'm open to using any other tools that may be helpful. (I know my custom mappings are the reason for the stack overflow, but surely there must be a more graceful way of handling this than setting everything to null before projecting from base class to model.)
Classes:
public class InterestCategory
{
public long Id { get; set; }
public string Name { get; set; }
public ICollection<Interest> Interests { get; set; }
}
public class Interest
{
public long Id { get; set; }
public string Name { get; set; }
public long InterestCategoryId { get; set; }
public InterestCategory InterestCategory { get; set; }
}
Models:
public class InterestCategoryModel
{
public long Id { get; set; }
public string Name { get; set; }
public List<InterestModel> Interests { get; set; }
}
public class InterestModel
{
public long Id { get; set; }
public string Name { get; set; }
public InterestCategoryModel InterestCategory { get; set; }
public long? InterestCategoryId { get; set; }
}
Mapping functions:
public static InterestCategoryModel ToModel(this InterestCategory category)
{
var m = new InterestCategoryModel
{
Name = category.Name,
Description = category.Description
};
if (category.Interests != null)
m.Interests = category.Interests.Select(i => i.ToModel()).ToList();
return m;
}
public static InterestModel ToModel(this Interest interest)
{
var m = new InterestModel
{
Name = interest.Name,
Description = interest.Description
};
if (interest.InterestCategory != null)
m.InterestCategory = interest.InterestCategory.ToModel();
return m;
}
This is returned by the query. (Sorry, needed to censor some things.)
This is not .NET Core related! JSON.NET is doing the serialization.
To disable it globally, just add this during configuration in Startup
services.AddMvc()
.AddJsonOptions(options =>
{
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
}));
edit:
Is it an option to remove the circular references form the model and have 2 distinct pair of models, depending on whether you want to show categories or interests?
public class InterestCategoryModel
{
public long Id { get; set; }
public string Name { get; set; }
public List<InterestModel> Interests { get; set; }
public class InterestModel
{
public long Id { get; set; }
public string Name { get; set; }
}
}
public class InterestModel
{
public long Id { get; set; }
public string Name { get; set; }
public InterestCategoryModel InterestCategory { get; set; }
public class InterestCategoryModel
{
public long Id { get; set; }
public string Name { get; set; }
}
}
Note that each of the models has a nested class for it's child objects, but they have their back references removed, so there would be no infinite reference during deserialization?

EF6 Interceptor to set a value on Insert or Update

I am having troubles trying to figure out how to use the EF6 interceptors to set a value on Insert/Update.
What I wanted to do is to have an interceptor to automatically create a new instance of Audit like so:
public class FooContext : DbContext
{
public DbSet<Invoice> Invoices { get; set; }
public DbSet<Audit> Audits { get; set; }
}
public class Invoice
{
public int Id { get; set; }
public string Name { get; set; }
public Audit AuditAndConcurrencyKey { get; set; }
}
public class InvoiceItem
{
public int Id { get; set; }
public Invoice Header { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
//For legacy reasons. I know this design is wrong :(
public Audit AuditAndConcurrencyKey { get; set; }
}
public class Audit
{
public int Id { get; set; }
public int InstanceId { get; set; }
public string Message { get; set; }
}
[Test]
public void WillCreateAudit()
{
using (var db = new FooContext())
{
var inv = new Invoice {Name = "Foo Invoice"};
var invLine = new InvoiceItem {Header = inv, Price = 1, Name = "Apple"};
db.Invoices.Add(inv);
db.SaveChanges();
//Inceptors should figure out that we are working with "Invoice" and "InvoiceLine"
//And automatically create an "Audit" instance
Assert.That(inv.AuditAndConcurrencyKey != null);
Assert.That(invLine.AuditAndConcurrencyKey != null);
Assert.That(inv.AuditAndConcurrencyKey == invLine.AuditAndConcurrencyKey)
}
}
The first thing I checked is this example for SoftDeleteInterceptor. I don't think this is what I want because it looks like at the point where we are already generating the expression tree, we are no longer aware of the type of object you are working with.
I checked this example as well, but again, it looks like we are injecting strings instead of setting object references.
Ideally I want something like this:
public class AuditInterceptor
{
public void Intercept(object obj)
{
if (!(obj is Invoice) && !(obj is InvoiceItem))
return; //not type we are looking for, by-pass
//Set the audit here
}
}

Adding Entity with child entities to Redis server resulting System.StackOverflowException

I am working on ASP.NET MVC project with EF6 with Database First. I am trying to use Redis server to cache frequently used objects.
But i am getting problem in saving related entities (parent-child). For example following Author and Author_Book classes are parent-child and referencing to each other (Foreign-Key constraint in RDBMS)
public partial class Author
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Author_Book> Author_Book { get; set; }
}
public partial class Author_Book
{
public int Id { get; set; }
public int AuthorId { get; set; }
public string Title { get; set; }
public virtual Author Author { get; set; }
}
public partial class Customer
{
public int ID { get; set; }
public string CustomerName { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
}
Querying and trying to store query result to Redis server as below
using (EFTestContext db = new EFTestContext())
{
var data = db.Authors.ToList();
redisClient.Set<List<Author>>("author", data);
}
Above line redisClient.Set.. resulting following Exception
An unhandled exception of type 'System.StackOverflowException' occurred in mscorlib.dll
However if i store Customer entity (which doesn't have child entities) into Redis server it work fine
using (EFTestContext db = new EFTestContext())
{
var customers = db.Customers.ToList();
redisClient.Set<List<Customer>>("customers", customers);
}
So my question is how to store complete entity (with childs) into redis server?
I followed [DataContract] / [DataMember] approach (As #oferzelig mentioned in comment). It is working fine and no longer raising that exception. I am describing it here so it could help someone else.
EF Database-first by default does not add [DataContract] / [DataMember] attributes, we need to modify T4 template for it. I did following modifications in Model template Model1.tt.
Added [DataContract] attribute before the line
<#=codeStringGenerator.EntityClassOpening(entity)#>
now it looks like
[DataContract]
<#=codeStringGenerator.EntityClassOpening(entity)#>
Added [DataMember] attribute before line
<#=codeStringGenerator.Property(edmProperty)#>
and it looks like
[DataMember]
<#=codeStringGenerator.Property(edmProperty)#>
We also need to generate [DataMember] attribute for one-to-many relationships (e.g. public virtual ICollection<Author_Book> Author_Book { get; set; } in question) but NOT for one-to-one (e.g. public virtual Author Author { get; set; } in question). To achieve it i added a new function in CodeStringGenerator class
public string NavigationProperty_NeedDataMember(NavigationProperty navProp)
{
return string.Format(
CultureInfo.InvariantCulture,
"{0}",
navProp.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("[DataMember]") : ""
);
}
and called it just before the line
<#=codeStringGenerator.NavigationProperty(navigationProperty)#>
as below
<#=codeStringGenerator.NavigationProperty_NeedDataMember(navigationProperty)#>
<#=codeStringGenerator.NavigationProperty(navigationProperty)#>
And finally modified UsingDirectives procedure to add System.Runtime.Serialization, as below
public string UsingDirectives(bool inHeader, bool includeCollections = true)
{
return inHeader == string.IsNullOrEmpty(_code.VsNamespaceSuggestion())
? string.Format(
CultureInfo.InvariantCulture,
"{0}using System;{1}" +
"{2}" +
"{3}",
inHeader ? Environment.NewLine : "",
includeCollections ? (Environment.NewLine + "using System.Collections.Generic;") : "",
includeCollections ? (Environment.NewLine + "using System.Runtime.Serialization;") : "",
inHeader ? "" : Environment.NewLine)
: "";
}
That all.
Now it is generating following classes and i no need to edit classes manually after each update.
[DataContract]
public partial class Author
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public virtual ICollection<Author_Book> Author_Book { get; set; }
}
[DataContract]
public partial class Author_Book
{
[DataMember]
public int Id { get; set; }
[DataMember]
public int AuthorId { get; set; }
[DataMember]
public string Title { get; set; }
public virtual Author Author { get; set; }
}
Hope it will help someone else.

Categories