I have an entity which looks like this:
public class MyEntity {
public virtual int Id { get; set; }
public virtual string Name { get; set ; }
public virtual Role UserRole { get; set; }
}
The property "Name" is not a mapped property but "Id" and "UserRole" is. I want to populate this class using a custom criteria like this:
ICriteria c = Db.CreateCriteria<MyEntity>("m")
.CreateAlias("Role", "r")
.SetProjection(Projections.ProjectionList()
.Add(
Projections.SqlProjection(#"Name = case TypeId
when 2 then (select Name from tblOne where Id = EntityId)
when 4 then (select Name from tblTwo where Id = EntityId)
when 3 then (select Name from tblThree where Id = EntityId)
end", new string[] { "Name" }, new NHibernate.Type.IType[] { NHibernateUtil.String }))
.Add(Projections.Property("m.Id"), "Id")
.Add(Projections.Property("r.Id"), "UserRole.Id")
.Add(Projections.Property("r.Desc"), "UserRole.Desc")
.Add(Projections.Property("r.Permission"), "UserRole.Permission")
)
.SetResultTransformer(Transformers.AliasToBean<EntityPermission>());
However if I execute this it throws an exception "Could not find a setter for property '{UserRole.Id}' in class 'MyEntity'".
So my question is doesn't aliastobean support associations, and if it does, what's the proper syntax?
You should crate an alias to "UserRole" instead of "Role", since the name of the property is "UserRole", not "Role".
Related
Say there's a project that involves a lot of tables, all of which reference a single table that holds a field called group ID. However, not all of the tables reference this single table directly.
For example, the db contains a garages table, and each garage is referenced by rows in a cars table, and each car is referenced by rows in a tire table. I want to organize the data into two groups, group1 and group2. Each garage has a group ID, however the cars and tires do not. Here is a diagram of the tables:
As you can see, assets in group 1 are highlighted red, and assets in group 2 are highlighted yellow. But not all rows highlighted necessarily have a group ID. You can imagine how this may go even longer, with hub caps having forgien keys for their respective tires, bolts with forgien keys to their respective hub caps, etc.
Is there a way to dynamically call an EF query that can get the group ID from the related garage, given EITHER a child car, tire, hub cap, and so on? Implementation might be something like this:
var tire = Context.Tires.where(t => t.ID == 3).FirstOrDefault<Tire>();
tire.findShortestJoinToEntity(entity => entity.GetProperty("GroupID") != null);
// Returns 2, the group of "Toyota Tires"
In more technical terms, it would need to recursively check for any forgien keys in the passed in model, then all the forgein keys of the referenced models, all their referenced models, etc. until there are either no more forgein keys or a field with the passed in name is found.
I think you can do it using reflection, it is not the ideal solution due to possible performance issues, but it does what you are looking for.
The key idea here is having all entity classes implementing a common interface IEntity so we can call a recursive method over any entity. Also assumes that we have both parentId (foreign key) and parent object in any entity, although you can tweak the method to use only the parentId, I have put the parent object only to take advantage of EF tracking mechanism linking parents and child entities
public interface IEntity
{
int Id { get; set; }
}
public class Group : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Garage : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
public virtual int GroupId { get; set; }
public virtual Group Group { get; set; }
}
public class Car : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
public virtual int GarageId { get; set; }
public virtual Garage Garage { get; set; }
}
// ... other entity classes here
public static class EntityExtensions
{
public static int FindGroupId(this IEntity entity, Context context, string propertyName = null)
{
var groupId = 0;
if (entity != null)
{
var entityType = entity.GetType();
if (entity is Group)
{
// we found it
groupId = propertyName == null
? entity.Id
: (int)entityType.GetProperty(propertyName).GetValue(entity);
}
else
{
// look into properties for parent entities
foreach (var property in entityType.GetProperties())
{
if (property.GetGetMethod().IsVirtual
&& property.PropertyType.GetInterface(nameof(IEntity)) != null)
{
// it is parent entity
var parentEntity = property.GetValue(entity) as IEntity;
if (parentEntity == null)
{
// if parent entity is null, let's get it from db
var parentIdName = $"{property.PropertyType.Name}Id";
var parentId = (int)entityType.GetProperty(parentIdName).GetValue(entity);
parentEntity = context.Find(property.PropertyType, parentId) as IEntity;
}
groupId = FindGroupId(parentEntity, context, propertyName);
}
if (groupId > 0)
{
// we found it
break;
}
}
}
}
return groupId;
}
}
The RavenDb documentation states:
Numeric or Guid Id properties are supported and will work seamlessly. In this case, RavenDB will automatically make the translation between the inner string ID to the numeric or Guid value shown in the entity and back.
I have stored the following objects:
class A
{
public Guid Id { get; set; }
public Guid BId { get; set; }
}
class B
{
public Guid Id { get; set; }
public string Name { get; set; }
}
I have then created the following projection:
class AB
{
public Guid Id { get; set; } // This should be the Id of A
public Guid BId { get; set; } // This should be the Id of B
public string BName { get; set; } // This should be the name of B
}
I have created the following index to create the projection:
class MyIndex : AbstractIndexCreationTask<AB>
{
public MyIndex()
{
Map = docs =>
from d in docs
select new
{
d.Id,
d.BId,
BName = string.Empty
};
TransformResults = (database, results) =>
from r in results
let b = database.Load<B>("bs/" + r.BId.ToString())
select new
{
r.Id,
r.BId,
BName = b.Name
};
}
}
When I use the following query:
session.Query<AB, MyIndex>().FirstOrDefault(t => t.Id == guid);
I get this exception:
Error converting value "bs/cc0a65ae-dd36-4437-8a57-fa20b91eeef7" to type 'System.Guid'. Path 'Id'.
Questions:
It is caused by the conversion in my projection since the Id is a string there and not my Guid anymore. However, leaving it out will not return the Id. What must I do?
I have to use the string building "bs/" + r.BId.ToString() to load the related doc. Is there a way not having to do this? Is there some sort of function that would resolve the doc tag for me?
Is there a generic way to strip out the document tag altogether?
My constraints.
I will generate the Guid and cannot let RavenDb generate it for me. I know that the Document ID in reality is string, but I really need to use a Guid that I create. I would prefer to own the Id property of my entities.
I'm using Raven.Client 1.0.972
You can achieve this using a MultiMap/Reduce Index, but you will need some hackery:
1) You will need to reduce using strings, not guids. You can still get the values back as guids in your AB class, as I will demonstrate below.
2) You can't call the first property of your AB class "Id", as raven will try to translate it to "__document_id". So call it "AId" and it will work fine.
3) In the mapping phase, you have to manipulate the strings yourself to strip off the document key prefix.
Here's a sample program that puts it all together. This demonstrates that it does indeed work, but I think it also shows why Ayende prefers string identifiers so you don't have to deal with this kind of mess.
using System;
using System.Linq;
using Raven.Client.Document;
using Raven.Client.Indexes;
namespace RavenScratchTest
{
class Program
{
static void Main()
{
var documentStore = new DocumentStore { Url = "http://localhost:8080" };
documentStore.Initialize();
IndexCreation.CreateIndexes(typeof(Program).Assembly, documentStore);
using (var session = documentStore.OpenSession())
{
var b = new B { Id = Guid.NewGuid(), Name = "Foo" };
var a = new A { Id = Guid.NewGuid(), BId = b.Id };
session.Store(a);
session.Store(b);
session.SaveChanges();
}
using (var session = documentStore.OpenSession())
{
var a = session.Query<A>().Customize(x => x.WaitForNonStaleResults()).First();
var b = session.Query<B>().Customize(x => x.WaitForNonStaleResults()).First();
Console.WriteLine("A: Id = {0}", a.Id);
Console.WriteLine(" BId = {0}", a.BId);
Console.WriteLine();
Console.WriteLine("B: Id = {0}", b.Id);
Console.WriteLine(" Name = {0}", b.Name);
Console.WriteLine();
var guid = a.Id;
var ab = session.Query<AB, MyIndex>().Customize(x => x.WaitForNonStaleResults())
.FirstOrDefault(t => t.AId == guid);
if (ab == null)
Console.WriteLine("AB: NULL");
else
{
Console.WriteLine("AB: AId = {0}", ab.AId);
Console.WriteLine(" BId = {0}", ab.BId);
Console.WriteLine(" BName = {0}", ab.BName);
Console.WriteLine();
}
}
Console.WriteLine();
Console.WriteLine("Done.");
Console.ReadLine();
}
}
class A
{
public Guid Id { get; set; }
public Guid BId { get; set; }
}
class B
{
public Guid Id { get; set; }
public string Name { get; set; }
}
class AB
{
public Guid AId { get; set; }
public Guid BId { get; set; }
public string BName { get; set; }
}
class MyIndex : AbstractMultiMapIndexCreationTask<MyIndex.ReduceResult>
{
public MyIndex()
{
AddMap<A>(docs => from a in docs
select new
{
AId = a.Id.ToString().Split('/')[1],
a.BId,
BName = (string)null
});
AddMap<B>(docs => from b in docs
select new
{
AId = (string)null,
BId = b.Id.ToString().Split('/')[1],
BName = b.Name
});
Reduce = results => from result in results
group result by result.BId
into g
select new
{
g.FirstOrDefault(x => x.AId != null).AId,
BId = g.Key,
g.FirstOrDefault(x => x.BName != null).BName
};
}
internal class ReduceResult
{
public string AId { get; set; }
public string BId { get; set; }
public string BName { get; set; }
}
}
}
You can provide an ID to RavenDB explicitly upon saving:
session.Store(doc, explicitIdValueString);
The explicitIdValueString can be a Guid string. This value will be used to identify the document within the entire database and will not be prefixed by a type tag name. You can also customized the tag name, or the ID generation strategy all together by overriding conventions on IDocumentStore.Conventions such as FindTypeTagName which is a Func<Type, string>.
The main problem is that while RavenDB can deal with numeric / integer on the client, but on the server side, RavenDB uses string ids.
In general, it isn't recommended to use Guids / numeric ids.
Suppose you have Users and you want to generate guid identifiers for these.
new User { Id = "users/" + Guid.NewGuid().ToString("N") }
For sanity purposes, in documents i eagerly create keys for I set them up as immutable.
public class User
{
public User(Guid? guid = null)
{
IdPart = (guid ?? Guid.NewGuid()).ToString("N")
}
string IdPart { get; }
string Id => $"Users/{IdPart}"
Sometimes IdPart actually a whole key. Suppose we have "Users/abc". If a user has a Project. I will commonly create a document similar to:
public class Project
{
public User(Guid? userId = null)
{
UserId = "Users/" + (guid ?? Guid.NewGuid()).ToString("N");
Id = $"{UserId}/project/"
}
Note the trailing project/ this will inform raven to create a HiLo value after the slash.
This concept can be used to easily intermix both assigned identifiers, natural identifiers, and sequence/hilo/identity keys while promoting readable identifiers as opposed to 1. 1 is what? But User/abc/project/1, i can tell you what that is. The first project created by abc
class MyIndex : AbstractIndexCreationTask<AB>
{
public MyIndex()
{
Map = docs =>
from d in docs
select new
{
d.Id,
d.BId,
BName = string.Empty
};
TransformResults = (database, results) =>
from r in results
let b = database.Load<B>("bs/" + r.BId.ToString())
select new
{
Id = Guid.Parse(r.Id.ToString().Split('/').Last()),
r.BId,
BName = b.Name
};
}
}
I am using Castle ActiveRecord and created two entities as follow :
[ActiveRecord("Teams")]
public class Team : ActiveRecordLinqBase<Team>
{
public Team()
{
Members = new List<Member>();
}
[PrimaryKey]
public int Id { get; set; }
[Property("TeamName")]
public string Name { get; set; }
[Property]
public string Description { get; set; }
[HasMany(Inverse = true,
Lazy = true,
Cascade = ManyRelationCascadeEnum.AllDeleteOrphan)]
public virtual IList<Member> Members { get; set; }
}
[ActiveRecord("Members")]
public class Member : ActiveRecordLinqBase<Member>
{
[PrimaryKey]
public int Id { get; set; }
[Property]
public string FirstName { get; set; }
[Property]
public string Lastname { get; set; }
[Property]
public string Address { get; set; }
[BelongsTo("TeamId")]
public Team Team { get; set; }
}
And I used ICriterion to have filtered Team data
IList<ICriterion> where = new List<ICriterion>();
where.Add(Expression.Eq("Name", "name1"));
ICriterion[] criteria = where.ToArray();
var teams = Team.FindAll(criteria);
So far it works well, but I want to add another filter on Members table. The result query would be like this
select *
from Teams t join Member m on t.Id = m.TeamId
where t.Name = 'name1'
and m.Address = 'address'
How to get this done using ICriterion?
I mean how to add criterion for Team.Members property.
Not using LINQ. (I know this could be done using linq easily)
For join you can use
DetachedCriteria
DetachedCriteria criteriaTeam = DetachedCriteria.For<Team>();
DetachedCriteria criteriaMember = criteriaTeam .CreateCriteria("Members");
criteriaTeam .Add(Expression.Eq("Name", "name1"));
criteriaMember.Add(Expression.Eq("Address", "address"));
ICriteria executableCriteria = criteriaTeam .GetExecutableCriteria(session);
executableCriteria.List<Team>();
This will return only Team.To return both Team and Members in a single fetch you can use NHibernate result transformer Projections in NHibernate
I have the following model and methods:
[PetaPoco.TableName("TestStep")]
[PetaPoco.PrimaryKey("ID")]
public class TestStep
{
public int ID { get; set; }
public int ParentID { get; set; }
public string Name { get; set; }
public string Details { get; set; }
}
public IEnumerable<TestStep> GetById(int ID)
{
var db = new PetaPoco.Database("TestProcedureDB");
return db.Query<TestStep>(#"SELECT * FROM TESTSTEP TS
INNER JOIN TESTSTEPLINK L ON L.STEPID = TS.ID
WHERE L.TESTID = #0", ID);
}
When the POCO is populated, the ID property value is that of the ID column in the TESTSTEPLINK table. If I change the query to return SELECT TS.* then all is ok. Is this a bug or am I missing something?
PetaPoco will go through all your return columns and map them.
First it will map Id from the table TESTSTEP, then it finds Id again and so it overrides the previously set value.
If you are doing a join like this and only want specific information, you should either only specify the columns you want to return (otherwise you are bringing back more data than needed which is a performance issue)
or do as you did to fix it by using TS.* to ensure only the columns from the first table are mapped.
I have the following database tables. Primary keys are ID and AnimalType.Type is a unique string.
Animal
- ID
- Name
- TypeID
AnimalType
- ID
- Type [Herbivore, Carnivore]
My classes are as follows.
public class Animal
{
public int ID { get; private set; }
public string Name { get; set; }
public AnimalType Type { get; set; }
}
public class AnimalType
{
private int ID { get; set; }
public string Type { get; set; }
}
How would I get the following code to work in NHibernate so that it references the same AnimalType of Herbivore?
var horse = new Animal() { Name = "Horse", Type = new AnimalType() { "Herbivore" }};
repository.SaveOrUpdate(horse);
var rabbit = new Animal() { Name = "Rabbit", Type = new AnimalType() { "Herbivore" } };
repository.SaveOrUpdate(rabbit);
UPDATE
Be nice if I could get NHibernate to perform this logic: http://andreas.scherbaum.la/blog/archives/11-Avoid-Unique-Key-violation.html
I'd investigate a few options.
1) If the data doesn't change frequently, can you make AnimalType an Enum instead of an Entity Object?
2) Instantiate an object of the Herbivore AnimalType using animalTypeRepository.FindByType("Herbivore") and pass that object into your new Animal.
3) Move the above logic into the animalRepository.SaveOrUpdate(animal) method so that you'd have...
public class AnimalRepository
{
public void SaveOrUpdate(Animal animal)
{
var animalType = animal.Type;
if (animalType.ID == 0)
{
animal.Type = animalTypeRepository.Find(animalType.Type);
}
// save or update animal...
}
}