EF Code First on existing database, mapping one to many - c#

I have a database generated by application which I can't modify (I can add tables, views and such but I can't modify existing tables, add columns to them). I work on a web application which uses BreezeJS to allow the client-side part of the web app query for the data via OData protocol.
Measurement table has following structure:
MeasurementId INT
DeviceId INT FOREIGN KEY REFERENCES Devices (DeviceId)
Name VARCHAR,
PRIMARY KEY (MeasurementId)
What I need is to add nullable ParentId self referencing foreign key and because I can't modify existing tables, I've created new one, Measurement_Parent:
MeasurementId INT FOREIGN KEY REFERENCES Measurements (MeasurementId),
ParentId INT FOREIGN KEY REFERENCES Measurements (MeasurementId),
PRIMARY KEY (MeasurementId)
I have following entity:
public partial class Measurement
{
public Measurement()
{
this.Children = new List<Measurement>();
}
public Int32 MeasurementId { get; set; }
public virtual Measurement Parent { get; set; }
public Int32 DeviceId { get; set; }
public virtual Device Device { get; set; }
public String Name { get; set; }
public virtual ICollection<Measurement> Children { get; set; }
}
Now the tricky part. I've tried many different approaches to get this working but without success. Current EntityTypeConfiguration for my entity looks like this:
// Primary Key
this.HasKey(m => m.MeasurementId);
// Table & Column Mappings
this.Property(t => t.MeasurementId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
// Table & Column Mappings
this.ToTable("Measurement");
this.Property(m => m.MeasurementId);
this.Property(m => m.DeviceId);
this.Property(m => m.Name);
// Relationships
// Each measurement references device performing the measurement.
this.HasRequired(d => d.Device)
.WithMany(m => m.Measurements)
.HasForeignKey(d => d.DeviceId);
// Each measurement can have optional parent.
this.HasOptional(measurement => measurement.Parent)
.WithMany() // .WithMany(measurement => measurement.Children) ??
.Map(m =>
{
m.MapKey("ParentId");
m.ToTable("Measurement_Parent");
});
Unfortunately this gives me weird error while loading my app:
Metadata query failed for: api/EDW/Metadata; The specified table 'Measurement_Parent' was not found in the model. Ensure that the table name has been correctly specified.
I have no idea why is this happening because the table is there. I tried mapping these two tables onto one entity (table splitting), but because the ParentId can be NULL and EF generated INNER JOIN instead of LEFT OUTER JOIN for this mapping, it didn't work because some rows in Measurement table were ommited as they didn't have any corresponding rows in Measurement_Parent.
Basically what I need is to have optional Parent property with reference to parent measurement and list of Children measurements.

What you need is entity splitting - splitting a single entity among two or more tables. This implicitly involves shared primary key - in this case, the shared key in the relationship table will be the child entities' ID. You do this by calling multiple Map methods, each with a call to EntityMappingConfiguration.Properties to define which properties should be included in that mapping fragment and a call to ToTable to set the table name.
modelBuilder.Entity<Measurement>()
.HasKey( ke => ke.MeasurementId )
.Map( emc =>
{
emc.Properties( pe => new { pe.MeasurementId, pe.Name, pe.DeviceId } );
emc.ToTable( "Measurement" );
} )
.Map( emc =>
{
emc.Properties( pe => new { pe.MeasurementId, pe.ParentId } );
// maybe name this MeasurementExtension? This table could
// be used for any properties you wish to add to the Measurement entity
emc.ToTable( "Measurement_Parent" );
// for this example's clarity I've named the PK Child_MeasurementId
// but in real-world use I would name it MeasurementId
emc.Property( pe => pe.MeasurementId ).HasColumnName( "Child_MeasurementId" );
emc.Property( pe => pe.ParentId ).HasColumnName( "Parent_MeasurementId" );
} );
modelBuilder.Entity<Measurement>()
.HasOptional( npe => npe.Parent )
.WithMany( npe => npe.Children )
.HasForeignKey( fke => fke.ParentId );
Here's the result in the DB (note I did not set up a FK/nav prop for Device but you know how to do that):
Ideally, the Parent_MeasurementId field would be not null and the record would be deleted instead of setting that column to null if their is no parent, but that doesn't seem possible with entity splitting. In any case, this does exactly what you're looking for - extending an entity without modifying the initial underlying table.

Related

Can I setup a navigation property between two objects without a unique foreign key?

I'm using Entity Framework Core and have two tables in my database :
Table 1 (Contract)
Columns : ContractNumber, ContractCode, ProductType
Table 2 (ContractRole)
Columns: ContractNumber, ContractCode, ProductType, RoleType, RoleName
So, my database doesn't have a foreign key, instead I use two columns (contractnumber, contractcode) to reference tables.
My goal is to create my entities, so that I can fetch contracts and then for each Contract I can extract a relevant list of ContractRoles. That means using navigation properties.
My code will be something like:
[Table("XXXXX")]
public class Contract
{
public Contract()
{
ContractRoles = new HashSet<ContractRole>();
}
public ICollection<ContractRole> ContractRoles { get; set; }
}
If I had a direct contractId foreign key then I could do:
modelBuilder.Entity<ContractRoles>()
.HasOne(x => x.Contract)
.WithMany(x => x.ContractRoles)
.HasForeignKey(x => x.ContractId);
But I don't! Therefore, I need the reference to be made to two fields: contract number and contract code. Is it possible?
I did make it work by fetching the flat data with a query and then building my proper objects (Contract object with a list of ContractRoles) later:
var result = (from s in _dbContextReadOnly.Contracts
join sa in _dbContextReadOnly.ContractRoles
on new { s.ContractNumber, s.ContractCode } equals new { sa.ContractNumber, sa.ContractCode }
select new FlatContractWithContractRoles
{
ContractNumber = s.ContractNumber,
ContractCode = s.ContractCode,
RoleType = sa.RoleType,
RoleName = sa.RoleName
}).Distinct().ToList();
Please don't advise me to modify the database at the source, it is not a possibility. I just want to know if I can fetch a Contract with a list of ContractRoles using the navigation properties directly.
Thanks :) !
I got it! I could just do :
.HasForeignKey(x => new { x.ContractNumber, x.ContractCode });

EF Fluent API: Discriminator column is NULL for Derived Type

I am working on creating an Entity Framework (v6) Code First model from an existing database. After endless hours of frustration working with the EF Fluent API, I finally have a mapping which (almost) works as I want. Specifically, I have a conceptual model which is hierarchical, and I'm trying to map one particular entity which is split across three different tables. The POCO classes look like this:
// Base Class
public class Parameter
{
// Primary Key for the 'Parameters' table
public int Key { get; set; }
...
}
// Derived Class 1
public class EmbeddedMessageParameter : Parameter
{
// Primary Key for the 'DataTypeEmbeddedMessage' table.
public int DataTypeKey { get; set; }
...
}
// Derived Class 2:
public class EmbeddedMessageCollectionParameter : EmbeddedMessageParameter
{
// Primary Key for the 'DataTypeEmbeddedMessageCollection' table.
public int CollectionDataTypeKey { get; set; }
...
}
The database has the 'Parameters' table, the 'DataTypeEmbeddedMessage' table and the 'DataTypeEmbeddedMessageCollection' table. The 'Parameters' table contains all of the data for the Parameter parent class and contains a discriminator column called 'DataType'. If 'DataType' = 'Embedded Message', then an EmbeddedMessageParameter object is created. If 'DataType' = 'Embedded Message Collection', then an EmbeddedMessageCollectionParameter object is created.
The Fluent API code in my DbContext class is as follows:
// Map the Parameters table.
modelBuilder.Entity<Parameter>().ToTable("Parameters");
modelBuilder.Entity<Parameter>().HasKey(param => param.Key);
modelBuilder.Entity<Parameter>().Property(param => param.Key).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<Parameter>().Property(param => param.Key).HasColumnName("ParameterID");
// Embedded Message Parameters.
modelBuilder.Entity<EmbeddedMessageParameter>()
.Map(m =>
{
m.Properties(p => new
{
p.Key,
...
});
m.ToTable("Parameters").Requires("DataType").HasValue("Embedded Message");
})
.Map(m => m.ToTable("DataTypeEmbeddedMessage"));
modelBuilder.Entity<EmbeddedMessageParameter>().HasKey(p => p.DataTypeKey);
modelBuilder.Entity<EmbeddedMessageParameter>().Property(p => p.DataTypeKey).HasColumnName("AutoKey");
modelBuilder.Entity<EmbeddedMessageParameter>().Property(p => p.DataTypeKey).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
...
// Embedded Message Collection Parameters.
modelBuilder.Entity<EmbeddedMessageCollectionParameter>()
.Map(m =>
{
m.Requires("DataType").HasValue("Embedded Message Collection");
m.ToTable("DataTypeEmbeddedMessageCollection");
});
modelBuilder.Entity<EmbeddedMessageCollectionParameter>().HasKey(p => p.CollectionDataTypeKey);
modelBuilder.Entity<EmbeddedMessageCollectionParameter>().Property(p => p.CollectionDataTypeKey).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<EmbeddedMessageCollectionParameter>().Property(p => p.CollectionDataTypeKey).HasColumnName("AutoKey");
...
This mapping works well for the most part. I can read EmbeddedMessageParameter and EmbeddedMessageCollectionParameter objects from the database and create and update them. However, there is one problem. If I create an EmbeddedMessageParameter object and save it to the database, the 'DataType' discriminator column in the 'Parameters' table is correctly populated with the value of 'Embedded Message'. However, when I create an EmbeddedMessageCollectionParameter object and save it to the database, the 'DataType' discriminator column is NULL. I am expecting it to have 'Embedded Message Collection' in this case.
Does anyone have any ideas how to fix this?

LinQ to SQL ignoring != null on virtual properties

I've been using LinQ to SQL with EF for a while now and have just stumbled on some strange behaviour that hopefully someone may be able to explain for me.
I'm performing a LinQ query on a database context that has a POCO entity with virtual properties for related entities. I'm using a where clause to eliminate instances where that entity is null. I'm using Lazy Loading.
return this.runtimeContext.FatalExceptionLogs.Where(l => l.RuntimeToDesignJuicerRelationship != null);
What I'm finding is that when my query is evaluated LinQ to SQL seems to entirely ignore my condition that the virtual property is null as if I'd never included this check at all. Instead it returns all records in my dbset named FatalExceptionLogs.
Now I have a simple workaround for this which is to first load the data into memory using .ToList() then
This looks like so:
return this.runtimeContext.FatalExceptionLogs.ToList().Where(l => l.RuntimeToDesignJuicerRelationship != null);
Now the check is performed in memory and all instances where the virtual property is null are returned (because there is no corresponding record as the id which is used for the join is nullable) and we're all good.
I have also considered:
Checking if the id that is joined on is null but unfortunately I can't garauntee that the referential integrity of the table has been maintained as there is no foreign key applied urgh!.
Checking if there are any records in the other table with the matching id, but that could be rather inefficient.
So I have a way of working around this but I'd really like to understand why LinQ to Sql is doing this and what other options there are, can anyone help?
The full code if it helps is below though I've cut it down for this example:
The query:
return this.runtimeContext.FatalExceptionLogs.ToList().Where(l => l.RuntimeToDesignJuicerRelationship != null);
The entity:
public class FatalExceptionLog
{
public int Id { get; set; }
public int? RuntimeJuicerId { get; set; }
public virtual RuntimeToDesignJuicerRelationship RuntimeToDesignJuicerRelationship { get; set; }
}
The mapping:
public class FatalExceptionLogMap : EntityTypeConfiguration<FatalExceptionLog>
{
public FatalExceptionLogMap()
{
// Primary Key
this.HasKey(t => t.Id);
// Table & Column Mappings
this.ToTable("FatalExceptionLogging");
this.Property(t => t.RuntimeJuicerId).HasColumnName("JuicerLiveID");
this.HasRequired(t => t.RuntimeToDesignJuicerRelationship)
.WithMany(t => t.FatalExceptionLogs)
.HasForeignKey(t => t.RuntimeJuicerId);
}
}
Why NOT just do the normal joining?
return this.runtimeContext.FatalExceptionLogs.Where(
l => runtimeContext.RuntimeJuicers.Any(
y => y.RuntimeJuicerId == l.RuntimeJuicerId
)
);

A relationship is in the Deleted state

When I am trying to clear a collection (calling .Clear) I get the following exception:
An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. See the InnerException for details.
The inner exception is:
A relationship from the 'User_Availability' AssociationSet is in the 'Deleted' state. Given multiplicity constraints, a corresponding 'User_Availability_Target' must also in the 'Deleted' state.
User looks like this:
....
ICollection<Availability> Availability { get; set; }
Availability looks like this:
int ID { get; set; }
User User { get; set; }
DateTime Start { get; set;
DateTime End { get; set; }
Configuration is as follows:
HasMany(x => x.Availability).WithRequired(x => x.User);
HasRequired(x => x.User).WithMany(x => x.Availability);
The code causing the problem is:
user.Availability.Clear();
I've looked at other alternatives such as using the DbSet to remove items, but I don't feel my code will be as clean. Is there a way to accomplish this by clearing the collection?
The only way that I'm aware of to make it work is defining the relationship as an identifying relationship. It would required to introduce the foreign key from Availability to User as a foreign key into your model...
public int ID { get; set; }
public int UserID { get; set; }
public User User { get; set; }
...and make it part of the primary key:
modelBuilder.Entity<Availability>()
.HasKey(a => new { a.ID, a.UserID });
You can extend your mapping to include this foreign key (just to be explicit, it isn't required because EF will recognize it by convention):
modelBuilder.Entity<Availability>()
.HasRequired(a => a.User)
.WithMany(u => u.Availability)
.HasForeignKey(a => a.UserID);
(BTW: You need to configure the relationship only from one side. It is not required to have both these mappings in your question.)
Now you can clear the collection with user.Availability.Clear(); and the Availability entities will be deleted from the database.
There is one trick. You can delete entities without using special DbSet:
(this.dataContext as IObjectContextAdapter).ObjectContext.DeleteObject(entity);
Execute this for each item in Availability collection before clearing it. You don't need 'identifying relationships' for this way.
In case someone has the same problem using SQLite:
Unfortunately the accepted answer does not work with SQLite because SQLite does not support auto increment for composite keys.
You can also override the SaveChanges() Method in the Database context to delete the children:
//// Long Version
//var localChilds = this.SubCategories.Local.ToList();
//var deletedChilds = localChilds.Where(w => w.Category == null).ToList();
//foreach(var child in deletedChilds) {
// this.SubCategories.Remove(child);
//}
// Short in LINQ
this.SubCategories.Local
.Where(w => w.Category == null).ToList()
.ForEach(fe => this.SubCategories.Remove(fe));
#endregion
See this great Blogpost as my source (Unfortunately written in german).

Mapping one entity to multiple tables

I have a question related to the Fluent NHibernate. I can not describe the schema mapping one entity to multiple tables. There is the following structure of the database:
Create table CeTypes (Id int not null PRIMARY KEY, Name nvarchar(100) not null)
Create table CeValues (Id int not null PRIMARY KEY, Name nvarchar(100) not null)
Create table Ces (Id int not null PRIMARY KEY, CeType_id int not null FOREIGN KEY REFERENCES CeTypes(Id), CeValue_id int not null FOREIGN KEY REFERENCES CeTypes(Id))
there is the following entity:
public class Ce
{
public virtual int Id { get; set; }
public virtual string Type { get; set; }
public virtual string Value { get; set; }
}
CeType, CeValue entities in the domain and there is no. I do not know how to describe the mapping Ce entity.
Tried to describe:
public class CeMap : ClassMap<Ce>
{
public CeMap()
{
Table("Ces");
Id(c => c.Id);
Join("CeTypes", m => m.Map(ce => ce.Type).Column("Name"));
Join("CeValues", m => m.Map(ce => ce.Value).Column("Name"));
}
}
But with such a scheme CeType, CeValue tables should have a field Ce_id. How can I describe scheme mapping under the current structure of the database?
I tried doing the same thing when I first started using nHibernate and couldn't find a way to do it. I actually don't believe that you can map multiple tables to a single object. Usually you would have one entity per table. Each entity will be mapped to their table and would have references/hasmany links between them.
You'll probably find that having one entity per table is better in the long run as well because it allows for simpler mapping to the database.

Categories