I have an object called "Customer" which will be used in the other tables as foreign keys.
The problem is that I want to know if a "Customer" can be deleted (ie, it is not being referenced in any other tables).
Is this possible with Nhibernate?
What you are asking is to find the existence of the Customer PK value in the referenced tables FK column.
There are many ways you can go about this:
as kgiannakakis noted, try to do the delete and if an exception is thrown rollback. Effective but ugly and not useful. This also requires that you have set a CASCADE="RESTRICT" in your database. This solution has the drawback that you have to try to delete the object to find out that you can't
Map the entities that reference Customer as collections and then for each collection if their Count > 0 then do not allow the delete. This is good because this is safe against schema changes as long as the mapping is complete. It is also a bad solution because additional selects will have to be made.
Have a method that performs a query like bool IsReferenced(Customer cust). Good because you can have a single query which you will use when you want. Not so good because it may be susceptible to errors due to schema and/or domain changes (depending on the type of query you will do: sql/hql/criteria).
A computed property on the class it self with a mapping element like <property name="IsReferenced" type="long" formula="sql-query that sums the Customer id usage in the referenced tables" />. Good because its a fast solution (at least as fast as your DB is), no additional queries. Not so good because it is susceptible to schema changes so when you change your DB you mustn't forget to update this query.
crazy solution: create a schema bound view that makes the calculation. Make the query on it when you want. Good because its schema-bound and is less susceptible to schema changes, good because the query is quick, not-so-good because you still have to do an additional query (or you map this view's result on solution 4.)
2,3,4 are also good because you can also project this behavior to your UI (don't allow the delete)
Personally i would go for 4,3,5 with that preference
I want to know if a "Customer" can be deleted (ie, it is not being referenced in any other tables).
It is not really the database responsibility to determine if the Customer can be deleted. It is rather part of your business logic.
You are asking to check the referential integrity on the database.
It is ok in non OOP world.
But when dealing with objects (like you do) you better add the logic to your objects (objects have state and behavior; DB - only the state).
So, I would add a method to the Customer class to determine if it can be deleted or not. This way you can properly (unit) test the functionality.
For example, let's say we have a rule Customer can only be deleted if he has no orders and has not participated in forum.
Then you will have Customer object similar to this (simplest possible case):
public class Customer
{
public virtual ISet<Order> Orders { get; protected set; }
public virtual ISet<ForumPost> ForumPosts { get; protected set; }
public virtual bool CanBedeleted
{
get
{
return Orders.Count == 0 && ForumPosts.Count == 0
}
}
}
This is very clean and simple design that is easy to use, test and does not heavily relies on NHibernate or underlying database.
You can use it like this:
if (myCustomer.CanBeDeleted)
session.Delete(mycustomer)
In addition to that you can fine-tune NHibernate to delete related orders and other associations if required.
The note: of course the example above is just simplest possible illustrative solution. You might want to make such a rule part of the validation that should be enforced when deleting the object.
Thinking in entities and relations instead of tables and foreign keys, there are these different situations:
Customer has a one-to-many relation which builds a part of the customer, for instance his phone numbers. They should also be deleted by means of cascading.
Customer has a one-to-many or many-to-many relation which is not part of the customer, but they are known/reachable by the customer.
Some other entity has a relation to the Customer. It could also be an any-type (which is not a foreign key in the database). For instance orders of the customer. The orders are not known by the customer. This is the hardest case.
As far as I know, there is no direct solution from NHibernate. There is the meta-data API, which allows you to explore the mapping definitions at runtime. IMHO, this is the wrong way to do it.
In my opinion, it is the responsibility of the business logic to validate if an entity can be deleted or not. (Even if there are foreign keys and constraints which ensures integrity of the database, it is still business logic).
We implemented a service which is called before deletion of an entity. Other parts of the software register for certain types. They can veto against the deletion (eg. by throwing an exception).
For instance, the order system registers for deletion of customers. If a customer should be deleted, the order system searches for orders by this customer and throws if it found one.
It's not possible directly. Presumably your domain model includes Customer's related objects, such as Addresses, Orders, etc. You should use the specification pattern for this.
public class CustomerCanBeDeleted
{
public bool IsSatisfiedBy(Customer customer)
{
// Check that related objects are null and related collections are empty
// Plus any business logic that determines if a Customer can be deleted
}
}
Edited to add:
Perhaps the most straightforward method would be to create a stored procedure that performs this check and call it before deleting. You can access an IDbCommand from NHibernate (ISession.Connection.CreateCommand()) so that the call is database agnostic.
See also the responses to this question.
It might be worth looking at the cascade property, in particular all-delete-orphan in your hbm.xml files and this may take care of it for you.
See here, 16.3 - Cascading Lifecycle
A naive solution will be to use a transaction. Start a transaction and delete the object. An exception will inform you that the object can't be deleted. In any case, do a roll-back.
Map the entities that reference Customer as collections. Name each collection in your Customer class with a particular suffix.For example if your Customer entity has some Orders, name the Orders collection as below:
public virtual ISet<Order> Orders_NHBSet { get; set; } // add "_NHBSet" at the end
Now by using Reflection you can get all properties of Customer at run time and get those properties that their names ends with your defined suffix( In this case "_NHBSet" ) Then check each collection if they contain any element and if so avoid deleting customer.
public static void DeleteCustomer(Customer customer)
{
using (var session = sessions.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
var listOfProperties =typeof(Customer).GetProperties();
foreach (var classProperty in listOfProperties )
{
if (classProperty.Name.EndsWith("_NHBSet"))
{
PropertyInfo myPropInfo = typeof(Customer).GetProperty(classProperty.Name);
dynamic Collection = myPropInfo.GetValue(customer, null);
if (Enumerable.FirstOrDefault(Collection) !=null)// Check if collection contains any element
{
MessageBox.Show("Customer Cannot be deleted");
return;
}
}
}
session.Delete(customer);
transaction.Commit();
}
}
}
The Advantage of this approach is that you don't have to change your code later if you add new collections to your customer class.And you don't need change your sql query as Jaguar suggested.
The only thing you must care about is to add the particular suffix to your newly added collections.
Related
I am working on a pretty straight forward C# application that uses LINQ to SQL for database access. The application is a non-web (i.e. thick client) application.
The problem that I have recently run into is with the default association name that LINQ to SQL is creating for fields that are foreign keys to another table. More specifically, I have provided an example below:
Example of Problem
The majority of my combo boxes are filled using values from a reference data table (i.e. RefData) that stores a type, description, and a few other fields. When the form initially loads, it fills the combo boxes with values based on a query by type. For example, I have a form that allows the user to add customers. On this form, there is a combo box for state. The stateComboBox is filled by running a query against the RefData table where type = stateType. Then, when the user saves the customer with a selected state the id of the RefData column for the selected state is stored in the state column of the customer table. All of this works as expected. However, if my customer table has more than one column that is a foreign key to the RefData table it quickly becomes very confusing because the association name(s) created by LINQ are Customer.RefData, Customer.RefData1, Customer.RefData2, etc... It would be much easier if I could override the name of the association so that accessing the reference data would be more like Customer.State, Customer.Country, Customer.Type, etc...
I have looked into changing this information in the DBML that is generated by VS but, my database schema is still very immature and constantly requires changes. Right now, I have been deleting the DBML every day or two to regenerate the LINQ to SQL files after making changes to the database. Is there an easy way to create these associations with meaningful names that will not be lost while I frequently re-create the DBML?
I am not sure LINQ to SQL is the best method of accessing data, period, but I find it even more problematic in your case.
Your real issue is you have the concept of your domain objects fairly static (you know what the program needs to use to get work done), but you are not sure how you are persisting the data, as your schema is in flux. This is not a good scenario for automagic updates.
If it were me, I would code the domain models so they do not change except when you desire change. I would then determine how to link to the persistent schema (database in this case). If you like a bit more automagic, then I would consider Entity Framework, as you can use code first and map to the schema as it changes.
If you find this still does not help, because your database schema changes are incompatible with the domain models, you need to get away from coding and go into a deeper planning mode. Otherwise, you are going to continue to beat your head against the proverbial wall of change.
Create a partial class definition for your Customer table and add more meaningful getter properties for the LINQ to SQL generated member names:
public partial class Customer
{
public string Name { get; set; }
[JsonIgnore]
public RefData State => this.RefData;
[JsonIgnore]
public RefData Country => this.RefData1;
}
I blogged about this here
Disclaimer: I'm outlining simplified picture to emphasize main point of my question.
The application I'm working on maintains a set of resources. We have a corresponding table and mapped entity in NHibernate. Each resource identified by integer id. Also we have user table and corresponding entity to maintain user profiles.
We need to log user accesses to the application and retrieve access history. For repository class we have to introduce 2 methods:
IEnumerable GetUserLog(User user) to retrieve user access history order by date in descending order and
void WriteLogEntry(User user, Resource resource) to write new entry
I have tried to simply define LogEntry entity as following:
public class LogEntry
{
public virtual User User {get; set;}
public virtual Resource Resource {get; set;}
public virtual DateTime Date {get; set;}
}
and map it using Fluent NHibernate as usually. To retrieve/update log entries we can simply use
Session.Query<LogEntry>().Where(entry => entry.User = currentUser).OrderByDesc(entry => entry.Date)
Session.Save(new LogEntry() {
User = currentUser,
Resource = resource,
Date = DateTime.Now
})
This is the most convenient way to deal with this issue for us.
Problem
The problem is that NHibernate requires id mapping. We have to use composite id here and the only option is to map User, Resource and Date columns because only this combination provides uniqueness. Also in case of composite id we have to override Equals and GetHashCode and all this seems to be overkill for such a simple task. Another problem that lazy loading cannot be used for id fields and it's too much as well. We do not want to load all related Resource entities in advance.
Another possible solution is to define plain class, not entity and then use SetResultTransformer(Transformers.AliasToBean()) to retrieve results. In that case we have to construct queries manually, retrieve related entities manually and this way it's not better in general then dealing with raw connection.
I would like to ask expert opinion because I'm confident people around had similar experience and can help. Thanks in advance.
P.S. This is ASP.NET MVC application using NHibernate 3 (+ Fluent). Log information will be used to display last 5-10 resources user accessed.
have you considered introducing an Id field for LogEntry table as well?
many DBAs will recommend it and it seems like the easiest solution.
I am using NHibernate to map a class to a database table. The Part table has an ID column (primary key) and a ParentPart column (along with a few others).
class Part
{
public virtual long ID{ get; set; }
public virtual Part ParentPart { get; set; }
}
The ParentPart is normally another valid part in the part table but I have two special cases. I have a case where the ParentPart column can be 0 (zero) and another case where it can be -1. Neither of these cases currently represent another valid Part object. I was thinking I could make 2 subclasses of Part (ZeroPart and NegativeOnePart) that would never persist. I want the zero and -1 values to be entered in the column but not persist the entire ZeroPart or NegativeOnePart objects. I am unsure how to map this (I'm using hbm files) or if this even the correct approach.
How can I map this so that normal valid parts are persisted but I can also handle the special cases?
As an aside: My current hbm file has the Part.ID's unsaved value as zero but I think I can just change this in the mapping to something different and default it in the class.
If I understand you correctly, you have a tree structure and you want to assign dummy-objects (with ID = 0 or ID = -1) that shall not be persisted to the database when persisting the objects. The ParentPart column shall contain ID = 0 or -1, depending on some criteria.
Based on this information I assume that you do not have foreign-keys in your table, so that saving an object with a ParentPart = 0 or -1 will not result in a violation of referential integrity. Under normal circumstances I would suggest using a foreign key and allowing NULL in the ParentPart column.
However, since you want two different values (0 and -1) as an indicator that no parent exists, I believe that your best option is probably just creating the two entries in the database (both with ParentPart = NULL).
If you strictly do not want to add those entries, you can have a look at intereceptors and events.
I don't think you can solve this within your mapping files. The NHibernate mapping feature is not designed to handle such specific logic, nor should it be able to do so.
(Having that kind of logic in the database may not be very desirable, but it sounds like this is not a new project, so we don't need to argue about it here.)
Will there be Part values in your "parts" table with the specified IDs, to maintain referential integrity?
If so, then I would suggest creating two special Part instances, each representing the special cases your model implies. You would need to fetch these instances from the database when you want to assign them, but this can be made simple with some helper methods on your repositories.
I use a Where clause in my FluentNHibernate mappings as follows:
public class FooMap : ClassMap<Foo>
{
public FooMap()
{
Table("MySchema.Foos");
Where("Deleted = 0");
etc etc
}
}
This where clause gets appended to the SQL when I load individual Foo instances through session.Load<Foo>(1) and when I use LINQ queries. However, if another class has a collection of Foos and I iterate through the collection, the SQL generated to load the Foos does NOT contain the where clause.
Is this a bug in FluentNHibernate, or NHibernate in general? Or am I doing something wrong? Or is it (shudder) a 'feature'?
It's a fact, I don't know if it's a bug, a feature, or a missing feature. I ran into the same issue with a legacy database, although mine was with a many-to-one relationship (a "god" lookup table).
I think the justification is that once the foreign-key relationship has been established then the where clause has no meaning. For soft delete tables, look into using filters or (my preference) map to a view that filters the deleted records, assuming they don't need to appear in the UI.
I'm using NHibernate (fluent) to access an old third-party database with a bunch of tables, that are not related in any explicit way. That is a child tables does have parentID columns which contains the primary key of the parent table, but there are no foreign key relations ensuring these relations. Ideally I would like to add some foreign keys, but cannot touch the database schema.
My application works fine, but I would really like impose a referential integrity rule that would prohibit deletion of parent objects if they have children, e.i. something similar 'ON DELETE RESTRICT' but maintained by NHibernate.
Any ideas on how to approach this would be appreciated. Should I look into the OnDelete() method on the IInterceptor interface, or are there other ways to solve this?
Of course any solution will come with a performance penalty, but I can live with that.
I can't think of a way to do this in NHibernate because it would require that NHibernate have some knowledge of the relationships. I would handle this in code using the sepecification pattern. For example (using a Company object with links to Employee objects):
public class CanDeleteCompanySpecification
{
bool IsSatisfiedBy(Company candidate)
{
// Check for related Employee records by loading collection
// or using COUNT(*).
// Return true if there are no related records and the Company can be deleted.
// Hope that no linked Employee records are created before the delete commits.
}
}