Complex mapping with tables without IDs - c#

Short version:
Is it possible in NHibernate to create a mapping for a class that has no corresponding table in the database and specify for each property where the data should come from?
Long version:
I have the following class:
public class TaxAuditorSettings
{
private readonly IList<Month> _allowedMonths = new List<Month>();
private readonly IList<Company> _allowedVgs = new List<Company>();
public IList<Month> AllowedMonths
{
get { return _allowedMonths; }
}
public IList<Company> AllowedVgs
{
get { return _allowedVgs; }
}
}
The class Company is a normal entity that is mapped to a table.
The class Month is a simple class without ID or existing mapping (Constructor and error checking removed for brevity):
public class Month
{
public int MonthNumber { get; set; }
public int Year { get; set; }
}
My database has the following two tables:
Table TAX_AUDITOR_ALLOWED_COMPANIES has only one column COMPANY_ID that is a FK to the table COMPANY and has a UNIQUE index.
Table TAX_AUDITOR_ALLOWED_MONTHS has two columns MONTH_NUMBER and YEAR. There is a UNIQUE index spanning both columns.
I would like to map TaxAuditorSettings such that I can ask my NHibernate session for an object of this type and NHibernate then should put the contents of TAX_AUDITOR_ALLOWED_MONTHS into the list TaxAuditorSettings.AllowedMonths and the companies referenced in TAX_AUDITOR_ALLOWED_COMPANIES into the list TaxAuditorSettings.AllowedCompanies.
Is this even possible? If so, how? If not, how would you do it instead?
Please note: I can change the database if necessary.

not quite what you requested for but here goes
public TaxAuditorSettings GetAuditorSettings(ISession session)
{
// assuming there is a ctor taking the enumerables as parameter
return new TaxAuditorSettings(
session.CreateSQLQuery("SELECT MONTH_NUMBER, YEAR FROM TAX_AUDITOR_ALLOWED_MONTHS")
.SetResultTransformer(new MonthResultTransformer())
.Future<Month>(),
session.CreateCriteria<Company>()
.Add(NHibernate.Criterion.Expression.Sql("Id IN (SELECT COMPANY_ID FROM TAX_AUDITOR_ALLOWED_COMPANIES)"))
.Future<Company>())
}
class MonthResultTransformer : IResultTransformer
{
public IList TransformList(IList collection)
{
return collection;
}
public object TransformTuple(object[] tuple, string[] aliases)
{
return new Month
{
MonthNumber = (int)tuple[0],
Year = (int)tuple[1],
}
}
}
Update: saving
public void SaveOrUpdate(ISession session, TaxAuditorSettings settings)
{
using (var tx = session.BeginTransaction())
{
// get whats in the database first because we dont have change tracking
var enabledIds = session
.CreateSqlQuery("SELECT * FROM TAX_AUDITOR_ALLOWED_COMPANIES")
.Future<int>();
var savedMonths = session
.CreateSQLQuery("SELECT MONTH_NUMBER, YEAR FROM TAX_AUDITOR_ALLOWED_MONTHS")
.SetResultTransformer(new MonthResultTransformer())
.Future<Month>();
foreach (var id in settings.AllowedVgs.Except(enabledIds))
{
session.CreateSqlQuery("INSERT INTO TAX_AUDITOR_ALLOWED_COMPANIES Values (:Id)")
.SetParameter("id", id).ExecuteUpdate();
}
foreach (var month in settings.AllowedMonths.Except(savedMonths))
{
session.CreateSqlQuery("INSERT INTO TAX_AUDITOR_ALLOWED_MONTHS Values (:number, :year)")
.SetParameter("number", month.Number)
.SetParameter("year", month.Year)
.ExecuteUpdate();
}
tx.Commit();
}
}
Note: if you can change the database it would be much easier and performant to sanitise the tables

I would do it this way.
public class MonthMap : ClassMap<Month>{
public MonthMap(){
CompositeId()
.KeyProperty(x=>x.MonthNumber,"MONTH_NUMBER")
.KeyProperty(x=>x.Year);
Table("TAX_AUDITOR_ALLOWED_MONTHS");
}
}
Add a column to the COMPANY table called TaxAuditable and map it to a bool property. Update the column to be 1 where a matching row is found in TAX_AUDITOR_ALLOWED_COMPANIES. Then remove the table TAX_AUDITOR_ALLOWED_COMPANIES as it serves no real purpose.
Now you have a Company with an appropriate property on it you can query Company's where TaxAuditable is true and pass them into a method along with the Months to do your work/calculations etc... Something like this perhaps?
var taxAuditableCompanies = session.QueryOver<Company>()
.Where(x=>x.TaxAuditable==true).Future();
var months=session.QueryOver<Month>().Future();
var myService = new MyService();
myService.DoSomeWork(taxAuditableCompanies, months);

Related

Dapper with Mapping by code: Multi-Mapping with repeating column names

I'm trying to perform a simple query and the result data is almost all null.
I have this table structure
Table Registros
ID | Autonumeric
TareaM_Id | Numeric
Fecha | Date/Time
and Macro_tareas table
ID | Autonumeric
Nombre | Short Text
I have mapped the classes in C# like this:
[Table("Registros")]
public class Registro
{
[Column("ID")]
public virtual int ID { get; set; }
[Column("Fecha")]
public virtual DateTime Fecha { get; set; }
[Column("TareaM_Id")]
public virtual int TareaM_Id { get; set; }
public virtual MacroTarea MacroT { get; set; }
}
[Table("Macro_tarea")]
public class MacroTarea
{
[Column("ID")]
public virtual int ID { get; set; }
[Column("Nombre")]
public virtual string Nombre{ get; set; }
public virtual ICollection<Registro> Registros { get; set; }
}
This is the query i'm trying to use
string sql = #"SELECT reg.ID, mac.ID
FROM Registros as reg INNER JOIN Macro_tarea as mac on reg.TareaM_Id = mac.ID
WHERE Fecha = #Fecha";
using (IDbConnection db = new OleDbConnection(ConnectionString))
{
var result = db.Query<Registro,MacroTarea, Registro>(sql,
(reg,mac) =>
{
reg.MacroTarea = mac;
return reg;
}
,new { #Fecha = new DateTime(2019, 1, 4).Date }
, splitOn: "mac.ID")
.AsList();
}
I'm trying to only retrieve ids, but both id become null why is this happening?
The thing is, if I add Registros.Fecha and Macro_tarea.Nombre to the query, it got the value correctly. But id keep coming null.
Apparently the issue is happening only with ids. I suspect this issue is due to duplicate column names.
I'm working with Microsoft Access just in cast that matters.
My question is not similar to the possible duplicate because I have the classes defined as they should be mapped.
Renaming your database columns because your code cannot cope with the data is not a good idea. In the world of separation of concerns, why should your database care? There are good database reasons to name ID columns "Id", and you may not even have the option to change them.
There's another issue with Dapper mapping that renaming columns does not get around; repeated types. If you are trying to map to more than one instance of a class Dapper gets confused, and renaming columns won't work because you will rename both instances.
Here is the solution I have come up with. It's similar to a lot of examples that use a dictionary, except:
it can nest to as many levels as you like
can cope with Dappers 7 item limit
can cope with duplicates of the same class
can be reused e.g., for Get, GetCurrent and GetAll
In this example there is an Auction that has many Lots. Each Lot may have 1 or many Items. Items might be packs of Items. The Items are from a limited catalogue and we like relational data, so a Things table contains the details on each Item, like colour, size, etc. Here we are only getting a single Lot, but getting an Auction is the same with another level on top for Auction.
Parameter 1 - The SQL to get everything in one go
Parameter 2 - A Type array of each object we'll get back. For this reason it's best to order your SELECT to group the fields into the classes
Parameter 3 - Call the method we're about to write with the SQL result
Parameter 4 - Standard parameter array for the SQL. SQL Injection is bad, m'kay?
public async Task<List<Lot>> GetAll(int auctionId)
{
using (var connection = new SqlConnection(_appSettings.ConnectionString))
{
await connection.OpenAsync();
var result = new List<Lot>();
await connection.QueryAsync($#"
SELECT [Lot].*,
[Item].[Id],
[Item].[LotId],
[Item].[Notes],
itemDetails.[Id],
itemDetails.[ThingId],
itemDetails.[Colour],
itemDetails.[Size],
[SubItem].[Id],
[SubItem].[ItemId],
[SubItem].[Notes],
subItemDetails.[Id],
subItemDetails.[ThinId],
subItemDetails.[Colour],
subItemDetails.[Size]
FROM [Lot]
INNER JOIN [Item] ON [Item].[LotId] = [Lot].[Id]
LEFT JOIN [Thing] AS itemDetails ON itemDetails.[Id] = [Item].[ThingId]
LEFT JOIN [SubItem] ON [SubItem].[ItemId] = [Item].[Id]
LEFT JOIN [Thing] AS subItemDetails ON subItemDetails.[Id] = [SubItem].[ThingId]
WHERE [AuctionId] = #{nameof(auctionId)}
ORDER BY [Lot].[Id], [Item].[Id], [Expansion].[Id];",
new Type[] {
typeof(Lot),
typeof(Item),
typeof(Thing),
typeof(Expansion),
typeof(Thing)
},
MapResult(result),
new
{
AuctionId = auctionId
}
);
return result.ToList();
}
}
private Func<object[], Lot> MapResult(List<Lot> result)
{
return (obj) =>
{
Lot lot = (Lot)obj[0];
Item item = (Item)obj[1];
Thing itemDetails = (Thing)obj[2];
SubItem subItem = (SubItem)obj[3];
Thing subItemDetails = (Thing)obj[4];
if (lot != null)
{
if (result.Any(a => a.Id == lot.Id))
{
lot = result.First(a => a.Id == lot.Id);
}
else
{
result.Add(lot);
}
}
if (item != null)
{
if (lot.Items.Any(i => i.Id == item.Id))
{
item = lot.Items.First(i => i.Id == item.Id);
}
else
{
lot.Items.Add(item.FromThing(itemDetails));
}
}
if (expansion != null)
{
if (item.SubItems.Any(e => e.Id == subItem.Id) == false)
{
item.SubItems.Add(subItem.FromThing(subItemDetails));
}
}
return null;
};
}
MapResult is the meat of the code. It returns a Func with two types, the Type array we defined above and the return Type, and takes a List of the top level object.
I then map each item from the object array to another of it's actual type. This keeps the code easier to read, and enables properties and methods of the object to be accessed without issue.
Then it's a case of stepping down the hierarchy, checking at each step if one already exists with a matching id, and swapping the iterator to a reference to it if it does. This means that following code will add to the existing item.
In the particular case I've also added a FromThing function to allow easier combining of object properties.
As we discussed in comments, this is an issue due to duplicate column names in two tables. This is where the similar issue and solution could be found. But, it does not include "mapping by code" as you said. So it is not exact duplicate.
I suggest you change the names of ID fields in your tables to avoid colliding them. Of-course, you should also change the name of your POCO properties and mappings accordingly.
If you cannot change the column names in table, change the POCO property name, and use the column alias in SQL query to match those new property names.
I hope this helps you.
The problem was effectively the name of the properties.
I solved it using Custom Column Mapping to do it i got two possible solutions:
Without extensions
First, we define a Dictionary with the name of the column as key, and the name of the property as value
IDictionary<string, string> columnMaps = new Dictionary<string, string>()
{
{ "Macro_tarea.ID", "ID" },
{ "Registros.ID", "ID" }
};
Then, we define a delegate to obtain the PropertyInfo object of the property to which we intend to assign the alias of the previous dictionary
var mapper = new Func<Type, string, PropertyInfo>((type, columnName) =>
{
if (columnMaps.ContainsKey(columnName))
return type.GetProperty(columnMaps[columnName]);
else
return type.GetProperty(columnName);
});
Now, we define an object that implements the ITypeMap interface using CustomPropertyTypeMap implementation
ITypeMap MacroTareaMapper = new CustomPropertyTypeMap(typeof(Macro_tarea),
(type, columnName) => mapper(type, columnName));
ITypeMap RegistrosMapper = new CustomPropertyTypeMap(typeof(Registros),
(type, columnName) => mapper(type, columnName));
Then we register them
SqlMapper.SetTypeMap(typeof(Macro_tarea), MacroTareaMapper);
SqlMapper.SetTypeMap(typeof(Registros), RegistrosMapper);
Simpler solution with Dapper.FluentMap
It is implemented as follows:
We create a class that inherits from EntityMap<T> and using the Map method we define which column corresponds to each property. For example,
internal class Macro_tareaMap : EntityMap<Macro_tarea>
{
internal Macro_tareaMap()
{
//Mi propiedad ID esta asociada a la columna Macro_tarea.ID
Map(x => x.ID).ToColumn("Macro_tarea.ID");
}
}
Then just register it
FluentMapper.Initialize((config) =>
{
config.AddMap(new Macro_tareaMap());
});
Hope it helps another people!
Source: https://medium.com/dapper-net/custom-columns-mapping-1cd45dfd51d6

Orchard 1.8 - Creating a part corresponding to a 1-n relationship

I need to create a part to attach to an existing content type. The model associated to this part has no variables except a list of items, corresponding to a 1-N relationship with the data contained in a table I created. If I use the Record approach, creating a Model containing only the list, it will generate a "fake" table with no other data except its Id. This is the current code:
**Model**
public class MyPartRecord : ContentPartRecord
{ public virtual IList<OtherRecord> MyList { get; set; } }
public class MyPart : ContentPart<MyPartRecord>
{
public IList<OtherRecord> MyList {
get { return Record.MyList; }
}
public class OtherRecord
{
public virtual string Var1 { get; set; }
public virtual int Var2 { get; set; }
}
**Migration**
SchemaBuilder.CreateTable("MyPartRecord", table => table
.ContentPartRecord());
SchemaBuilder.CreateTable("OtherRecord", table => table
.Column<int>("Id", column => column.PrimaryKey().Identity())
.Column<string>("Var1")
.Column<int>("Var2")
.Column<int>("MyPartRecord_Id")
);
I'm not convinced by this approach, so I'd like to know if I can create the 1-N relationship using only the Infoset approach and eliminating the need to create the "fake" table in the migration, or if there is some smarter way to do it using the Record approach.
UPDATE
I tried to use the infoset approach but I can't make it work. I changed my files like this:
**Model**
public class MyPart : ContentPart
{
public IList<OtherRecord> MyList {
get { return this.Retrieve(x => x.MyList, new List<OtherRecord>()); }
}
**Migration**
SchemaBuilder.CreateTable("OtherRecord", table => table
.Column<int>("Id", column => column.PrimaryKey().Identity())
.Column<string>("Var1")
.Column<int>("Var2")
.Column<int>("MyPart_Id")
);
But when the driver calls the get method I always receive an empty list, so I don't think that the relations are built properly.

How to return the ID of each record after insert using EF

I have a data class with nested Lists. A simplified example (not real code):
public class Movie
{
public Int32 TVIndex;
public string MovieName;
public string MovieRating;
public string MovieRuntime;
public List<Actor> MovieActors;
public List<MovieArt> MovieImages;
}
public class Actor
{
public string ActorName;
public string ActorRole;
}
public class MovieArt
{
public string ImagePath;
}
List<Movie> myMovies = new List<Movie>();
This may contain many movies and each movie may contain many Actors and many MovieArt.
I pass this list to a web service that inserts the records into tables using DataEntityModel.
Something Like..
public void InsertMovies(List<Movie> request)
{
using (TransactionScope scope = new TransactionScope())
{
using (MyMovieStorageEntities DbContext = new MyMovieStorageEntities())
{
foreach (Movie m in request)
{
Movies movie = new Movies();
movie.MovieName = m.MovieName;
movie.MovieRating = m.MovieRating;
movie.MovieRuntime = m.MovieRuntime;
DbContext.DBMovies.Add(movie);
foreach (Actor a in m.MovieActors)
{
Actors actor = new Actors();
actor.ActorName = a.ActorName;
actor.ActorRole = a.ActorRole;
DbContext.DBActors.Add(actor);
}
for (MovieArt i in m.MovieImages)
{
MovieArt artwork = new MovieArt();
artwork.FileName = i.FileName;
DbContext.DBMovieArt.Add(artwork);
}
}
DbContext.SaveChanges();
}
}
}
In the Movie table is an Identity column and I need to return the identity for each inserted record for use in the client app.
I am new to DataEntity and not really sure where to start. All the searching I have done hasn't really helped. Any pointers?
Don't lose the references to your entity framework objects (e.g. add them to a list), and after SaveChanges(), the object property containing the primary key will be automatically updated with the ID.
See here for more info
How can I get Id of inserted entity in Entity framework?
EF automatically returns the ID. movie.ID gives you the last inserted ID you can use it or return it from your method to use somewhere else.

C# dynamic casts when using Entity Framework for insertion

I have a database which contains tables of mapping values for a variety of objects.
For example the Colour table contains BLK > Black, BLU > BLUE, ORA > ORANGE etc.. and the CarType table contains 4DH > Four-door hatchback, 4WD > Four wheel drive etc...
I'm using Entity Framework code-first so I have a context set up something like this.
public class MappingContext : DbContext
{
public MappingContext ()
: base("Mappings")
{
}
public DbSet<Colour> ColourMappings { get; set; }
public DbSet<CarType> CarTypeMappings { get; set; }
}
Every object that relates to each table in my Mapping database inherits from a base class like so:
public class Mapping
{
public int Id { get; set; }
public string OrignalValue { get; set; }
public string MappedValue { get; set; }
}
public class CarType : Mapping{}
public class Colour : Mapping{}
Now what I want to do is read these mappings in from an XML file filled with "Templates" which contain the mappings and insert them in the DB.
I have the following method to do this:
public void InsertMappings(XDocument xml, string templateName, Type typeToInsert)
{
// Here I find the relevent mappings
using (var repo = new Repository())
{
var mapppings = mappings.Select(mapping => new Mapping
{
MappedValue = mapping.Value,
OrignalValue = GetCode(mapping)
});
foreach (var mapping in mapppings.ToList())
{
var map = (typeToInsert)mapping // << This line will not compile
repo.Insert(map);
}
repo.Save();
}
}
This will not complie as it doesnt recognise the attempted cast "(typeToInsert)mapping".
So basically what I need to know is how to I cast this Mapping object to a Specific Mapping object when it comes to inserting it into the db? Or any suggestions for a better way of doing this!
From the looks of it you are trying to cast an instance of Mapping as a CarType or Colour which won't work because Mapping doesn't know anything about those types as it's the base class.
Your code would need to create an instance of the concrete type i.e. typeToInsert and cast it as Mapping. You could do something like:
public void InsertMappings(XDocument xml, string templateName, Type typeToInsert)
{
// Here I find the relevent mappings
using (var repo = new Repository())
{
foreach (var m in mappings)
{
// XML -> Entity
var mapping = (typeToInsert)Activator.CreateInstance(typeToInsert);
(mapping as Mapping).MappedValue = m.Value;
(mapping as Mapping).OriginalValue = GetCode(m);
// Update database
repo.Insert(mapping);
}
repo.Save();
}
}
You should probably be making use of generics here as well, you could refactor your method to look like:
public void InsertMappings<T>(XDocument xml, string templateName)
{
// Here I find the relevent mappings
using (var repo = new Repository())
{
foreach (var m in mappings)
{
// XML -> Entity
var mapping = (T)Activator.CreateInstance(typeof(T));
(mapping as Mapping).MappedValue = m.Value;
(mapping as Mapping).OriginalValue = GetCode(m);
// Update database
repo.Insert(mapping);
}
repo.Save();
}
}
Usage
InsertMappings<CarType>(xmlDoc, "CarTemplate");
InsertMappings<Colour>(xmlDoc, "ColourTemplate");

How to save class with master/detail

This is my class Record which can be master/detail/details
Records of teachers and their students
class Record
{
public string RecorNo { get; set; }
private List<Teacher> _Student = new List<Teacher>();
public List<Teacher> Teacher
{
get { return _Teacher; }
set { _Teacher=value; }
}
}
class Teacher
{
private string _TeacherName;
public Teacher(string teachername)
{
_TeacherName = teachername;
}
public string TeacherName
{
get { return _TeacherName; }
set { _TeacherName = value; }
}
private List<Students> _students = new List<Students>();
public List<Students> Students
{
get { return _students;}
set { _students = value;}
}
}
class Details
{
private string _studentname;
public Details(string studentsname)
{
_studentsname = studentsname;
}
I have bind the Teacher/Student in 2 datagridview like so:
TeacherBindingSource.DataSource = record.Teacher;
StudentBindingSourcce.DataSource = TeacherBindingSource;
StudentBindingSourcce.DataMember = "Student";
dataGridView1.DataSource =TeacherBindingSource;
dataGridView2.DataSource = StudentBindingSourcce;
Now Im lost how to save this record.
How can I save this to the database?
I want to have a.
record.Save(); method that will save the whole object.
I can iterate through the list and insert it one by one but the record may be
existing already in the database so i had to know which record is added, updated or deleted?
Also how to make a method that will fill the teacher/student list like:
records.LoadTeachers();
records.Teacher("Smith").LoadStudents();
you might want to have a look at ORM (object-relational mapping) libraries like Microsofts Entity Framework or (my favorite) NHibernate. Their sole reason to exist is to make persisting objects to a relational database easier. They can create new rows, can track object updates and relations between objects. You could create the logic needed to persist your objects on your own, but the established ORM libraries are matured, widely used and probably much better designed than anything a single developer could come up with in a reasonable amount of time.

Categories

Resources