NHibernate, how to disable additional selects for orphaned references - c#

I'm using NHibernate for data access and have such situation
public class B
{
public virtual long Id { get; set;}
...
}
public class B
{
public virtual long Id { get; set;}
public virtual A AReference { get; set; }
...
}
and mapping
public class BMapping : ClassMapping<B>
{
...
ManyToOne(x => x.AReference, mapper =>
{
mapper.ForeignKey("none");
mapper.Insert(false);
mapper.Update(false);
mapper.Column("a_id");
mapper.Fetch(FetchKind.Join);
mapper.NotFound(NotFoundMode.Ignore);
});
...
}
My problem is that there is no foreign key constraint between tableA and tableB so there are cases that tableB references nonexistent items. (I know that's bad but can not do anything about that).
Setting
mapper.NotFound(NotFoundMode.Ignore);
allows me to retrieve data without "No row with the given identifier exists" exceptions, but seems NHibernate is trying to load that orphaned items using separate selects like
SELECT ... FROM tableA where tableA.ID = ?
So, the question is, can I disable that additional queries and how.
Thanks in advance.

As discussed here NHibernate Prevent Lazy Loading of unmatched reference and here Lazy loading for NHibernate with Ignore.NotFound the point is:
When you specify the .NotFound().Ignore() this forces the entity to be eagerly loaded and cannot be overriden with the .LazyLoad(). NHibernate does this because it has to be sure that relationship exists or doesn't exist since you are not relying on the database to enforce this.
Check this for
NHibernate force not-found ignore to not execute an extra select
and mostly the link provided there
How to use 0 instead of null for foreign keys
to get some understanding how to create custom PocoEntityTuplizer to be used that.
.. during the build process of the Person entity will collection object[] values contain also CountryProxy. Let's say that missing in DB is one with Id == 0 (use your own logic there as needed). This proxy will be replaced with null so no SELECT will be executed...

Related

Flatten composite entities

Is it possible to flatten a two-table relationships into a single entity in Entity Framework?
Specifically, (simplified for example) given the following two tables that define a 1-1 relationship
create table Foo
(
Id int not null identity (1, 1)
constraint PK_Foo_Id primary key (Id),
Name nvarchar(64) not null,
BarId int not null
constraint FK_Bar_Foo foreign key (BarId) references Bar (Id)
)
create table Bar
(
Id int not null identity (1, 1)
constraint PK_Bar_Id primary key (Id),
Value nvarchar(max) not null
)
I can easily map this to entities like this
public class Foo
{
public int Id { get; set;}
public string Name { get; set;}
public Bar Bar { get; set;}
}
public class Bar
{
public int Id { get; set;}
public string Value { get; set;}
}
But what I would like to map to a single flattened entity
public class FlatFoo
{
public int Id { get; set;}
public string Name { get; set;}
public string Value { get; set;}
}
Notice that only one field from table Bar is mapped to FlatFoo
Notes
The actual tables are larger.
Since the text value in Bar can get large it would fill index pages quickly, so there are two tables for quicker index searches against Foo.Id and Foo.Name.
I have looked into Split Entities, but it required both tables have the same primary key.
I have looked at Complex Types but it works in the opposite manner taking a flat table and splitting into composite entities.
I am looking to use the Fluent API to perform the mapping.
Can you provide any help in flattening the mapping between two tables and a single entity?
Update
Yes, views will work to get a flat entity, but then I am not mapping from tables to entity. Likewise, from the other side, I know it is possible to map to non-public composition and expose the property that way. But, I am more interested in learning if EF fluent API is flexible enough to handle the mapping directly than I am in solving a particular issue.
Unfortunately, there is considerable push-back here (at work) to any suggestion of adding anything other than tables to a database (something as basic as views included). It is typically pointed out that doing so adds additional point of maintenance, increases training for support, adds complexity for basic CRUD and other excuses for not learning the tools available. It is silly at best, but it is something I have to deal with. :(
So, as a point of learning for me, is it possible to do this seemingly basic task of directly mapping fields from two arbitrary tables into one entity using EF, fluent API preferred?
Entity Framework doesn't provide a way to map one entity to two tables and then cherry pick from the columns in the way you describe unless the tables share a common key. So as mentioned in the comments, the simplest solution is to create a View and map the entity to that.
public class FlatFooMap : EntityTypeConfiguration<FlatFoo>
{
public FlatFooMap ()
{
ToTable("vwFlatFoo");
HasKey(t => t.Id);
}
}

Entity Framework 6 Lazy Loading returns null

I have a class with the following properties
public class Booking
{
public long BookingId {get;set;}
public string RoomNumber {get;set;}
[ForeignKey("BookingCustomer")]
public long? BookingCustomerId {get;set;}
public virtual Customer BookingCustomer {get;set;}
}
public class Customer
{
public long CustomerId {get;set;}
public string FirstName {get;set;}
}
if in a method I reference properties of the customer class am getting object null reference exception while BookingCustomerId is populated.i.e.,
hotel.BookingCustomerId=2
For instance,
string customerFirstName = hotel.BookingCustomer.FirstName;
if I peek at the hotel.BookingCustomer i get null
How do I go about this Lazy Loading?
If the related entity is coming back as null this means the relationship as understood by entity framework can't find any related entities.
It appears you are using data annotations to flag properties with properties such as foreign keys. You may also need to flag primary keys with the [key] attribute.
You will also need to add a related booking entity to your customer data.
Alternatively you can you the fluent api to do the following in your context.
// Configure the primary key for the Booking
modelBuilder.Entity<Booking>()
.HasKey(t => t.BookingID);
modelBuilder.Entity<Booking>()
.HasRequired(t => t.customer)
.WithRequiredPrincipal(t => t.booking);
More on the fluent a picture here:
https://msdn.microsoft.com/en-us/data/jj591620.aspx#RequiredToRequired
Lazy loading implies that the related objects are retreived when the getter of that object is used for the first time.
At just that time a query to the database is executed to retreive for that object ,for example Hotel.BookingCustomer.
Try to see if the query is indeed executed e.g. with Sql Server profiler.
Examine the query to makes sure everything is correct
If you can't see the query triggered, try to it without the virtual keyword (eager loading) and see if it's working then.

Entity framework map entity to multiple tables in cascade

I'm facing a problem using EF.
I have the following situation:
From this database schema i'd like to generate the following entity by merge tables data:
// Purchases
public class Purchase
{
//Fields related to Purchases
public int IdPurchase { get; set; }
public string CodPurchase { get; set; }
public int IdCustomer { get; set; }
public decimal Total { get; set; }
//Fields related to Customers table
public string CodCustomer { get; protected set; }
public string CompanyTitle { get; protected set; }
public string CodType { get; protected set; }
//Fields related to CustomersType table
public string DescrType { get; protected set; }
}
As you can see, in my context i don't want 3 separated entities for each table. I want a single one with the fields related to all tables. All fields of Customers and CustomersType tables must be readonly (so i've set the relative setters protected) and the others must be editables so that EF can track changes. In particular, i'd like to have the ability to change the "IdCustomer" field and let EF to automatically update "CodCustomer", "CompanyTitle", "DescrType"....and so on by doing cross table select.
To do that, i wrote this configuration class:
internal class PurchaseConfiguration : EntityTypeConfiguration<Purchase>
{
public PurchaseConfiguration(string schema = "dbo")
{
ToTable(schema + ".Purchases");
HasKey(x => x.IdPurchase);
Property(x => x.IdPurchase).HasColumnName("IdPurchase").IsRequired().HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(x => x.IdCustomer).HasColumnName("IdCustomer").IsRequired();
Property(x => x.Total).HasColumnName("Total").IsRequired().HasPrecision(19, 4);
Map(mc =>
{
mc.Properties(n => new
{
n.CodCustomer,
n.CompanyTitle,
n.CodType
});
mc.ToTable("Customers");
});
Map(mc =>
{
mc.Properties(n => new
{
n.DescrType,
});
mc.ToTable("CustomersType");
});
}
}
I've tested it but it doesn't work as expected. I always get this message:
Properties for type 'Purchase' can only be mapped once. The non-key
property 'CodCustomer' is mapped more than once. Ensure the
Properties method specifies each non-key property only once.
Maybe there's something wrong or i forget something (for example the join fields of Map<> that i don't know where to specify them).
How can i accomplish in the correct way this task?
I don't want to have "Customers" and "CustomersType" DBSets in my context.
Is there a way to avoid it?
I even thought to add into the "IdCustomer" setter a custom query to update manually "Customers" and "CustomersType" related fields, but i don't want to do that for 2 reasons:
I don't have any DbConnection avaiable into the "Purchases" class, so i can't create a DbCommand to read data from DB.
I want entity class to be persistent-ignorant
EF seems to be a powerfull tool that can do these sort of things and i don't want to reinvent the wheel by writing custom procedures.
I've uploaded the example C# source and the tables CREATE scripts (MS SQLServer) here.
All entities are autogenerated by the "EF reverse POCO generator" T4 template (the T4 template is disabled, to activate it set CustomTool = TextTemplatingFileGenerator).
Do not forget to update the ConnectionString in the app.config.
Thanks in advance.
Not the right mapping
I'm afraid the bad news is that this mapping is not possible with this table structure. What you're trying to achieve here is known as entity splitting. However, entity splitting requires 1:1 associations, because sets of records in the involved tables represent one entity. With this mapping, you can't have a Customer belonging to more than one Purchase. That would mean that you could modify multiple Purchase entities by modifying a Customer property of only one of them.
Maybe the news isn't that bad, because I think you actually want to have 1-n associations. But then you can't have these "flattened" properties in Purchase.
As an alternative you could create delegated properties like so:
public string CodCustomer
{
get { return this.Customer.CodCustomer; }
set { this.Customer.CodCustomer = value; }
}
You'd have to Include() Customers and CustomersTypes when you fetch Purchases.
Another alternative is to use a tool like AutoMapper to map Purchase to a DTO type having the flattened properties.
But what does the exception tell me?
You map the Purchase entity to the Purchases table. But you don't specify which properties you want to map to this table. So EF assumes that all properties should be mapped to it. So that's the first (implicit) mapping of CodCustomer. The second one is the one in the mc.ToTable statement. (EF only reports the first problem.)
To fix this, you should add a mapping statement for the left-over Purchase properties:
Map(mc =>
{
mc.Properties(n => new
{
n.IdPurchase,
n.CodPurchase,
n.IdCustomer,
n.Total,
});
mc.ToTable("Purchases");
});
By the way, you should also remove the mapping configuration classes of Customer and CustomersType, they're redundant.
But, as said, the database schema doesn't match the required structure. If you try to save a Purchase you will get a foreign key constraint exception. This is because EF expects the following table structure:
Where the columns IdPurchase in Customer and CustomersType are both primary key and foreign key to Purchase. I don't think this is what you had in mind when designing the database.

How to work with true self referencing entities in code first EF5?

There are a few questions out there on this topic, but my question is very specific to true self referencing. All the examples for other questions are circular references and that doesn't help me in this case.
Lets say I have this model:
public class User
{
[Key]
public int Id { get; set; }
...
public int CreatedByUserId { get; set; }
}
and this map:
public class UserMap : EntityTypeConfiguration<User>
{
public UserMap()
{
this.HasRequired(a => a.CreatedByUser)
.WithMany()
.HasForeignKey(u => u.CreatedByUserId);
}
}
After migrations generates a database with this code I can manually add a User in SQL Management Studio with Id = 1, and CreatedByUserId = 1 so that tells me that self references like this can work.
However when using EF to create a user, I run into a "unable to determine a valid ordering for dependent operations" issue. There are a lot of questions on the matter that involve a new entity that refers another new entity that has a foreign key on the first entity, which is a circular reference. The solution in those cases is either save one of entities first or to have a nullable id on the circular entity foreign key. I can not do either of those because the first would be impossible and the second is a external constraint that I cannot have nullable ids.
So, seeing how I can achieve this by adding a entry manually I can assume it's a limitation of EF5. What are the work arounds?
You can still satisfy your interface and do the save first then set method by adding another property to act as a nullable backer for CreatedByUserId:
public class User : ICreatable
{
[Key]
public int Id { get; set; }
...
public int CreatedByUserId
{
get
{
if (!_CreatedByUserId.HasValue)
//throw new exception, something went wrong.
return _CreatedByUserId;
}
set
{
_CreatedByUserId = value;
}
}
int? _CreatedByUserId { get; set; }
}
You may want to rethink the possibility that a user can create him or herself...
However if you really want to do this then there is a solution. Your main problem is the fact that your column is an IDENTITY column which means that EF doesn't specify the Id, SQL server is giving each row an auto-incrementing Id. Any value you set as the Id is ignored. You don't necessarily know when executing the INSERT what the next Id is going to be so you can't create a reference to a row that doesn't exist yet.
Change your mapping code to something like the following:
this.Property(x => x.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
this.HasRequired(x => x.CreatedByUser)
.WithMany();
You don't need to specify the foreign key if the name pattern matches (eg. CreatedByUser and CreatedByUserId).
Now when you insert a User you can specify the Id and the CreatedById. Although note that you must now always specify the Id to insert a new User. This is common practice if you are using GUIDs as Ids because you can just generate a new GUID without having to first query for the next "available" Id before creating a new object.

Cascadable one-to-one, required:required relationship with EF

I have a Video class and a MediaContent class that are linked by a 1-1, required:required relationship: each Video must have exactly 1 associated MediaContent. Deleting a MediaContent object must result in the deletion of the associated Video object.
Using the fluent API, the relationship can be modeled as follows:
modelBuilder.Entity<Video.Video>()
.HasRequired(v => v.MediaContent).WithRequiredPrincipal(mc => mc.Video)
.WillCascadeOnDelete(true);
When adding a migration to reflect this change in the database, this is how the relationship gets transcribed in terms of foreign keys:
AddForeignKey("MediaContents", "MediaContentId", "Videos", "VideoId", cascadeDelete: true);
Updating the database, I get the following error:
Cascading foreign key 'FK_MediaContents_Videos_MediaContentId' cannot be created where the referencing column 'MediaContents.MediaContentId' is an identity column.
Dropping the WillCascadeOnDelete(true) property removes the error, but I'm not sure I understand why. Shouldn't the error appear whether or not cascading is turned on? The way I understand the problem, the error comes from the fact that the generation of VideoId and MediaContentId is handled by auto-increment (or by whatever the id generation strategy is), potentially contradicting the foreign key constraint. But I can't see what this has to do with delete-cascading...
What am I missing? More generally, how would you go about modeling a cascadable one-to-one, required:required relationship with EF?
I avoid the modelBuilder cruft approach and use simple POCOs and attributes generally - which you can use to accomplish your goals like so:
public class Video
{
public int Id { get; set; }
// Adding this doesn't change the db/schema, but it is enforced in code if
// you try to add a Video without a MediaContent.
[Required]
public MediaContent MediaContent { get; set; }
}
public class MediaContent
{
[ForeignKey("Video")]
public int Id { get; set; }
public Video Video { get; set;}
}

Categories