How to implement Entity Framework if my tables have unconventional column names? - sql

I've joined a team that uses non standard names for tables and columns, and have trouble building database-first projects with Entity Framework.
Here's my problem:
tFWAClientProcessing (Table)
FWAClientHandling (Primary Key, INT)
iClientID (Foreign Key, INT)
.
tClients (Table)
AClientID (Primary Key, INT)
sClientName (VARCHAR(255))
I need Entity Framework to detect the relationship between these two tables without making changes to those tables in production.

I'd long given up on EDMX and convention-based mapping for relationships and just set up EF via EntityConfiguration classes. Attributes in the entity definitions are another option which should work for simple cases like identifying column names. You can also wire up mapping in the OnModelCreating override directly.
For instance: To have entities called Client and FWAClientProcessing for that table structure:
public class Client
{
public int ClientId { get; set; }
public string ClientName { get; set; }
}
public class FWAClientProcessing
{
public int FWAClientProcessingId { get; set; }
public virtual Client Client { get; set; }
}
public class ClientConfiguration : EntityTypeConfiguration<Client>
{
public ClientConfiguration()
{
ToTable("tClients"); // assumes default schema, i.e. "dbo" in SQL Server. Can add schema name as 2nd parameter otherwise.
HasKey(x => x.ClientId)
.Property(x => x.ClientId)
.HasColumnName("iClientID");
Property(x => x.ClientName)
.HasColumnName("sClientName");
}
}
public class FWAClientProcessingConfiguration : EntityTypeConfiguration<FWAClientPrcessing>
{
public FWAClientProcessingConfiguration()
{
ToTable("tFWAClientProcessing");
HasKey(x => x.FWAClientProcessingId)
.Property(x => x.FWAClientProcessingId)
.HasColumnName("FWAClientHandling");
HasRequired(x => x.Client)
.WithMany()
.Map(x => x.MapKey("iClientID"));
}
}
Assuming that the EntityTypeConfiguration classes are in the same assembly as the entities, and the DBContext, registering them in the context becomes:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.AddFromAssembly(TypeOf(YourDbContex).Assembly);
base.OnModelCreating(modelBuilder);
}
These examples are for EF6, EF Core uses the concept of Shadow Properties for mapping FK relationships without exposing FK properties, and can accommodate the different column naming. EntityTypeConfiguration is available as an Interface with a Configure method accepting the builder.
I favor using the explicit entity type configuration by default as it keeps the configuration nicely isolated and out of the way and can handle all mapping scenarios that might come up that annotations cannot do. It's a bit of a one-off cost to set up, but at least then you have full visibility and control over how the schema is mapped and not simply hoping EF works things out. :)

Use the modern replacement for EDMX-based Database-First and reverse-engineer a code-first model from the existing database. Customizing an EDMX-based model with its mappings is a rabbit-hole of obsolete technology.
This is available for EF Core and EF6.
The reverse-engineered model is then a starting point for you to make model customizations, like mapping the tables and columns to sensible names, and configuring any Navigation Properties that for whatever reason didn't get picked up by the tooling.

You are right, it is easier if people follow the entity framework conventions. However, if you have to deviate from them, OnModelCreating is your friend.
In OnModelCreating, from every Table, column, relation between tables, that are not standard, you can inform entity framework about these deviations.
You can give different table names
You can use other column names
You can say that certain properties should be saved in certain database formats, for instance ProductPrice is a decimal with 2 digits after the decimal point, instead of the default number of digits.
etc.
There seems to be a one-to-many relation between Clients and ClientsProcessing: every Client with primary key Id, has zero or more ClientsProcessings, every ClientProcessing belongs to exactly one Client, namely the Client that the foreign key ClientId refers to.
You want to use unconventional table names, unconventional names for you primary and foreign keys, and you need to inform about what keys are used to define the one-to-many relation.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Configure DbSet<Client>:
ver clients = modelBuilder.Entity<Client>();
clients.ToTable("tClients")
.HasKey(client => client.Id)
// property Id should be in "AClientID"
clients.Property(client => client.Id).HasColumnName("AClientID");
clients.Property(client => client.Name).HasColumnName("sClientName");
Apart from different names of the columns, you can also declare whether the properties are required or optional, what format they should have (is a decimal with two digits after the decimal point, or does it have four digits?), etc.
Do something similar for modelBuilder.Entity<ClientProcessing>();
For the one-to-many relation: every Client has zero or more ClientProcessings; every ClientProcessing belongs to exactly one (required!) Client, namely the foreign key that ClientId refers to:
clients.HasMany(client => client.ClientProcessings)
.WithRequired(clientProcessing => clientProcessing.Client)
.HasForeignKey(clientProcessing => clientProcessing.ClientId);
Or if you want, you can start at ClienProcessing: every ClientProcessing has exactly one Client (required!), using foreign key ClientId. Every Client has many ClientProcessings.
modelBuilder.Entity<ClientProcessing>()
.HasRequired(clientProcessing => clientProcessing.Client)
.WithMany()
.HasForeignKey(clientProcessing => clientProcessing.ClientId);
Note: by default this will cascade on delete: whenever you delete a client, you will also delete all its processings: you did define there are no processings without a client.
In some relations, you don't want this, especially many-to-many relations or one-to-zero-or-one relation: a Student may have zero or one School-supplied-Laptop. If you delete the Laptop, you don't want to delete the Student as well. In that case you'll have to add .WillCascadeOnDelete(false)

Related

EF Core: error when attempting to add table to ApplicationDbContext

I have a .NET 6 / EF Core 6 DB-first application with the standard ApplicationDbContext inherited from IdentityContext. I would like to extend this by adding a table from the whole database (included in my other 'business' DbContext).
However, adding just one table as so:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
{
}
public DbSet<business_units> business_units { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<business_units>().ToTable("business_units"); //New table
}
}
yields an error upon running the following in my AuthManager:
var u =_context.Users.Where(x => x.UserName.ToUpper() == email.ToUpper()).First();
System.InvalidOperationException: 'The entity type 'AspNetUserLogins' requires a primary key to be defined. If you intended to use a keyless entity type, call 'HasNoKey' in 'OnModelCreating'. For more information on keyless entity types,
_context is of type ApplicationDbContext.
The code runs fine without me attempting to add this table and I can't see how I'm somehow invalidating the primary key requirement on a different Identity table by adding this seemingly unconnected table.
I'd wondered if it was an issue with a table being in more than one context?
Update:
It appears that it has something to do with having foreign keys in the Identity tables (ie I can add a table that is not related to the Identity tables this way). So does this mean I need to bring the Identity tables into the context (which feels like duplication given their derived objects such as 'User' are already therein)? It also doesn't seem like that's what the error is telling me - my added table has no relationships with AspNewUserLogins.

Entity Framework Update Key

[Table("tblClients")]
public class ClientsTbl
{
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ClientID { get; set; }
[Key]
public int userid { get; set; }
}
If i have old data (from unknown source) to import which has userid that related with other tables for example invoice etc without using Foreign Key, how can i design my DB model? If i would design like that insert must be like that :
Random a = new Random();
ClientsTbl c = new ClientsTbl()
{
userid = a.Next(-1000, -1),
}
dataContext.Clients.Add(c);
dataContext.SaveChanges();
var client = dataContext.Clients.FirstOrDefault(x => x.userid == c.userid);
if(client!=null)
{
client.userid = c.ClientID;
}
dataContext.SaveChanges();
after inserting a record I need to update "userid" : `` And it gives an error
The property 'userid' is part of the object's key information and cannot be modified.
If i would change all operations to Stored Procedure's (insert and update) everything is ok.
Why Entity Framework doesn't allow to update Key ? (Sql server does)
This is due to the fact that you're referencing the same object. This has been asked before and answered here:
The property 'Id' is part of the object's key information and cannot be modified
I hope this helps you.
OO paradigm has this thing about "object identity". RM does not have this (not in the sense that every identifier of a thing MUST MANDATORILY remain unchanged during the whole lifetime of the thing it identifies).
Tools that attempt to bridge the gap between the two are therefore inevitably faced with a kind of mismatch, and must do one of two things to address it : (a) break the OO paradigm of cast-in-concrete-immutable identity, or (b) enforce the OO paradigm and rule out perfectly legitimate RM operations.
Most if not all of them opt for (b) because that is the easiest way out, at least for the developers.

Mapping an extension table that might not have a row

I'm working with Fluent nHibernate on a legacy database and have a main Person table and several extension tables containing additional information about the person. These extension tables are one-to-one, meaning that a person will only have one row on the extension table and the extension table should always map back to one person.
Table: Person
Columns: PersonID, FirstName, LastName, etc.
Table: PersonLogin
Columns: PersonID (FK, unique), UserName, Password, etc.
I have my mappings defined as this (with the irrelevant properties omitted):
public PersonMap()
{
Table("Person");
Id(x => x.Id, "PersonID").Not.Nullable();
References(x => x.Login, "PersonID").LazyLoad();
}
public LoginMap()
{
Table("PersonLogin");
Id(x => x.Id, "PersonID").GeneratedBy.Foreign("Person");
References(x => x.Person, "PersonID").LazyLoad();
}
This works when I have data on both tables, but I recently learned that some of the extension tables don't have data for all Person rows. This caused me to get errors during the query. So, I added .NotFound.Ignore() to my PersonMap making it look like this:
References(x => x.Login, "PersonID").LazyLoad().NotFound.Ignore();
That caused me to get unnecessary selects from the Login table due to https://nhibernate.jira.com/browse/NH-1001 when my business layer doesn't need to project any of the extension table values. It is causing the performance to be terrible in some of my search queries.
I've scoured a lot of posts, but haven't found a rock solid answer about how to address this scenario. Below are the options I've tried:
Option One:
Create rows on the extension table to ensure there is no Person without a row on the extension table and then remove the .NotFound.Ignore().
The issue with this option is that it's a legacy database and I'm not sure where I'd need to update to ensure that a PersonLogin is inserted when a Person is inserted.
Option Two:
Remove the PersonLogin reference from my PersonMap and custom load it inside my Person class. Like this:
public class Person
{
/// <summary> Gets or sets the PersonID </summary>
public virtual int Id { get; set; }
private bool loadedLogin;
private PersonLogin login;
public virtual PersonLogin Login
{
get
{
if (!loadedLogin)
{
login = SessionManager.Session().Get<PersonLogin>(Id);
loadedLogin = true;
}
return login;
}
set
{
login = value;
loadedLogin = true;
}
}
}
The issue I'm having with it is that I can't eagerly fetch the data when performing a query to pull back a large number of Person objects and their Logins.
Option Three:
I just started playing to see if I could write a custom IEntityNotFoundDelegate to not throw the exception for these objects.
private class CustomEntityNotFoundDelegate : IEntityNotFoundDelegate
{
public void HandleEntityNotFound(string entityName, object id)
{
if (entityName == "my.namespace.PersonLogin")
{
return;
}
else
{
throw new ObjectNotFoundException(id, entityName);
}
}
}
And I added this to the config
cfg.EntityNotFoundDelegate = new CustomEntityNotFoundDelegate();
It catches my scenario and returns back now instead of throwing the error, but now when I try to project those PersonLogin properties onto my business objects, it's attempting to use the Proxy object and throws this error that I'm trying to figure out if I can handle cleanly (possibly in a IPostLoadEventListener).
System.Reflection.TargetException occurred
Message = Non-static method requires a target
I think I've got this working now by keeping the .NotFound.Ignore().
I originally stated:
That caused me to get unnecessary selects from the Login table due to https://nhibernate.jira.com/browse/NH-1001 when my business layer doesn't need to project any of the extension table values. It is causing the performance to be terrible in some of my search queries.
I was able to tweak my LINQ queries to use the IQueryOver in some instances and to improve my use of LINQ in other scenarios to project only the necessary values. This appears to have resolved the queries from pulling back the extension tables since their values were not needed in the projections.
I thought that my queries weren't projecting these extension tables, but figured out that I had a method ToKeyValuePair that I was using in the projection to concatenate the ID and a Name field together of some related properties. That method was causing the objects to load completely since LINQ wasn't able to determine that the needed fields were present without joining to the extension table.

Fluent NHibernate Mapping Single Column to Composite Key

I have a situation where i have defined an entity in my domain model in which I would like to expose a single id column.
public class OfferedProduct
{
public virtual string Id {get; set;}
//other properties
}
The legacy database table this will map to is
CREATE TABLE ProductGrouping
MemberNumber INT NOT NULL,
GroupId CHAR NOT NULL,
...
I dont want to compromise the domain model by introducing two properties and mapping them using the "CompositeId" construct.
CompositeId().KeyProperty(x => x.MemberNumber).KeyProperty(x => x.GroupId)
What I want ideally is to concatenate the two values in the form {MemberNumber}{GroupId} and expose this as the Id value. I would then use a Custom Type to handle how these values are concatenated when retrieved from the DB and broken apart when saving/selecting.
I have noticed that the "CompositeId" method does not allow a customType as with the standard "Id" call; but the "Id" method does not provide the ability to set multiple columns. I have seen examples where people have used "Map" to combine two columns using a custom type, but not for id values.
I have noticed the "CompositeId" has an overload that can take a custom identity class but I am unsure how to use it in this scenario.
CompositeId<OfferedProductIdentifier>(x => x.?)
Any help would be greatly appreciated.
in case someone comes here
CompositeId()
.KeyProperty(t => t.Id, c =>
c.Type(typeof(MyUserType)).ColumnName("MemberNumber").ColumnName("GroupId"));

Fluent NHibernate: Foreign Key violation or Null values

Hey guys, I am having some real issues with mapping using fluent nhibernate. I realise there are MANY posts both on this site and many others focusing on specific types of mapping but as of yet, I have not found a solution that solves my issue.
Here is what I have:
namespace MyProject.Models.Entites
{
public class Project
{
public virtual Guid Id {get; set;}
// A load of other properties
public virtual ProjectCatagory Catagory{get;set;}
}
}
and then the map:
namespace MyProject.DataAccess.ClassMappings
{
public class ProjectMap : ClassMap<Project>
{
public ProjectMap()
{
Id(x => x.Id);
Map(x => x.Title);
Map(x => x.Description);
Map(x => x.LastUpdated);
Map(x => x.ImageData).CustomSqlType("image");
HasOne(x => x.Catagory);
}
}
}
So as you can see, I have a project which contains a catagory property. Im not so hot on relational databases but from what I can figure, this is a many-one relationship where many Projects can have one catagory. No, projects cannot fall into more than one category.
So now we have:
namespace MyProject.Models.Entities
{
public class ProjectCatagory
{
public virtual Guid Id { get; set; }
public virtual String Name { get; set; }
}
}
and its map:
public ProjectCatagoryMap()
{
Id(x => x.Id);
Map(x => x.Name);
}
Issue is, well, it doesn't work ! I will do something similar to the following in a unit test:
Project myproject = new Project("Project Description");
// set the other properties
myProject.Catagory = new ProjectCatagory(Guid.New(), "Test Catagory");
repository.Save(myProject);
Now I have tried a number of mapping and database configurations when trying to get this to work. Currently, the Project database table has a column, "Catagory_id" (which i didnt put there, i assume NH added it as a result of the mapping) and I would LIKE it set to not allow nulls. However, when set as such, I get exceptions explaining that I cannot insert null values into the table (even though during a debug, i have checked all the properties on the Project object and they are NOT null).
Alternatively, I can allow the table to accept nulls into that column and it will simply save the Project object and totally disregard the Category property when saving, therefore, when being retrieved, tests to check if the right category has been associated with the project fails.
If i remember correctly, at one point I had the ProjectMap use:
References(x => x.Catagory).Column("Catagory_id").Cascade.All().Not.Nullable();
this changed the exception from "Cannot insert null values" to a foreign key violation.
I suspect the root of all this hassle comes from my lack of understanding of relational database set up as I have other entities in this project that do not have external dependencies which work absolutely fine with NHibernate, ruling out any coding issues I may of caused when creating the repository.
Any help greatly appreciated. Thank you.
The main issue here is a common misunderstand about the "one-to-one" relation in a relational database and the HasOne mapping in Fluent. The terms in the mapping are relational terms. (Fluent tries to "beautify" them a bit which makes it worse IMO. HasOne actually means: one-to-one.)
Take a look at the Fluent wiki:
HasOne is usually reserved for a
special case. Generally, you'd use a
References relationship in most
situations (see: I think you mean a
many-to-one).
The solution is very simple, just exchange HasOne with References (one-to-one to many-to-one in an XML mapping file). You get a foreign key in the database which references the ProjectCatagory.
A real one-to-one relation in a relational database is ideally mapped by a primary key synchronization. When two objects share the same primary key, then you don't waste space for additional foreign keys and it is ensured to be one-to-one.
To synchronize primary key, you need to hook up one's key to the others. However this works, it is not what you need here.
After playing around with all the available options for mapping. I found the answer to be similar to that suggested.
As was suspected, HasOne() was clearly wrong and References(x => x.Catagory) was part of the solution. However, I still received foreign key violation exceptions until:
References(x => x.Catagory).Column("Catagory_id").Cascade.SaveUpdate().Not.Nullable().Not.LazyLoad();
Just thought id update the thread in case someone else stumbles across this with a similar issue as just using References() did not work.
Its seems ProjectCatagory class is parent class of Project Class. So without parent class how can child class exist.
You have to use -
References(x => x.Catagory).Column("Catagory_id").Foreignkey("Id");
here Foreign Key is your ProjectCatagory table ID.