One of my applications is a public website, the other is an intranet. The public website runs using a limited security user that must access a certain table through a view, whereas the intranet can access the table itself.
This seems like it would be quite simple to setup using Fluent NHibernate. In my ClassMap I could do a check like this:
public class MyEntityClassMap : ClassMap<MyEntity>
{
public MyEntityClassMap()
{
if (NHibernateConfig.Current.Context == "intranet")
Table("t_MyEntity");
else
Table("v_MyEntity_pub");
... etc
}
}
Is there a simple way of doing this for embedded hbm files? The only method I can think of would be to have two copies of the hbm file, which would be confusing and far from ideal.
Is there perhaps a better way of achieving the same result?
Actually what you ask it is possible. You can actually access the embedded XML files and alter their content before the SessionFactory is build (on Application Start).
Assuming your will choose to reference the "t_MyEntity" in your entities by default here is how you can dynamically change this reference when you want to reference the "v_MyEntity_pub" table instead (the code may not work as it is but you will get the idea):
NHibernate.Cfg.Configuration cfg = new NHibernate.Cfg.Configuration();
cfg.AddAssembly(ASSEMBLYNAME);
if (NHibernateConfig.Current.Context != "intranet") //this is how you have stated you distinguish the intranet application from the other one.
{
string[] resourcesNames = assembly.GetManifestResourceNames();
foreach (string resourceName in resourcesNames)
{
StreamReader sr = new StreamReader(assembly.GetManifestResourceStream(resourceName));
string resourceContent = sr.ReadToEnd();
resourceContent = resourceContent.Replace("t_MyEntity", "v_MyEntity_pub");
cfg.AddXmlString(resourceContent);
}
}
ISessionFactory sessionFactory = cfg.BuildSessionFactory();
The above code should be executed only once for the lifetime of your application and only for the intranet application.
Although this is perhaps not the most helpful answer to your problem, I don't believe that this is possible in a mapping file. I also don't think that two hbm files would work for the same name, as it would be unable to distinguish between the two, you would instead have to have two identical objects each with slightly different names and mapping files. Which as you said in your question, would be completely confusing and ideal would simply be a spot on the horizon that you were hoping to, someday, reach.
Why is it that can't access everything directly through the view? I'm assuming there is no writing involved in this process? Is there any way you can change this method of accessing data while still maintaining your security?
Related
All,
I have a requirement to hide my EF implementation behind a Repository. My simple question: Is there a way to execute a 'find' across both a DbSet AND the DbSet.Local without having to deal with them both.
For example - I have standard repository implementation with Add/Update/Remove/FindById. I break the generic pattern by adding a FindByName method (for demo purposes only :). This gives me the following code:
Client App:
ProductCategoryRepository categoryRepository = new ProductCategoryRepository();
categoryRepository.Add(new ProductCategory { Name = "N" });
var category1 = categoryRepository.FindByName("N");
Implementation
public ProductCategory FindByName(string s)
{
// Assume name is unique for demo
return _legoContext.Categories.Where(c => c.Name == s).SingleOrDefault();
}
In this example, category1 is null.
However, if I implement the FindByName method as:
public ProductCategory FindByName(string s)
{
var t = _legoContext.Categories.Local.Where(c => c.Name == s).SingleOrDefault();
if (t == null)
{
t = _legoContext.Categories.Where(c => c.Name == s).SingleOrDefault();
}
return t;
}
In this case, I get what I expect when querying against both a new entry and one that is only in the database. But this presents a few issues that I am confused over:
1) I would assume (as a user of the repository) that cat2 below is not found. But it is found, and the great part is that cat2.Name is "Goober".
ProductCategoryRepository categoryRepository = new ProductCategoryRepository();
var cat = categoryRepository.FindByName("Technic");
cat.Name = "Goober";
var cat2 = categoryRepository.FindByName("Technic");
2) I would like to return a generic IQueryable from my repository.
It just seems like a lot of work to wrap the calls to the DbSet in a repository. Typically, this means that I've screwed something up. I'd appreciate any insight.
With older versions of EF you had very complicated situations that could arise quite fast due to the required references. In this version I would recomend not exposing IQueryable but ICollections or ILists. This will contain EF in your repository and create a good seperation.
Edit: furthermore, by sending back ICollection IEnumerable or IList you are restraining and controlling the queries being sent to the database. This will also allow you to fine tune and maintain the system with greater ease. By exposing IQueriable, you are exposing yourself to side affects which occur when people add more to the query, .Take() or .Where ... .SelectMany, EF will see these additions and will generate sql to reflect these uncontrolled queries. Not confining the queries can result in queries getting executed from the UI and is more complicated tests and maintenance issues in the long run.
since the point of the repository pattern is to be able to swap them out at will. the details of DbSets should be completly hidden.
I think that you're on a good path. The only thing I probaly ask my self is :
Is the context long lived? if not then do not worry about querying Local. An object that has been Inserted / Deleted should only be accessible once it has been comitted.
if this is a long lived context and you need access to deleted and inserted objects then querying the Local is a good idea, but as you've pointed out, you may run into difficulties at some point.
Suppose I have a class Customer that is mapped to the database and everything is a-ok.
Now suppose that I want to retrieve - in my application - the column name that NH knows Customer.FirstName maps to.
How would I do this?
You can access the database field name through NHibernate.Cfg.Configuration:
// cfg is NHibernate.Cfg.Configuration
// You will have to provide the complete namespace for Customer
var persistentClass = cfg.GetClassMapping(typeof(Customer));
var property = persistentClass.GetProperty("FirstName");
var columnIterator = property.ColumnIterator;
The ColumnIterator property returns IEnumerable<NHibernate.Mapping.ISelectable>. In almost all cases properties are mapped to a single column so the column name can be found using property.ColumnInterator.ElementAt(0).Text.
I'm not aware that that's doable.
I believe your best bet would be to use .xml files to do the mapping, package them together with the application and read the contents at runtime. I am not aware of an API which allows you to query hibernate annotations (pardon the Java lingo) at runtime, and that's what you would need.
Update:
Judging by Jamie's solution, NHibernate and Hibernate have different APIs, because the Hibernate org.hibernate.Hibernate class provides no way to access a "configuration" property.
I have som entities and now want to make some DTO´s based on there entities using nhibernate.
I have a Service - Allocation -Ressource where allocation describes how the ressource is allocated for the service.
I want a DTO like
ServiceDTO
-Name
-RessourceDTO
where RessourceDTO also has a name.
In the examples I have see for NHibernate projection/DTO you either use properties or constructor. If I use The Constructor approach I would have something like
ServiceDTO(Name, List
But I can't figure out how to make this work.
Another approach is to extract all the services and then loop through them and hit the database each time, or extract a larger result and then make the DTO's
What is the best approach? I going to hide all of this inside a repository.
How about
public ServiceDTO GetDTOFor(int Id);
{
var service = Session.CreateCriteria<Service>()
.Add(Restrictions.Eq("Id", id)
.SetFetchMode("Resources", fetchmode.eager) // eager load resources
.uniqueResult<Service>();
return new ServiceDTO(service.Name, service.Resources.ToList()) // Copy the Resources
}
I'm using NHibernate to connect to an ERP database on our DB2 server. We have a test schema and a production schema. Both schemas have the same table structure underneath. For testing, I would like to use the same mapping classes but point NHibernate to the test environment when needed and then back when in production. Please keep in mind that we have many production schemas and each production schema has an equivalent test schema.
I know that my XML mapping file has a schema property inside it, but since it's in XML, it's not like I can change it via a compiler directive or change the schema property based on a config file.
Any ideas?
Thank You.
No need to specify schema in the mappings: there's a SessionFactory-level setting called default_schema. However, you can't change it at runtime, as NHibernate pregenerates and/or caches SQL queries, including the schema part.
To get what I wanted, I had to use NHibernate.Mapping.Attributes.
[NHibernate.Mapping.Attributes.Class(0, Table = “MyTable”, Schema = MySchemaConfiguration.MySchema)]
In this way, I can create a class like MySchemaConfiguration and have a property inside of it like MySchema. I can either set the property's value via a compiler directive or get it through a configuration file. This way I only have to change the schema in one place and it will be reflected throughout all of the other mappings.
I have found following link that actually fixes the problem.
How to set database schema for namespace in nhibernate
The sample code could be
cfg.ClassMappings.Where(cm => cm.Table.Schema == "SchemaName")
.ForEach(cm => cm.Table.Schema = "AnotherSchemaName");
This should happen before you initialize your own data service class.
#Brian, I tried NHibernate.Mapping.Attributes, the attribute value you put inside should be a constant. So it could not be updated during run time. How could you have set the property's value using a parameter value in configuration file?
The code to fix HBM XML resources.
// This is how you get all the hbm resource names.
private static IList<string> GetAllHbmXmlResourceNames(Assembly assembly)
{
var result = new List<string>();
foreach (var resource in assembly.GetManifestResourceNames())
{
if (resource.EndsWith(".hbm.xml"))
{
result.Add(resource);
}
}
return result;
}
// This is how you get the stream for each resource.
Assembly.Load(assembly).GetManifestResourceStream(name)
// What you need to do next is to fix schema name in this stream
// Replacing schema name.
private Stream FixSchemaNameInStream(Stream stream)
{
StreamReader strStream = new StreamReader(stream);
string strCfg = strStream.ReadToEnd();
strCfg = strCfg.Replace(string.Format("schema=\"{0}\"" , originalSchemaName), string.Format("schema=\"{0}\"" , newSchemaName));
return new MemoryStream(Encoding.ASCII.GetBytes(strCfg));
}
Take a look at SchemaUpdate.
http://blogs.hibernatingrhinos.com/nhibernate/archive/2008/04/28/create-and-update-database-schema.aspx
If i want to use Linq-SQL i also have to drag the DB Table unto the designer surface to create the entity classes.
I always like full control in my application and do not like the classes created by dotnet.
Is it possible to provide this connection between Linq and the DB using my own Data Access Layer Entity classes?
How can i get it done?
You can write your own classes very easily using Linq-to-SQL - just involves painting your classes with some Attributes.
For Example, this is a very simple table I have in one of my projects, and it works with Linq-to-SQL just fine:
[Table(Name = "Categories")]
public class Category : IDataErrorInfo
{
[Column(IsPrimaryKey = true, IsDbGenerated = true, AutoSync = AutoSync.OnInsert)]
public int Id { get; set; }
[Column] public string Name { get; set; }
[Column] public string ExtensionString { get; set; }
}
The code was very easy, especially if you make your property names line up with your table names (you don't have to).
Then you just need a Repository to connect to the DB:
class CategoryRepository : ICategoryRepository
{
private Table<Category> categoryTable;
public CategoryRepository(string connectionString)
{
categoryTable = (new DataContext(connectionString)).GetTable<Category>();
}
}
Of course there is more to it, but this shows you the very basics and it is not hard to do once you understand it. This way you have 100% control over your classes and you can still take advantage of Linq-to-SQL.
I learned this approach from Pro ASP.NET MVC Framework, an awesome book.
If you want to see more, all of my Linq-to-SQL classes were written from scratch on one of my projects you can browse here.
To avoid drag & drop you can take a look at SqlMetal.exe.
However, it sounds like you really are requesting Persistence Ignorance, and I'm not sure that this is possible with L2S - it certainly isn't possible with LINQ to Entities until .NET 4...
I once wrote a blog post on using SqlMetal.exe and subsequently modifying the generated schema - perhaps you will find it useful, although it has a different underlying motivation.
I've got a couple tutorials up on CodeProject that walk through how to do this, including how to handle the relationships (M:M, 1:M, M:1) in an OO way and keep them in synch as you make updates:
A LINQ Tutorial: Mapping Tables to Objects
A LINQ Tutorial: Adding/Updating/Deleting Data