Design a generic model for multiple entities with Entity Framework - entity-framework

How can I model this scenario with Entity Framewrok Code First:
Cats:
| Id | Name |
|----|-------|
| 1| Cat1|
| 2| Cat2|
| 3| Cat3|
Dogs:
| Id | Name |
|----|-------|
| 1 | Dog1|
| 2 | Dog2|
| 3 | Dog3|
Owner:
| Id | Name |TableName |EntityId|
|----|-------|--------------------
| 1 | John| Dog | 1|
| 2 | Pete| Cat | 1|
| 3 |Jessica| Cat | 2|
This is just an example of what I want to accomplish. I need that Owner Table can store any Animal, I meant, I have no control over the animal tables, so plus cats and dogs, there may be mouses, lions, etc.
Entity Framework support this? Can I model this with navigation properties?
I have read about Table Per Hierarchy but don't think that should follow this approach. Also, I want to navigate from Owner table to any animal entity.

I offer to you this solution, that completely compatible with your tables and also if you will add another table with new animal, it is not needed to make any changes at Owner class, that is why it is generic approach as you desired.
MODELS:
public class Animal
{
public int Id { get; set; }
[StringLength(64)]
public string Name { get; set; }
}
public class Dog : Animal
{
}
public class Cat : Animal
{
}
public class Owner
{
public int Id { get; set; }
[StringLength(64)]
public string Name { get; set; }
[StringLength(64)]
public string TableName { get; private set; }
public int? EntityId { get; private set; }
[NotMapped]
public Animal animal {
get {
if (EntityId == null || string.IsNullOrEmpty(TableName))
return null;
using (var context = new Context())
{
var type = typeof(Animal).Assembly.GetTypes().Where(x => x.Name.Contains(TableName)).First();
return (Animal)context.Set(type).Find(EntityId);
}
}
set {
//I assume that animal has already existed on this moment i.e. Id field is valid.
EntityId = value.Id;
TableName = value.GetType().Name;
}
}
}
IMPLEMENTATION:
var cat = context.Cats.Add(new Cat { Name = "Cat1" });
context.SaveChanges();
var owner = context.Owners.Add(new Owner { Name = "Owner", animal = cat });
context.SaveChanges();
var animal = owner.animal;

Related

Using Join to get the updated Table Linq Entity Framework

Trying joining using Linq. What should I use? Left join or right join?
FIRST LIST SECOND LIST
APPLICANT_ID|Applicant_Name| NOTES | |APPLICANT_ID|Applicant_Name | NOTES |
1 | RAY HEAVENS | Note1 | | 2 | BEN TULFO | NoteA
2 | BEN TULFO | Note2 | | 3 | ERNIE BARON | NoteB
3 | ERNIE BARON | Note3 | |
4 | SUPERMAN | Note4 | |
5 | MARK LAPID | Note5 | |
Desired output:
APPLICANT_ID | Applicant_Name | NOTES
-------------+----------------+---------
1 | RAY HEAVENS | Note1
2 | BEN TULFO | NoteA
3 | ERNIE BARON | NoteB
4 | SUPERMAN | Note4
5 | MARK LAPID | Note5
This is my code in my controller:
var applicantList = (from a in db.Profiles
where a.isDeleted == false
select a ).ToList();
var GetNewNotes = (from a in db.ProfilesNotes
where a.isDeleted == false
select a).ToList();
var lst = (from lst1 in applicantList
where !GetNewNotes.Any(x => x.APPLICANT_ID == lst1.APPLICANT_ID )
select lst1).ToList();
ViewBag.updatedNotes = lst;
I hope someone can recommend me what to use or what to do.
Thank you in advance.
This is an odd structure to map. Structurally it looks like a 1-to-0..1, but in concept it looks like it should be 1-to-many. For a 1-to-many I'd be expecting a table structure more like:
Applicant ( ApplicantId | Name )
ApplicantNote ( ApplicantNoteId | ApplicantId | Note )
This would be mapped in EF something like:
public class Applicant
{
public int ApplicantId { get; set; }
public string Name { get; set; }
public virtual ICollection<ApplicantNote> { get; set; } = new List<ApplicantNote>();
}
public class ApplicantNote
{
public int ApplicantNoteId { get; set; }
public virtual Applicant Applicant { get; set; }
}
public class ApplicantConfig : EntityTypeConfiguration<Applicant>
{
public ApplicantConfig()
{
ToTable("Applicant");
HasKey(x => x.ApplicantId);
HasMany(x => x.ApplicantNotes)
.WithRequired(x => x.Applicant)
.Map(x => x.MapKey("ApplicantId"));
}
}
public class ApplicantNoteConfig : EntityTypeConfiguration<ApplicantNote>
{
public ApplicantNoteConfig()
{
ToTable("ApplicantNote");
HasKey(x => x.ApplicantNoteId);
}
}
What you have is more like an Applicant table that contains a note, but then there is an additional table that can hold a single, additional extra note.
Applicant ( ApplicantId | Name | Note )
ExtraApplicantNote ( ApplicantId | Note ) // Name isn't required.
which in a 1-to-0..1 would look something like:
public class Applicant
{
public int ApplicantId { get; set; }
public string Name { get; set; }
public string Note { get; set; }
public ExtraApplicantNote ExtraApplicantNote { get; set; }
}
public class ExtraApplicantNote
{
public int ApplicantId { get; set; }
public string Note { get; set; }
public virtual Applicant Applicant { get; set; }
}
public class ApplicantConfig : EntityTypeConfiguration<Applicant>
{
public ApplicantConfig()
{
ToTable("Applicant");
HasKey(x => x.ApplicantId);
HasOptional(x => x.ExtraApplicantNote)
.WithRequired(x => x.Applicant);
}
}
public class ExtraApplicantNoteConfig : EntityTypeConfiguration<ExtraApplicantNote>
{
public ExtraApplicantNoteConfig()
{
ToTable("ExtraApplicantNote");
HasKey(x => x.ApplicantId);
}
}
This joins this extra applicant note record to the Applicant as an optional associated entity. When selecting as an entity graph:
var applicant = context.Applicants
.Include(x => x.ExtraApplicantNote)
.Single(x => x.ApplicantId == applicantId);
for example... then access the note(s) via applicant.Note and applicant?.ExtraApplicantNote.Note to account for the fact that an extra applicant note is optional.
To produce an output of all notes with their applicant details, a 1-to-many structure is far, far simpler to produce:
var notes = context.ApplicantNotes.Select(x => new
{
x.Applicant.ApplicantId,
x.Applicant.Name,
x.Note
}).ToList();
To do the same thing with a 1-to-0..1 is a fair bit more involved:
var notes = context.Applicants.Select(x => new
{
x.ApplicantId,
x.Name,
x.Note
}).Union(context.ExtraApplicantNotes.Select(x => new
{
x.ApplicantId,
x.Applicant.Name,
x.Note
})).ToList();
This involves first pulling the notes from the first table, then using a union to join the same details from the optional records in the second table.
** Edit ** Sorry, I re-read the question and you want the 2nd table to override the first.
In this case, similar to above:
var notes = context.ExtraApplicantNotes.Select(x => new
{
x.ApplicantId,
x.Applicant.Name,
x.Note
}).Union(context.Applicants
.Where(x => x.ExtraApplicant == null)
.Select(x => new
{
x.ApplicantId,
x.Name,
x.Note
})).ToList();
I would go for an inner join with .Join():
var lst = applicantList.Join(GetNewNotes,
(a) => a.APPLICANT_ID,
(n) => n.APPLICANT_ID,
(a, n) => return new
{
a.APPLICANT_ID,
a.Applicant_Name,
n.Notes
});
/*
lst:
2 | BEN TULFO | NoteA,
3 | ERNIE BARON | NoteB
*/
As a side note, is there any reason your second table contains ApplicantName?
Why not keep this in Applicant table only?
EDIT:
After re-reading the question, I realized that you need the unmatched entries from the left list
too. So, that should be left outer join instead, which you achieve with .GroupJoin() and .SelectMany():
var lst = applicantList.GroupJoin(GetNewNotes,
(a) => a.Id,
(n) => n.Id,
(a, n) => new
{
Id = a.Id,
Name = a.Name,
Notes = a.Notes,
ApplicantNotes = n
})
.SelectMany(
g => g.ApplicantNotes.DefaultIfEmpty(),
(g, applicantNotes) => new
{
Id = g.Id,
Name = g.Name,
Notes = applicantNotes?.Notes ?? g.Notes
});
/*
lst:
1 | RAY HEAVENS | Note1
2 | BEN TULFO | NoteA
3 | ERNIE BARON | NoteB
4 | SUPERMAN | Note4
5 | MARK LAPID | Note5
*/

EF Core - Multiple owned types mapping issue

I wanted to ask about building the model in EntityFramework Core - v 2.1.
First things first - what I want to achieve is described below:
// I want this entity to be stored in database, with EntityReferences as owned records,
// stored within the same table.
// There will be more properties of type EntityReference.
public partial class CdiEmailEvent
{
public override Guid Id { get; set; }
public EntityReference CdiAccountId
public EntityReference CdiAutomationId
}
// EntityReference is a pointer to record, containing Id of target record and it's type.
// So Id does not point to CdiEmailEntity record, but for example an Account or Contact.
// I want this to be an owned record within CdiEmailEvent.
public sealed class EntityReference : IExtensibleDataObject
{
public Guid Id { get; set; }
public string LogicalName { get; set; }
public string Name { get; set; }
public string RowVersion { get; set; }
}
So the real data could look like:
new CdiEmailEvent {
Id = "18640407-1A44-430A-8267-96BD23CC9EE8",
CdiAccountId = new CdiAccountId {
Id = "FBBB4932-C74C-47CE-8D1B-909C5975D945",
Name = "Account",
LogicalName = "cdi_account"
},
CdiAutomationId = new CdiAccountId {
Id = "5496BC1C-C5FD-4AFB-B6D9-913DD5549C13",
Name = "Automation",
LogicalName = "cdi_automation"
},
...
}
And I'd like databese table to look like:
Id | CdiAccountId | CdiAccountName | CdiAccountLogicalName | CdiAutomationId | CdiAutomationName | CdiAutomationLogicalName
"18640407-1A44-430A-8267-96BD23CC9EE8" | "FBBB4932-C74C-47CE-8D1B-909C5975D945" | "Account" | "cdi_account" | "5496BC1C-C5FD-4AFB-B6D9-913DD5549C13" | Automation | "cdi_automation"
My current model configuration is:
modelBuilder
.Entity<CdiEmailEvent>(entity =>
{
entity.HasKey(e => e.CdiEmailEventId);
entity.OwnsOne(rel => rel.CdiAccountId);
entity.OwnsOne(rel => rel.CdiAutomationId);
...
entity.ToTable("CdiEmailEvents", this._databaseConfig.DatabaseSchema);
});
The issue that I'm encountering is:
System.InvalidOperationException: 'The entity of type 'CdiEmailEvent' is sharing
the table 'replication.CdiEmailEvents' with entities of type
'CdiEmailEvent.CdiAutomationId#EntityReference', but there is no entity of this
type with the same key value '{CdiEmailEventId: 8a99d6ab-cd82-4eb8-b548-50d903d6f26c}'
that has been marked as 'Added'.'
I guess that EF Core is trying to use CdiAutomationId as a reference to CdiEmailEvent. Does anyone know how can I alter such behavior, and enforce EF Core to treat CdiAutomationId simply as an object without any keys? I went through EF Core's documentation, but it's not very descriptive in this matter.

Parent Category Always null in Entity Framework lazy load mode

It loads category but always with null parent. I want get hierarchy category with given ID.
public static Category GetCategory(System.Guid ID, ActionLinkInfo AInfo)
{
Category category = null;
using (TIKSN.STOZE.Data.StozeContext DContext = new Data.StozeContext())
{
var cats = from cat in DContext.Categories where cat.ID == ID select cat;
foreach (Data.Category Cat in cats)
{
category = new Category(Cat, Cat.Parent == null ? null : GetCategory(Cat.Parent.ID, AInfo), AInfo);
}
}
return category;
}
Try this:
var cats = from cat in DContext.Categories.Include("Parent") where cat.ID == ID select cat;
Or you can change your model, to include that ParentID as an integer in Category class:
public class Category {
/* (...) */
public int ParentID { get; set; }
[ForeignKey("ParentID")]
public Category Parent { get; set; }
}
With that you'll be able to get Cat.ParentID without loading whole Parent object from database.
The solution were to explicitly ask for loading a parent.
public static Data.Entity.Category GetCategory(long ID)
{
Data.Entity.Category category;
using (Data.StozeContext DContext = new Data.StozeContext())
{
var categories = from SingleCategory in DContext.Categories.Include("Parent") where SingleCategory.ID == ID select SingleCategory;
category = categories.Single();
}
return category;
}

How to achieve custom object materialization from DB using Entity Framework

I am fairly new to Entity Framework and investigating converting some legacy data access code to using EF. I want to know if the following is possible in EF and if yes how.
Say I have a Customer table like this
CustomerId | ProductId | StartDate | EndDate
--------------------------------------------
100 | 999 | 01/01/2012| null
Say I also load Product data from somewhere else (like an XML file) as a cache of product objects.
public class Customer
{
public int CustomerId {get;set;}
public int Product {get;set}
public DateTime StartDate {get;set;}
public DateTime? EndDate {get;set;}
}
public class Product
{
public int ProductId {get;set;}
public int Description {get;set}
}
Currently in CustomerDal class the method uses a StoredProc to get a Customer object like this
Customer GetCustomer(int customerId)
{
// setup connection, command, parameters for SP, loop over datareader
Customer customer = new Customer();
customer.CustomerId = rdr.GetInt32(0);
int productId = rdr.GetInt32(1);
// ProductCache is a singleton object that has been initialised before
customer.Product = ProductCache.Instance.GetProduct(productId);
customer.StartDate = rdr.GetDateTime(2);
customer.EndDate = rdr.IsDbNull(3) ? (DateTime?)null : rdr.GetDateTime(3);
return customer;
}
My question is this possible using EF when it materializes the Customer object it sets the Product property not from the DB but by another method, in this case from an in memory cache. Similary when saving a new Customer object it only gets the ProductId from the Products property and saves the value in DB.
If you attach your product instances to the EF context then when loading a Customer the Product property will be automatically filled from memory without a query to database as long as the product that is associated to the customer is already attached.
For example, starting with these entities:
public class Customer
{
public int Id { get; set; }
public int ProductId { get; set; }
public string Name { get; set; }
public Product Product { get; set; }
}
public class Product
{
public int Id { get; set; }
public string Description { get; set; }
}
Products will be available globally, for simplicity, lets make it a static class:
public static class CachedProducts
{
public static Product[] All
{
get
{
return new Product[] { new Product { Id = 1, Description = "Foo" } };
}
}
}
With this in mind we just need to assure that every EF context starts with all the products attached to it:
public class CustomerContext : DbContext
{
public CustomerContext()
{
// Attach products to context
Array.ForEach(CachedProducts.All, p => this.Products.Attach(p));
}
public DbSet<Customer> Customers { get; set; }
public DbSet<Product> Products { get; set; }
}
And finally, to make the sample complete and runnable we seed the database, request a customer and print the associated product description:
public class DatabaseInitializer : CreateDatabaseIfNotExists<CustomerContext>
{
protected override void Seed(CustomerContext context)
{
var p = new Product { Id = 1, Description = "Foo" };
var c = new Customer { Id = 1, Product = p, Name = "John Doe" };
context.Customers.Add(c);
context.SaveChanges();
}
}
class Program
{
static void Main(string[] args)
{
Database.SetInitializer<CustomerContext>(new DatabaseInitializer());
using (var context = new CustomerContext())
{
var customer = context.Customers.Single(c => c.Id == 1);
Console.WriteLine(customer.Product.Description);
}
}
}
If you attach a profiler to SQL Server you will notice that the customer is loaded from database but no query is performed to obtain the product since it is already attached to the context. This works when loading a customer and also when saving a new customer with an associated product.
Disclaimer: I'm not an EF expert so this approach may have some undesired side effects that I'm unable to consider.

Parse string to poco class enum in linq query MVC 2.0

I have the following enum and POCO class
public enum Gender
{
Male,
Female,
Unknown
}
public class Person
{
public int PersonId { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public Gender? Gender { get; set; }
}
I would like to perform a "get all people" query in my repository such that it would look something like this:
return from p in _db.People
select new Model.Person
{
PersonId = p.PersonId,
LastName = p.LastName,
FirstName = p.FirstName,
Gender = p.Gender,
};
Unfortunately I get an error "Cannot implicitly convert type 'string' to 'Model.Gender'"
I would like to convert the string which is being queried from the entity framework to my Gender enum and assign it to my POCO class.
Enums are not supported in Entity Framework. There is a workaround by Alex James, but it's quite involved.
Instead, i prefer to do this:
public enum Gender : byte
{
Male = 1,
Female,
Unknown
}
public class Person
{
public int PersonId { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public byte Gender { get; set; } // this is the EF model property
public Gender GenderType // this is an additional custom property
{
get { return (Gender) Gender; }
set { Gender = (byte)value; }
}
}
It's basically a hook/wrapper for the actual value. In your database, store Gender as a tinyint (which maps to byte on the conceptual side).
Then you can use a byte enum to map to and from the model property:
return from p in _db.People
select new Model.Person
{
PersonId = p.PersonId,
LastName = p.LastName,
FirstName = p.FirstName,
Gender = p.Gender, // sets byte
};
But then if you access that ViewModel, because your setting the byte field for Gender, you will also have access to the enum property GenderType.
Does that solve your problem?
The Entity Framework that I am familiar with does not provide support for enums. EF uses your query expression to create an SQL statement that it then sends to the server, if it cannot create the SQL equivalent of some operation it will throw a NotSupportedException for that operation. If you are expecting to return a small set of data you can separate from the Entity Framework by creating an object in memory using the ToArray method.
var myEntities = (from entity in _db.Entities
where /* condition */
select entity)
.ToArray();
This will create a sequence of entities in memory. Any further query statements will then be in the realm of LINQ to Objects which allows parsing of strings into enums:
return from myEntity in myEntities
select new MyDataContract
{
ID = myEntity.ID,
Gender g = (Gender)Enum.Parse(typeof(Gender), myEntity.Gender, true)
};
Or you could even break it out into a foreach loop:
List<MyDataContract> myDataContracts = new List<MyDataContract>();
foreach (var myEntity in myEntities)
{
var dataContract = new MyDataContract { ID = myEntity.ID };
if (Enum.IsDefined(typeof(Gender), myEntity.Gender))
dataContract.Gender = (Gender)Enum.Parse(typeof(Gender), myEntity.Gender, true);
myDataContracts.Add(dataContract);
}
return myDataContracts.AsEnumerable();
if (Enum.IsDefined(typeof(Gender), genderstring))
Gender g = (Gender) Enum.Parse(typeof(Gender), genderstring, true);
else
//Deal with invalid string.
try
Gender = p.Gender != null ? (Gender)Enum.Parse(typeof(Gender), p.Gender) : (Gender?)null;
To parse the string as one of the enums
here's a workaround but it means changing your nice and clean POCO
http://blogs.msdn.com/b/alexj/archive/2009/06/05/tip-23-how-to-fake-enums-in-ef-4.aspx