Map entity to view and table - c#

Consider the model below. I have an Order class and an OrderLine class. The Order.TotalAmount is calculated through a view which performs an OUTER APPLY across all the Order.OrderLines.
[Table("SelectOrder")]
public class Order
{
public decimal TotalAmount { get; set; }
public virtual ICollection<OrderLine> OrderLines { get; set; }
}
[Table("SelectOrderLine")]
public class OrderLine
{
public decimal Amount { get; set; }
public virtual Order Order { get; set; }
}
I have decorated my classes with the TableAttribute to enable Entity Framework Core to get the data from the views to the entity. The TableAttribute actually points to the view instead.
Now I would like to perform inserts, updates and deletes. This poses a problem as it's not possible to use a view with an OUTER APPLY for these changes. I've tried using query types for this but you cannot define an entity as both a query type and an entity type. Doing so results in an error for me. So adding a TableAttribute with the actual table e.g. Order in combination with modelBuilder.Query<Order>().ToView("SelectOrder"); does not work.
I could create a separate class SelectOrder which is mapped to the view and map my Order entity to the table. Or I could build a custom attribute and perform some custom SQL generation by overriding the SqlServerQuerySqlGenerator.
But before I go down these roads... Is it really not possible to map an entity to both a view for selects and a table for inserts, updates and deletes?

Maybe not the answer you're looking for, but you could add a calculated OrderTotal attribute to the Order table in the database.

Since .NET 5 Preview version was released it's possible to support a separation of query and update the mapping
ref. https://github.com/dotnet/efcore/issues?q=is%3Aissue+milestone%3A5.0.0-preview2+is%3Aclosed+label%3Atype-enhancement+is%3Aclosed

Related

How to use same DTO foreign key relationship for table and view

I'm using ORMLite and I have a foreign key relationship defined between an invoice DTO and invoice line DTO:
public interface IDto<PKType> where PKType : struct
{
[Required(ErrorMessage = "Id is required")]
PKType Id { get; set; }
}
[Ss.Alias("Invoices")]
public class InvoiceDto : AuditedDto, IAuditedDto, IDto<Guid>
{
[Required]
public Guid VendorId { get; set; }
[Ss.Reference]
public List<InvoiceLineDto> InvoiceLines { get; set; }
}
[Ss.Alias("InvoiceLines")]
public class InvoiceLineDto : AuditedDto, IAuditedDto, IDto<Guid>
{
[Required]
public string Item { get; set; }
[Required]
public int Qty { get; set; }
[Required]
public decimal UnitPrice { get; set; }
[Ss.ForeignKey(typeof(InvoiceDto))]
public Guid InvoiceId { get; set; }
}
Now I have defined a view across my invoices that returns the same data but includes the invoice status:
[Ss.Alias("InvoiceStatus")]
public class InvoiceStatusDto : InvoiceDto
{
public int Status { get; set; }
}
But when I query the InvoiceStatus view this gives a System.ArgumentException: Cant find 'InvoiceStatusId' Property on Type 'InvoiceLineDto'. I'm unsure how to address this short of duplicating the InvoiceLineDto specifically to define the foreign key relationship. Is there some way to handle this situation using code instead of annotations perhaps?
OrmLite foreign key and references attributes relies on naming conventions. The issue is that the InvoiceLines table maintains a foreign key to the Invoices table not the InvoiceStatus table.
You're doing a disservice by trying to overly use Inheritance in your Data Models for presumably some pursuit of DRY. OrmLite's POCO Data Models should map 1:1 to RDBMS tables, the use of multiple levels of inheritance suggests this isn't the case or if it is the underlying RDBMS has an unnecessary explosion of tables.
Whilst it should be a goal to DRY your implementation logic where it doesn't cause coupling conflicts, this does not apply to your POCOs or Schema definitions...
Hiding Properties by abusing Inheritance
Hiding properties isn't duplicating code, it's making the intent clear. Properties are declarative, hiding them just makes it harder to read and reason about your code which requires having to open multiple source files and puts the burden on the developer reading your code and increases their cognitive load required by forcing them to merge and construct the schema from multiple source files in their head instead of them being able to look at a single source file with all the properties of the table explicitly listed.
Multiple levels of inheritance in Data Models is an abuse of inheritance where I suspect it's the cause of tight coupling issues and poor RDBMS design. I don't see how you need a completely separate InvoiceStatus table instead of maintaining a single Invoice table that includes the Status field.
Other issues with your table structure is the Dto suffix which denotes these are DTOs not data models. Whilst it's perfectly reasonable to reuse OrmLite's Data Model classes as DTOs the Dto suffix suggests you're maintaining separate purpose-specific DTO classes (i.e. for Serialization) separate from your Data Models. But this example instead adds [ForeignKey] attributes and to DTO classes, instead of having them on Data Model classes.
The other naming issue is that some of your RDBMS tables use inconsistent plural naming conventions with [Ss.Alias("InvoiceLines")] being plural whilst [Ss.Alias("InvoiceStatus")] is not.

EntityFramework 6 DatabaseFirst add navigation property from code

I have (can`t change) EF DataBase first project without navigation property in models.
I want extend autogenerated models and add navigation property
Generated model
//generated.cs
public partial class company
{
public int id { get; set; }
public string name { get; set; }
}
public partial class user
{
public int id { get; set; }
public int company_id { get; set; }
}
I want add navigation property from code
//model_extension.cs
public partial class user
{
public company Company { get; set; }
}
I have exception "The specified type member 'Company' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported."
I work with CodeFirst before.
I understand, I must link user.company_id to Company
But not understand how make this with code (not designer)
In Database First Approach, You are generating your POCO objects from database schema via Entity Framework Designer/ADO.NET Entity Data Model so it is not flexible as Code-First, you need to go on database, and change the schema yourself and update your .edmx file. while adding properties to these Models are possible in c# side, but they are not going to be added to your database schema,
I suggest your reverse your database schema and go as Code-First Approach, This nuget package can do this for you.
After Reversing It's all about Code-First then, creating your own DbContext and OnModelCreating and let the Migration handle the rest. Then you can use Eager Loading of EF to load your data,

Include statement not working with LINQ query

Trying to iron out some issues with a an MVC project using models derived from existing database tables the developer built, but none of those tables has its relationships set up (they all have primary keys, just no relationships). As such the project uses ViewModels to get some things done when more than one table is needed.
I'm attempting to get around this by adding the necessary items to relate two of the tables.
Table One's POCO:
namespace Project.Models
{
using System;
using System.ComponentModel.DataAnnotations;
public partial class Table_One
{
[ScaffoldColumn(false)]
public short ID { get; set; }
[Display(Name="Name")]
public string NAME { get; set; }
[Display(Name = "Owner")]
public string OWNER { get; set; }
[Display(Name = "Property")]
public Nullable<decimal> PROPERTY { get; set; }
public virtual Table_Two Table_Two { get; set; }
}
}
Table Two's POCO:
namespace Project.Models
{
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
public partial class Table_Two
{
public Table_Two()
{
this.Table_One = new HashSet<Table_One>();
}
[ScaffoldColumn(false)]
public short ID { get; set; }
[Display(Name="Property")]
[MaxLength(20)]
public string Property { get; set; }
public ICollection<Table_One> Table_One { get; set; }
}
}
Were these tables set up with their relationships, they'd be joined on the Property values (I've sanitized the table names and properties). As far as I can tell I've set things up to mirror the way another project I've been working on, which has tables that are related, has been set up to include the relationships, but when I run this LINQ query:
var model = context.Table_One.Include(t => t.Table_Two);
I get the following error message:
"A specified Include path is not valid. The EntityType 'X.Table_One'
does not declare a navigation property with the name 'Table_Two'."
Originally these were being joined via a LINQ query using query syntax to select each of the table's properties into the ViewModel's properties.
I've tried removing the include, but that doesn't gel with other code in the controller. I've tried changing the ICollection to an IList to no avail. I've searched other answers here but none of them seem to solve the problem I'm having.
As far as I know the developer who started the project built the POCOs using a code generation tool that was run against the EDMX in the project. The only other thing I can think of at this point would be to have the developer add the relationships, then update the POCOs to pull in the updated tables via the EDMX.
I should also specify that the ID in Table_Two would be a foreign key in the PROPERTY column of Table_One.
You should use Include with properties which are collections of related entities
var model = context.Table_Two.Include(t => t.Table_One);
Include tries to fetch collection of related entities by using joins on sql query.
If you want to load a related entity you can use lazy loading or load it using the code below
context.Entry(table_one).Reference(x => x.Table_Two).Load();
To rely on Entity Framework's related entities loading, you need to set the entities relations.
Since you said "but none of those tables has its relationships set up", you won't be able to use nor lazy neither eager loading, you have to fetch the other entities by hand.

Can Entity Framework automatically extend models with datetime fields to keep the changing date

Can you tell Entity Framework to add an extra field for each field of a certain type? For example: Is it possible to generate a ChangedAt datetime field for each boolean field defined in the model, so this
public bool Confirmed { get; set; }
could result in a table with an additional field ConfirmedChangedAt where the value is updated each time the boolean value is changed.
Usually behavior like this should be implemented directly into your business logic and not automatically into the data layer. So I suggest to write something like this:
// entity
public class Order
{
public bool Confirmed { get; set; }
public DateTime? ConfirmedAt { get; set; }
}
// business logic
public class OrderManager
{
.................
public void Confirm( Order order )
{
// changing of entity status
order.Confirmed = true;
order.ConfirmedAt = DateTime.Now;
// storing new entity status
_orderRepository.Update( order );
................
}
}
I think if i understand you correctly, You are expecting the Entity Framework to be able to add columns to the database automatically so that you don't have to add them manually, Well you have 2 cases:
if you are using the database first approach you could achieve this
by using a query that's specific to your needs to add these columns
for you based on the conditions you have.
If you are using the code first approach and you have an existing database you may reverse engineer the database using the Entity Framework Power Tools and you could customize the T4 Templates to generate the entities with the extra properties that you need.
Plain answer no.
But it's depend on way how you interact with EF (code first, model first,database first).
If you using EF 6 and code first approach you can use idea of base Entity class
public class BaseEntity
{
public DateTime ChangedAt {get;set;}
}
public class ConcreteEntity : BaseEntity
{
public string Name {get;set;}
}
Now ConcreteEntity has ChangedAt by inheritance.
If this solution not for you, please explain question with more details.

Entity Framework associations with multiple (separate) keys on view

I'm having problems setting up an Entity Framework 4 model.
A Contact object is exposed in the database as an updateable view. Also due to the history of the database, this Contact view has two different keys, one from a legacy system. So some other tables reference a contact with a 'ContactID' while other older tables reference it with a 'LegacyContactID'.
Since this is a view, there are no foreign keys in the database, and I'm trying to manually add associations in the designer. But the fluent associations don't seem to provide a way of specifying which field is referenced.
How do I build this model?
public class vwContact
{
public int KeyField { get; set; }
public string LegacyKeyField { get; set; }
}
public class SomeObject
{
public virtual vwContact Contact { get; set; }
public int ContactId { get; set; } //references vwContact.KeyField
}
public class LegacyObject
{
public virtual vwContact Contact { get; set; }
public string ContactId { get; set; } //references vwContact.LegacyKeyField
}
ModelCreatingFunction(modelBuilder)
{
// can't set both of these, right?
modelBuilder.Entity<vwContact>().HasKey(x => x.KeyField);
modelBuilder.Entity<vwContact>().HasKey(x => x.LegacyKeyField);
modelBuilder.Entity<LegacyObject>().HasRequired(x => x.Contact).???
//is there some way to say which key field this reference is referencing?
}
EDIT 2: "New things have come to light, man" - His Dudeness
After a but more experimentation and news, I found using a base class and child classes with different keys will not work by itself. With code first especially, base entities must define a key if they are not explicitly mapped to tables.
I left the suggested code below because I still recommend using the base class for your C# manageability, but I below the code I have updated my answer and provided other workaround options.
Unfortunately, the truth revealed is that you cannot accomplish what you seek without altering SQL due to limitations on EF 4.1+ code first.
Base Contact Class
public abstract class BaseContact
{
// Include all properties here except for the keys
// public string Name { get; set; }
}
Entity Classes
Set this up via the fluent API if you like, but for easy illustration I've used the data annotations
public class Contact : BaseContact
{
[Key]
public int KeyField { get; set; }
public string LegacyKeyField { get; set; }
}
public class LegacyContact : BaseContact
{
public int KeyField { get; set; }
[Key]
public string LegacyKeyField { get; set; }
}
Using the Entities
Classes that reference or manipulate the contact objects should reference the base class much like an interface:
public class SomeCustomObject
{
public BaseContact Contact { get; set; }
}
If later you need to programmatically determine what type you are working with use typeof() and manipulate the entity accordingly.
var co = new SomeCustomObject(); // assume its loaded with data
if(co.Contact == typeof(LegacyContact)
// manipulate accordingly.
New Options & Workarounds
As I suggested in comment before, you won't be able to map them to a single view/table anyway so you have a couple options:
a. map your objects to their underlying tables and alter your "get/read" methods on repositories and service classes pull from the joined view -or-
b. create a second view and map each object to their appropriate view.
c. map one entity to its underlying table and one to the view.
Summary
Try (B) first, creating a separate view because it requires the least amount of change to both code and DB schema (you aren't fiddling with underlying tables, or affecting stored procedures). It also ensures your EF C# POCOs will function equivalently (one to a view and one to table may cause quirks). Miguel's answer below seems to be roughly the same suggestion so I would start here if it's possible.
Option (C) seems worst because your POCO entities may behave have unforseen quirks when mapped to different SQL pieces (tables vs. views) causing coding issues down the road.
Option (A), while it fits EF's intention best (entities mapped to tables), it means to get your joined view you must alter your C# services/repositories to work with the EF entities for Add, Update, Delete operations, but tell the Pull/Read-like methods to grab data from the joint views. This is probably your best choice, but involves more work than (B) and may also affect Schema in the long run. More complexity equals more risk.
Edit I'm not sure this is actually possible, and this is why:
The assumption is that a foreign key references a primary key. What you've got is two fields which are both acting as primary keys of vwContact, but depending on which object you ask it's a different field that's the primary key. You can only have one primary key at once, and although you can have a compound primary key you can't do primary key things with only half of it - you have to have a compound foreign key with which to reference it.
This is why Entity Framework doesn't have a way to specify the mapping column on the target side, because it has to use the primary key.
Now, you can layer some more objects on top of the EF entities to do some manual lookup and simulate the navigation properties, but I don't think you can actually get EF to do what you want because SQL itself won't do what you want - the rule is one primary key per table, and it's not negotiable.
From what you said about your database structure, it may be possible for you to write a migration script which can give the contact entities a consistent primary key and update everything else to refer to them with that single primary key rather than the two systems resulting from the legacy data, as you can of course do joins on any fields you like. I don't think you're going to get a seamlessly functional EF model without changing your database though.
Original Answer That Won't Work
So, vwContact contains a key KeyField which is referenced by many SomeObjects and another key LegacyKeyField which is referenced by many LegacyObjects.
I think this is how you have to approach this:
Give vwContact navigation properties for SomeObject and LegacyObject collections:
public virtual ICollection<SomeObject> SomeObjects { get; set; }
public virtual ICollection<LegacyObject> LegacyObjects { get; set; }
Give those navigation properties foreign keys to use:
modelBuilder.Entity<vwContact>()
.HasMany(c => c.SomeObjects)
.WithRequired(s => s.Contact)
.HasForeignKey(c => c.KeyField);
modelBuilder.Entity<vwContact>()
.HasMany(c => c.LegacyObjects)
.WithRequired(l => l.Contact)
.HasForeignKey(c => c.LegacyKeyField);
The trouble is I would guess you've already tried this and it didn't work, in which case I can't offer you much else as I've not done a huge amount of this kind of thing (our database is much closer to the kinds of thing EF expects so we've had to do relatively minimal mapping overrides, usually with many-to-many relationships).
As for your two calls to HasKey on vwContact, they can't both be the definitive key for the object, so it's either a compound key which features both of them, or pick one, or there's another field you haven't mentioned which is the real primary key. From here it's not really possible to say what the right option there is.
You should be able to do this with two different objects to represent the Contact view.
public class vwContact
{
public int KeyField { get; set; }
public string LegacyKeyField { get; set; }
}
public class vwLegacyContact
{
public int KeyField { get; set; }
public string LegacyKeyField { get; set; }
}
public class SomeObject
{
public virtual vwContact Contact { get; set; }
public int ContactId { get; set; } //references vwContact.KeyField
}
public class LegacyObject
{
public virtual vwLegacyContact Contact { get; set; }
public string ContactId { get; set; } //references vwLegacyContact.LegacyKeyField
}
ModelCreatingFunction(modelBuilder)
{
// can't set both of these, right?
modelBuilder.Entity<vwContact>().HasKey(x => x.KeyField);
modelBuilder.Entity<vwLegacyContact>().HasKey(x => x.LegacyKeyField);
// The rest of your configuration
}
I have tried everything that you can imagine, and found that most solutions won't work in this version of EF... maybe in future versions it supports referencing another entity by using an unique field, but this is not the case now. I also found two solutions that work, but they are more of a workaround than solutions.
I tried all of the following things, that didn't work:
Mapping two entities to the same table: this is not allowed in EF4.
Inheriting from a base that has no key definitions: all root classes must have keys, so that inherited classes share this common key... that is how inheritance works in EF4.
Inheriting from base class that defines all fields, including keys, and then use modelBuilder to tell wich base-properties are keys of the derived types: this doesn't work, because the methos HasKey, Property and others that take members as parameters, must reference members of the class itself... referencing properties of a base class is not allowed. This cannot be done: modelBuilder.HasKey<MyClass>(x => x.BaseKeyField)
The two things that I did that worked:
Without DB changes: Map to the table that is source of the view in question... that is, if vwContact is a view to Contacts table, then you can map a class to Contacts, and use it by setting the key to the KeyField, and another class mapping to the vwContacts view, with the key being LegacyKeyField. In the class Contacts, the LegacyKeyField must exist, and you will have to manage this manually, when using the Contacts class. Also, when using the class vwContacts you will have to manually manage the KeyField, unless it is an autoincrement field in the DB, in this case, you must remove the property from vwContacts class.
Changing DB: Create another view, just like the vwContacts, say vwContactsLegacy, and map it to a class in wich the key is the LegacyKeyField, and map vwContacts to the original view, using KeyField as the key. All limitations from the first case also applies: the vwContacts must have the LegacyKeyField, managed manually. And the vwContactsLegacy, must have the KetField if it is not autoincrement idenitity, otherwise it must not be defined.
There are some limitations:
As I said, these solutions are work-arounds... not real solutions, there are some serious implications, that may even make them undesirable:
EF does not know that you are mapping two classes to the same thing. So when you update one thing, the other one could be changed or not, it depends if the objects is cached or not. Also, you could have two objects at the same time, that represents the same thing on the backing storage, so say you load a vwContact and also a vwContactLegacy, changes both, and then try to save both... you will have to care about this yourself.
You will have to manage one of the keys manually. If you are using vwContacts class, the KeyFieldLegacy is there, and you must fill it. If you want to create a vwContacts, and associate is with a LegacyObject, then you need to create the reference manually, because LegacyObject takes a vwContactsLegacy, not a vwContacts... you will have to create the reference by setting the ContactId field.
I hope that this is more of a help than a disillusion, EF is a powerfull toy, but it is far from perfect... though I think it's going to get much better in the next versions.
I think this may be possible using extension methods, although not directly through EF as #Matthew Walton mentioned in his edit above.
However, with extension methods, you can specify what to do behind the scenes, and have a simple call to it.
public class LegacyObject
{
public virtual vwContact Contact { get; set; }
public string ContactId { get; set; } //references vwContact.LegacyKeyField
}
public class LegacyObjectExtensions
{
public static vwContact Contacts(this LegacyObject legacyObject)
{
var dbContext = new LegacyDbContext();
var contacts = from o in legacyObject
join c in dbContext.vwContact
on o.ContactId == c.LegacyKeyField
select c;
return contacts;
}
}
and
public class SomeObject
{
public virtual vwContact Contact { get; set; }
public int ContactId { get; set; } //references vwContact.KeyField
}
public class SomeObjectExtensions
{
public static vwContact Contacts(this SomeObject someObject)
{
var dbContext = new LegacyDbContext();
var contacts = from o in someObject
join c in dbContext.vwContact
on o.ContactId == c.KeyField
select c;
return contacts;
}
}
Then to use you can simply do like this:
var legacyContacts = legacyObject.Contacts();
var someContacts = someObject.Contacts();
Sometimes it makes more sense to map it from the other end of the relationship, in your case:
modelBuilder.Entity<LegacyObject>().HasRequired(x => x.Contact).WithMany().HasForeignKey(u => u.LegacyKeyField);
however this will require that u.LegacyKeyField is marked as a primary key.
And then I'll give my two cents:
if the Legacy db is using LegacyKeyField, then perhaps the legacy db will be read only. In this case we can create two separate contexts Legacy and Non-legacy and map them accordingly. This can potentially become a bit messy as you'd have to remember which object comes from which context. But then again, nothing stops you from adding the same EF code first object into 2 different contexts
Another solution is to use views with ContactId added for all other legacy tables and map them into one context. This will tax performance for the sake of having cleaner context objects, but this can be counteracted on sql side: indexed views, materialized views, stored procs, etc. So than LEGACY_OBJECT becomes VW_LEGACY OBJECT with CONTACT.ContactId brought over, then:
modelBuilder.Entity<LegacyObject>().ToTable("VW_LEGACY_OBJECT");
modelBuilder.Entity<LegacyObject>().HasRequired(x => x.Contact).WithMany().HasForeignKey(u => u.ContactId);
I personally would go with creating "mapper views" with CustomerId on legacy tables, as it's cleaner from c# layer perspective and you can make those views look like real tables. It is also difficult to suggest a solution without knowing what exactly is the scenario that you have a problem with: querying, loading, saving, etc.

Categories

Resources