Fluent-NHibernate many-to-many cascade does not populate the link table - c#

OK, no matter how I define these mappings, my many-to-many mapping does not want to work with cascade insert. I have tried various combination of Cascade() with Reverse() and removing all unnecessary properties just to understand if they had anything to do with this not working, but no lock.
It is really Simple stuff: I have a Message (like an email) which is sent from a user (I have called the entity BasicUser) to a number of users (through property To). User and Message in terms of recipients have a many-to-many relationship but FromUser has one-to-many. FromUser works fine and it is updated alright but my problem is with many-to-many. I even removed FromUser and relationship just to check if this was the problem, but did not help.
So here is the table design (Have removed the relationship from FromUser to BasicUser for simplicity)
And here are the mappings:
public class MessageMap : ClassMap<Message>
{
public MessageMap()
{
Id(x => x.Id).Column("MessageId");
Map(x => x.Subject);
Map(x => x.SentAt);
Map(x => x.Body);
References(x => x.From).Column("FromUser");
HasManyToMany(x => x.To).Table("BasicUserMessage").ChildKeyColumn("BasicUserId")
.ParentKeyColumn("MessageId").Cascade().All();
}
}
public class BasicUserMap : ClassMap<BasicUser>
{
public BasicUserMap()
{
Id(x => x.Id).Column("BasicUserId");
Map(x => x.DisplayName);
Map(x => x.Username);
HasManyToMany(x => x.Messages).Table("BasicUserMessage").ChildKeyColumn("MessageId")
.ParentKeyColumn("BasicUserId").Inverse();
}
}
And I call this and it does not work (table BasicUserMessage does not get populated):
(Note Users with Id 1, 2 and 3 do exist - I also tried getting them from database and then add to list still did not work)
ISessionFactory factory = GetSessionFactory();
ISession session = factory.OpenSession();
Message m = new Message()
{
Body = "Please note 2",
Subject = "Secret 2",
From = new BasicUser(){Id = 2},
SentAt = DateTime.Now,
};
m.To.Add(new BasicUser(){Id = 1});
m.To.Add(new BasicUser(){Id=3});
session.SaveOrUpdate(m);
session.Close();

The answer about transactions is correct-by-incidental-occurrence. Also correct-by-incidental occurrence is that this is manifesting itself as such because you are using something like an IDENTITY generator that requires a database trip on save to obtain the identity.
Here is what NHibernate does when you set a save-update cascade (or any cascade which implies that) on a many-to-many association with such:
Save the parent entity. This goes to the database immediately because of the identity strategy. The collection is "modified" (because it's new), so let's look at it's members. This step only occurs if inverse is not set on the parent's mapping of the relationship. Create an entry in the link table for each of them.
But wait, some of these entries are transient. Cascades are set properly, so that's okay - but in order to create the link table entry in the session, we need the id of those children, so let's save them immediately.
Now all relevant entities are persistent, and the session has a pending insert for all of the entries in the link table. Flushing the session will issue the commands and create those entries.
When you wrap your code in a transaction, committing the transaction flushes the session, so that is created when you commit. If you use an identity generator that doesn't require a DB round-trip, then the link table entries and the entities are all inserted at the same time, so you won't see the "disconnect" that you're seeing - if the session is never flushed, nothing is ever inserted, and when it is flushed, everything is inserted. If you have neither of these things, flushing your session explicitly will create the link table entries and all will be well.

You made both references inverse. This means to NH: don't store it from this side, because it is already stored by the other side. If both are inverse, nothing is stored.
Remove Inverse from one of the references.

You need to wrap your code in transaction. Otherwise Nhibernate won't save values in joining table

You need to make the BasicUser objects persistent:
ISessionFactory factory = GetSessionFactory();
ISession session = factory.OpenSession();
Message m = new Message()
{
Body = "Please note 2",
Subject = "Secret 2",
From = new BasicUser(){Id = 2},
SentAt = DateTime.Now,
};
var basicUser1 = new BasicUser(){Id = 1};
session.Save(basicUser1);
m.To.Add(basicUser1);
var basicUser3 = new BasicUser(){Id = 3};
session.Save(basicUser3);
m.To.Add(basicUser3);
session.Save(m);
session.Flush();
This should of course be done in a transaction (as Sly answered) and the session should be wrapped in a using statement.

Related

How do I get an identity value with Entity Framework(v5) before I save the record

I am new to entity framework and I have been searching a while for an answer to this question and I can't find anything that directally addresses this.
Here is the problem. I have a table in Oracle. In this table there are 2 fields(there are more but not important to this question). Card_Transaction_Id and Parent_Card_Transaction_ID. The Parent_Card_Transaction_Id field is constrained by the Card_Transaction_Id field and I am using a Oracle sequence via a trigger to populate the Card_Transaction_Id field.
In my code, I am using Entity Framework(Version 5) to connect using the Code First Approach.
The issue is when I try to create a new record. I need to know what the next sequence value is in order to populate the Parent_Card_Transaction_Id. My mapping for card transactions:
public class CardTransactionMap : EntityTypeConfiguration<CardTransaction>
{
public CardTransactionMap(string schema)
{
ToTable("CARD_TRANSACTION", schema);
// Mappings & Properties
// Primary Key
HasKey(t => t.CardTransactionId);
Property(t => t.CardTransactionId)
.HasColumnName("CARD_TRANSACTION_ID")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(t => t.ParentCardTransactionId)
.HasColumnName("PARENT_CARD_TRANSACTION_ID");
Property(t => t.CardProfileId)
.HasColumnName("CARD_PROFILE_ID");
}
}
The question is - is there any way to get the next sequence number before I save the record?
My current work arround is to use the following method:
public static decimal GetNextCardTransactionSequenceValue()
{
using (var context = new Context(new OracleConnectionFactory().GetConnection()))
{
var sequence = context.Database.SqlQuery<int>("select card_transaction_id from card_transaction").ToList();
return sequence[0];
}
}
Using that method, I get the next value and then just populate my table. This works but I don't like doing it this way. I feel that there must be a better way to do it.
Thanks in advance.
You have to do this by navigation properties.
By fetching the next value from a sequence before actually using it in the same session you create yourself a concurrency issue: another user can increment the index (by an insert) in the time between drawing its next value and assigning it to the child record. Now the child will belong to the other user's record!
If your CardTransaction class has a parent reference like this:
int ParentCardTransaction { get; set; }
[ForeignKey("ParentCardTransaction")]
CardTransaction ParentCardTransaction { get; set; }
you can create a parent and child in one go and call SaveChanges without worrying about setting FK values yourself:
var parent = new CardTransaction { ... };
var child = new CardTransaction { ParentCardTransaction = parent, ... };
SaveChanges();
Now EF wil fetch the new CardTransactionId from the parent and assign it to the FK of the child. So generating and getting the parent Id happens all in one session, so it is guaranteed to be the same value.
Apart from preventing concurrency issues, of course it is much easier anyway to let EF do the heavy lifting of getting and assiging key values.
Create a Stored Procedure or Query that will return you the next Value from the Table here is an Example
SELECT NVL(MAX(card_transaction_id + 1), 0) AS MAX_VAL
FROM card_transaction T;
or Create a Trigger - for OracleDB
Change your table definition to this :
CREATE TABLE t1 (c1 NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY,
c2 VARCHAR2(10));
as per the information in the link i provided in the comment.
after the update ef will automatically query the value for the id that is inserted, there is no need to fill in the id before the insert. ef will generate an insert sql query without id.

Invalid LazyInitializationException on collections

I've run into a truly frustrating problem and spent several hours trying every which way to sort out. When a collection is lazy loaded on some objects it throws a LazyInitializationException stating there is no session or the session is closed. After taking out the code into a clean console app for testing - I'm convinced the session simply cannot be closed! Here is the code:
private static ISessionFactory _sessionFactory;
static void Main(string[] args)
{
_sessionFactory = BuildFactory(#"*<ThisIsMyConnectionString>*");
using (var session = _sessionFactory.OpenSession())
{
var allContacts = session.Query<Contact>().Where(x=>x.EmployeeId.HasValue);
foreach (var contact in allContacts)
{
var allServices = contact.EmployeeServices.ToArray();
}
session.Dispose();
}
}
private static ISessionFactory BuildFactory(string connectionString = null)
{
return Fluently.Configure()
.Database(FluentNHibernate.Cfg.Db.MsSqlConfiguration.MsSql2008.ConnectionString(connectionString))
.Mappings(m =>
{
m.FluentMappings.Conventions.AddFromAssemblyOf<TableNameConvention>();
m.FluentMappings.AddFromAssemblyOf<KioskAdapterConfigMapping>();
})
.BuildConfiguration().BuildSessionFactory();
}
And here is my (fluent) mappings:
public ServiceMapping()
{
Table("tblServices");
Id(x => x.Id, "ServiceId");
Map(x => x.Name);
}
public ContactMapping()
{
Table("tblContacts");
Id(x => x.Id, "ContactId");
Map(x => x.EmployeeId);
HasManyToMany(x => x.EmployeeServices)
.Table("lnkEmployeeService")
.ParentKeyColumn("EmployeeId")
.PropertyRef("EmployeeId")
.ChildKeyColumn("ServiceId")
.NotFound.Ignore();
}
How on earth is the session closed? Some database records do not have any records in the "lnkEmployeeService" table, but all foreign keys are in place and valid. Also the link table does have extra columns and is actually a composite key with other columns, but I don't care about rest of the data.
The problem is hidden in the fact, that the EmployeeServices collection is mapped to the Contact by the not-unique value "EmployeeId".
Other words, let's assume three contacts
ContactId, EmployeeId
1 , 1
2 , 2
3 , 2
Now, we instruct NHibernate be ready, to load 3 collections. They will/should/must be unique, for each of the contacts. So in the ISession, they could be kept by their unique identifier. In case of NOT property-ref mapping they would be like:
CollectionId - Collection
1 - the first collection of all Services related to contact with ID 1
2 - the first collection of all Services related to contact with ID 2
3 - the first collection of all Services related to contact with ID 3
But we do have a problem here, because our unique identifier for our collections is
CollectionId - Collection
1 ...
2 ...
2 - here we go... this (collection) or the previous
must now be removed from a session...
no way how to load/handle it any more
The key, must be unique, that's the point, even if this is the property-ref. On a bit different place of the documentation, we can see 5.1.10. many-to-one:
The property-ref attribute should only be used for mapping legacy data
where a foreign key refers to a unique key of the associated table
other than the primary key.
While this is not explained for <bag> mapping the logic is still the same
I think the problem is that you're calling Dispose on an IDisposable that's wrapped in a using block. The using block is the equivalent of writing
var myDisposable = new SomeIDisposableImplementation();
try { ... }
finally
{
if(myDisposable != null) myDisposable.Dispose();
}
In your code Dispose is called twice. My guess is that's causing the problem but I wasn't able to duplicate the exception in a simple test.

Why become referential constraints inconsistent after updating foreign key?

Sorry for the nebulous title, it's hard to describe this in a single line:
I have 2 entities User and UserAddress, where User has 2 foreign keys DefaultInvoiceAddressId and DefaultDeliveryAddressId and UserAddress has a UserId foreign key.
The user object has navigation properties for the default addresses (DefaultInvoiceAddress and DefaultDeliveryAddress) as well as one for all of his addresses: AllAddresses.
The mapping etc. works, creating and updating users and addresses works too.
What does not work though is setting an existing Address of a User as e.g. DefaultInvoiceAddress. In SQL terms, what I want to happen is UPDATE USER SET DefaultInvoiceAddressId = 5 WHERE Id = 3.
I've tried this the following way:
private void MarkAs(User user, UserAddress address, User.AddressType type) {
if (context.Entry(user).State == EntityState.Detached)
context.Users.Attach(user);
// guess I don't really need this:
if (context.Entry(address).State == EntityState.Detached)
context.UserAddresses.Attach(address);
if (type.HasFlag(User.AddressType.DefaultInvoice)) {
user.DefaultInvoiceAddressId = address.Id;
user.DefaultInvoiceAddress = null;
context.Entry(user).Property(u => u.DefaultInvoiceAddressId).IsModified = true;
}
if (type.HasFlag(User.AddressType.DefaultDelivery)) {
user.DefaultDeliveryAddressId = address.Id;
user.DefaultDeliveryAddress = null;
context.Entry(user).Property(u => u.DefaultDeliveryAddressId).IsModified = true;
}
}
This method is called both when creating new UserAddresses as well as when updating addresses. The create scenario works as expected, however in the update case I receive the following error:
The changes to the database were committed successfully,
but an error occurred while updating the object context.
The ObjectContext might be in an inconsistent state.
Inner exception message: A referential integrity constraint violation occurred:
The property values that define the referential constraints are not consistent between principal and dependent objects in the relationship.
I call the method with a User object I retrive from the database and the DefaultDeliveryAddress it contains, which I load alongside it via eager loading.
var user = mainDb.User.Get(UnitTestData.Users.Martin.Id, User.Include.DefaultAddresses);
var existingAddress = user.DefaultDeliveryAddress;
mainDb.User.Addresses.SetAs(user, existingAddress, User.AddressType.DefaultInvoice))
// the SetAs method verfies input parameters, calls MarkAs and then SaveChanges
In a nutshell, I just want to make the DefaultDeliveryAddress of a user also his DefaultInvoiceAddress, which would be easily accomplished with the above SQL Update command, but I'm missing something with my EF code.
I've already checked that:
Only the Id is set, the navigation property (DefaultInvoiceAddress) is re-set to null
UserAddress.UserId = User.Id (obviously since it is already assigned to the user)
The user object will become Modified (checked with debugger), since one of its properties is being marked as modified
I also tried clearing both default address navigation properties, but that didn't help either
I suspect this problem is due to the User entity having 2 references to UserAddress, and both foreign keys are set to refer to the same address - how can I get EF to work with that?
Update:
Here are the mappings of the User entity:
// from UserMap.cs:
...
Property(t => t.DefaultInvoiceAddressId).HasColumnName("DefaultInvoiceAddressId");
Property(t => t.DefaultDeliveryAddressId).HasColumnName("DefaultDeliveryAddressId");
// Relationships
HasOptional(t => t.DefaultInvoiceAddress)
.WithMany()
.HasForeignKey(t => t.DefaultInvoiceAddressId);
HasOptional(t => t.DefaultDeliveryAddress)
.WithMany()
.HasForeignKey(t => t.DefaultDeliveryAddressId);
HasMany(t => t.AllAddresses)
.WithRequired()
.HasForeignKey(t => t.UserId)
.WillCascadeOnDelete();
UserAddress has no navigation properties back to User; it only contanis HasMaxLength and HasColumnName settings (I exclude them to keep the question somewhat readable).
Update 2
Here's the executed command from Intellitrace:
The command text "update [TestSchema].[User]
set [DefaultInvoiceAddressId] = #0
where ([Id] = #1)
" was executed on connection "Server=(localdb)\..."
Looks fine to me; seems only EF state manager gets confused by the key mappings.
Figured out the problem: apparently it makes quite the difference when to set navigational properties to null, as EF might otherwise interpret that as an intended change / update (at least that is what I suspect).
The following version of the MarkAs method works:
private void MarkAs(User user, UserAddress address, User.AddressType type) {
if (context.Entry(user).State == EntityState.Detached) {
// clear navigation properties before attaching the entity
user.DefaultInvoiceAddress = null;
user.DefaultDeliveryAddress = null;
context.Users.Attach(user);
}
// address doesn't have to be attached
if (type.HasFlag(User.AddressType.DefaultInvoice)) {
// previously I tried to clear the navigation property here
user.DefaultInvoiceAddressId = address.Id;
context.Entry(user).Property(u => u.DefaultInvoiceAddressId).IsModified = true;
}
if (type.HasFlag(User.AddressType.DefaultDelivery)) {
user.DefaultDeliveryAddressId = address.Id;
context.Entry(user).Property(u => u.DefaultDeliveryAddressId).IsModified = true;
}
}
To sum up my findings for future readers:
If you intend to update entities via Foreign Key properties, clear navigation properties. EF doesn't need them to figure out the update statement.
Clear navigation properties before you attach an entity to a context, otherwise EF might interpret that as a change (in my case the foreign key is nullable, if that isn't the case EF might be smart enough to ignore the navigation property change).
I will not accept my own answer right away to give other (more qualified) readers a chance to answer; if no answers are posted in the next 2 days, I'll accept this one.

Entity record is created but lookup field stays empty

I'm trying to create an Incident record with some Service Activity related ones attached.
For this, I leverage the RelatedEntities property as follows:
// (stripped to the essential)
Entity myRecord = new Entity("incident");
foreach(var e in myData.Select(record => record.ToEntityReference()) // (1) below
{
Entity relatedRecord = new Entity("serviceappointment");
// mandatory fields are set here (omitted)
relatedRecord["new_mylookup"] =
new EntityReference(e.LogicalName, e.Id) { Name = e.Name };
myRecord.RelatedEntities.Add(relatedRecord);
}
service.Execute(new CreateRequest(){ Target = myRecord });
(1): myData is a IQueryable<Entity> which picks some records out from a custom entity. Data is 101% correct (I already inspected it).
The outcome of this code is:
The incident is created
The serviceappointment records are created, *but my new_mylookup attribute is empty*. All other fields are perfectly fine and filled with my data.
I see no errors whatsoever, neither during the execution of the code nor in the server trace. As far as the lookup field goes, it's a common lookup (no scripts, no plugins) set as Recommended.
What's wrong with the code ?
NOTE: removing the Name from new EntityReference(e.LogicalName, e.Id) { Name = e.Name }; doesn't change anything: new EntityReference(e.LogicalName, e.Id); still doesn't fill the lookup.
It turns out a co-worker of mine added an onLoad script which inadvertently was blanking out the same field I was setting. My code worked from the start.

Why entity framework isn't implement identity map with unit of work?

I have written test code as bellow:
Entities db = new Entities();
var place = new Place
{
Id = Guid.NewGuid(),
Name = "test",
Address = "address"
};
db.Places.Add(place);
var cachedPlace = db.Places.Where(x => x.Id == place.Id).FirstOrDefault(); \\ null
I expected dbset will return the added entity. But it gives me object only after changes were saved to the real DB.
If you want to access the unsaved query, then you use the Local property of the DbSet.
The reason it doesn't work the way you want is that it must also support autonumbered identities, and that will mean the ID is 0. If you insert multiple records, you would have multiple objects with the same 0 ID. EF won't know what the real ID is until after it's been saved.

Categories

Resources