LINQ to SQL relationships using compact edition - sql

I'm having a problem trying to use linq to sql to create a relationship between my two database entities. I've been following this basic Microsoft guide but without getting it to work.
I'm trying to store a tree-like structure. The ConfigUnit class contains information about a category of items and stores the tree name as well as the root node.
storeItem contains the information for each node in that tree.
[Table(Name = "ConfigUnit")]
public class ConfigUnit
{
[Column(IsPrimaryKey = true, IsDbGenerated = true)]
public int cuID;
...
private EntitySet<storeItem> _rootNode;
[Association(Storage = "_rootNode", ThisKey = "cuID", OtherKey = "cuID")]
public EntitySet<storeItem> rootNode
{
get
{
return _rootNode;
}
set
{
_rootNode = value;
}
}
}
And the storeItem class:
[Table(Name = "storeItem")]
public class storeItem
{
[Column(IsPrimaryKey = true, IsDbGenerated = true)]
public int siID;
[Column]
public int cuID;
private EntityRef<ConfigUnit> _CU;
[Association(Storage = "_CU", ThisKey = "cuID")]
public ConfigUnit ConfigUnit
{
get { return this._CU.Entity; }
set { this._CU.Entity = value; }
}
...
}
However when I run the code and the database is created using DataContext.CreateDatabase() the relation does not seem to be created. The ConfigUnit table has no relationship column and no relationship values seem to be stored when I query the database afterwards.
What am I missing? I'm running a near-identical relationship as the linked Microsoft article.
Thanks for your time!
EDIT:
The generated SQL code is simply this:
CREATE TABLE [ConfigUnit](
[cuID] Int NOT NULL IDENTITY,
[topID] Int NOT NULL,
CONSTRAINT [PK_ConfigUnit] PRIMARY KEY ([cuID])
)
Ie no relation is generated at all.
The only other related problem I could find was people who were missing primary keys on their entities, which I am not.
EDIT 2:
I tried using a full SQL server instead of a Compact edition one. Still the same result.

Related

Looping through Dapper.Net query results without the use of models

I am still new to C# and I am struggling to find a solution to my problem. My SQL dapper query returns a table (based on my understanding though it is not really a table if it is IEnumerable unlike what I am use to working with ADO and recordsets) with three columns col1, col2, and col3 and has multiple rows. I need to loop through this query result for each row and test the values (ie, a foreach loop where I check row(0).field1=5, row(1).field1 = 5 for each row, etc) do what I need to do. This seems so basic but I all the dapper tutorials I see do not show examples for this and if they do they seem to utilize class objects rather than accessing the results directly (if thats even possible or do you have to map the results to a model?) My code is as follows:
String query = "exec dbo.storeProcedure #jsonData, #mainDocJSON, #supportingDocsJSON";
IEnumerable queryResult;
using (var connection = new SqlConnection(connectionString))
{
queryResult = connection.Query(query, new { jsonData = jsonData, mainDocJSON = mainDocJSON, supportingDocsJSON = supportingDocsJSON });
}
I also end up returning IEnumerable results from the controller this code resides in so I send it back to the user in JSON using the following.
return Ok(queryResult);
connection.Query return a IEnumerable, why dont we create a class to map the set from ? Dapper is a micro-ORM, but still... ORM.
For ex: Your table return 3 column Id, Name, CreatedDate.
// declare a class to map the result first
public class ResultHolderDto
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime CreatedDate { get; set; }
}
// query somewhere
// This will return IEnumerable<ResultHolderDto>, feel free to play around as normal
var queryResult = await connection.QueryAsync<ResultHolderDto>(query, new { jsonData = jsonData, mainDocJSON = mainDocJSON, supportingDocsJSON = supportingDocsJSON });
foreach(var item in queryResult)
{
var col1Value = queryResult.Id;
var col2Value = queryResult.Name;
var col3Value = queryResult.CreatedDate;
// Then do something with col1Value, col2Value, col3Value...
}

Entity Framework Core PostgreSQL Linq to SQL Json Field

Sorry for my bad English
Note: Npgsql does not supporting json query with EF Core Mapping directly
As yo know PostgreSQL is supporting json and hybrid data. And if you want to query, you can use syntax like this
select *
from archive.ArchiveObject
where FileInfo->>'FileName' = 'name.ext';
For my question here is sample table and sample class
Table:
CREATE TABLE archive."ArchiveObject"
(
"Id" bigint NOT NULL DEFAULT nextval('archive."ArchiveObject_Id_seq"'::regclass),
"Uid" uuid NOT NULL,
...
...
...
"FileInfo" json,
...
...
)
and here is C# classes for this table
[Table("ArchiveObject", Schema = CX.Schema)]
public partial class ArchiveObject
{
[Key]
public long Id { get; set; }
[Required]
public Guid Uid { get; set; }
[Column("FileInfo", TypeName = "json")]
public string _FileInfo { get; set; }
[NotMapped]
public ObjectFileInfo FileInfo
{
//get;set;
get
{
return string.IsNullOrEmpty(_FileInfo) ? null : JsonConvert.DeserializeObject<ObjectFileInfo>(_FileInfo);
}
set
{
_FileInfo = value != null ? JsonConvert.SerializeObject(value) : null;
}
}
...
...
}
OK. When I used linq or lambda like this no problem
var query = from x in db.ArchiveObject
where x.Id < 65535
select x;
generates
"SELECT x."Id", x."CreatedAt", x."KellePaca", x."Tags", x."Uid", x."UpdateHistory", x."VirtualPathId", x."FileInfo"
FROM archive."ArchiveObject" AS x
WHERE (x."Id" < 65535)
but I can't query json area named FileInfo field like this.
select *
from archive.ArchiveObject
where FileInfo->>'FileName' = 'name.ext' ;
because of EF doesn't have any Provider for convert to sql "file_info->>'FileName'"
I'm searched and found this terms
ExpressionVisitor, QueryProvider, BinaryExpression,
ParameterExpression, ObjectQuery.ToTraceString, Expression
and also found this documents and answers
How to convert an expression tree to a partial SQL query?
https://www.codeproject.com/Articles/22819/How-To-LINQ-To-SQL-Part-III
I'm believe can be generate [ file_info->>'FileName' = 'name.ext' ; ] query using this documents. But I can't get it.
public static IQueryable<ArchiveObject> FileNameEquals(this IQueryable<ArchiveObject> source, string s)
{
....
}
or what?
Please can you show me simple example.

Using NHibernate to retrieve a set of records with a single query and composite keys

I am using Nhibernate and it's mapping-by-code flavour to retrieve a set of records using a list of composite keys. I am using composite keys like this:
public class PersonAccountKey : IKey
{
public virtual string PersonId { get; set; }
public virtual string AccountNo{ get; set; }
public override bool Equals(object obj)
{
if (obj == null)
return false;
var t = obj as PersonKey;
if (t == null)
return false;
if (PersonId == t.PersonId && AccountNo == t.AccountNo)
return true;
return false;
}
public override int GetHashCode()
{
return (PersonId).GetHashCode() + "|" + (AccountNo).GetHashCode();
}
}
With a list of PersonAccountKey objects, I am trying to get NHibernate to send a single query down to database. I would imagine the query would look like this:
Select PersonId, AccountNo, AccountNickName
From PersonAccount
Where (PersonId = '11' and AccountNo = '10001111')
or (PersonId = '22' and AccountNo = '10001150')
I'm not sure how to achieve this? I tried to use Criteria with composite keys, but I don't think it was meant to be used together. I am trying now Linq 2 NHibernate but am not really getting anywhere either.
Ideally, I would like a method that would take a IEnumerable of keys such as Session.Get<T>(IEnumerable<object>), but this doesn't exist from my searching.
Is this possible out of the box with NHibernate?
Cheers.
Unlike scalar Ids, there's no direct support to use use a list of composite keys as a query parameter. This is another example of why composite keys should be avoided.
Here's an easy workaround:
IEnumerable<PersonAccountKey> keys = GetKeys();
var query = session.CreateCriteria<PersonAccount>();
var keyCriterion = Restrictions.Disjunction();
foreach (var key in keys)
keyCriterion.Add(Restrictions.Eq("id", key));
query.Add(keyCriterion);
var result = query.List<PersonAccount>();
There is an article on MSDN about Enumerable.Contains which does what I think you want.
I don't know if NHibernate's linq supports this at all. It may refuse to do it with an exception.

Many to many relationship using Fluent Nhibernate Automapping

We are facing problem applying many-to-many relationship using fluent nhibernate automapping.
The simplified form of domain model are as follows:
public class Group
{
private readonly IList<Recipient> _recipients = new List<Recipient>();
public virtual IList<Recipient> Recipients
{
get { return _recipients; }
}
}
public class Recipient
{
private readonly IList<Group> _groups = new List<Group>();
public virtual IList<Group> Groups
{
get { return _ groups; }
}
}
As the code above describes that Group and Recipient are having many-to-many relationship.
We are using automapping feature of fluent nhibernate to map our domain model with database. So, we needed to use Convention for automapping.
Following is code we used for many to many convention:-
public class ManyToManyConvention : IHasManyToManyConvention
{
#region IConvention<IManyToManyCollectionInspector,IManyToManyCollectionInstance> Members
public void Apply(FluentNHibernate.Conventions.Instances.IManyToManyCollectionInstance instance)
{
if (instance.OtherSide == null)
{
instance.Table(
string.Format(
"{0}To{1}",
instance.EntityType.Name + "_Id",
instance.ChildType.Name + "_Id"));
}
else
{
instance.Inverse();
}
instance.Cascade.All();
}
#endregion
}
I found this solution here :
http://blog.vuscode.com/malovicn/archive/2009/11/04/fluent-nhibernate-samples-auto-mapping-part-12.aspx#Many%20to%20Many%20convention
But in above code while debugging both time for Recipients-> Groups and for Groups->Recipients instance.OtherSide is coming not null. The assumption was 1st time instance.OtherSide will be not null and second time it will be null as relationship is applied on one side so we will just apply inverse to that.
So it’s creating 2 mapping tables which are same.
It is load to database to have 2 tables of same schema. Even when I try to save our domain model to database using many to many relationship. It’s saving only 1 side i.e it saves Recipients in the Groups , But not saving Groups in Recipients.In database also it is having entry in only one mapping table not in both.
So , the question is Are we doing the right thing? If not then how to do it.
you could take inverse itself as a criteria
public void Apply(IManyToManyCollectionInstance instance)
{
Debug.Assert(instance.OtherSide != null);
// Hack: the cast is nessesary because the compiler tries to take the Method and not the property
if (((IManyToManyCollectionInspector)instance.OtherSide).Inverse)
{
instance.Table(
string.Format(
"{0}To{1}",
instance.EntityType.Name + "_Id",
instance.ChildType.Name + "_Id"));
}
else
{
instance.Inverse();
}
instance.Cascade.All();
}

How to increment ID before any insert with NHibernate

It looks like NH gets MAX(ID) only once, at first insert and then stores this value internally, this causes me some problems when other processes inserts data. Then I have not actual ID and duplicate key exception is thrown.
Lets imagine we have table Cats
CREATE TABLE Cats(ID int, Name varchar(25))
Then we have corresponding mapping done with FluentNhibernate
public class CatMap : ClassMap<Cat>
{
public CatMap()
{
Id(m=>m.ID).GeneratedBy.Increment();
Map(m=>.Name);
}
}
All I want to achieve is to insert my Cat records with ID's generated by NHibernate using SELECT MAX(ID) FROM Cats before any insert. Executing Session.Flush after any commit dosnt work. I'v done some investigation using SQL Server profiler, and this sql stetement is executed only once (at first insert) - other inserts doesnt force to retreive actual MAX(ID). I know that other algorithms like HiLo are better, but I cant replace it.
As you found out, the NHibernate Increment id generator was not intended for use in a multi-user environment. You state that using a HiLo generator is not an option so you're left with these options:
use the Native generator and change the id column to use the database supported identity mechanism
use the Assigned generator and write code to determine the next valid id
create a Custom generator where you implement the IIdentifierGenerator interface to do what you need
Below is sample code for a custom generator that uses a generalized proc to get an ID for a given table. The main issue with this approach is that you must wrap the code in something like a Unit of Work pattern to ensure the 'select max(id) ..." and the insert are covered by the same database transaction. The IIdentifierGenerator link has the XML mapping you need to wire up this custom generator.
using System;
using System.Collections.Generic;
using System.Data;
using NHibernate.Dialect;
using NHibernate.Engine;
using NHibernate.Id;
using NHibernate.Persister.Entity;
using NHibernate.Type;
namespace YourCompany.Stuff
{
public class IdGenerator : IIdentifierGenerator, IConfigurable
{
private string _tableName;
// The "select max(id) ..." query will go into this proc:
private const string DefaultProcedureName = "dbo.getId";
public string ProcedureName { get; protected set; }
public string TableNameParameter { get; protected set; }
public string OutputParameter { get; protected set; }
public IdGenerator()
{
ProcedureName = DefaultProcedureName;
TableNameParameter = "#tableName";
OutputParameter = "#newID";
}
public object Generate(ISessionImplementor session, object obj)
{
int newId;
using (var command = session.Connection.CreateCommand())
{
var tableName = GetTableName(session, obj.GetType());
command.CommandType = CommandType.StoredProcedure;
command.CommandText = ProcedureName;
// Set input parameters
var parm = command.CreateParameter();
parm.Value = tableName;
parm.ParameterName = TableNameParameter;
parm.DbType = DbType.String;
command.Parameters.Add(parm);
// Set output parameter
var outputParameter = command.CreateParameter();
outputParameter.Direction = ParameterDirection.Output;
outputParameter.ParameterName = OutputParameter;
outputParameter.DbType = DbType.Int32;
command.Parameters.Add(outputParameter);
// Execute the stored procedure
command.ExecuteNonQuery();
var id = (IDbDataParameter)command.Parameters[OutputParameter];
newId = int.Parse(id.Value.ToString());
if (newId < 1)
throw new InvalidOperationException(
string.Format("Could not retrieve a new ID with proc {0} for table {1}",
ProcedureName,
tableName));
}
return newId;
}
public void Configure(IType type, IDictionary<string, string> parms, Dialect dialect)
{
_tableName = parms["TableName"];
}
private string GetTableName(ISessionImplementor session, Type objectType)
{
if (string.IsNullOrEmpty(_tableName))
{
//Not set by configuration, default to the mapped table of the actual type from runtime object:
var persister = (IJoinable)session.Factory.GetClassMetadata(objectType);
var qualifiedTableName = persister.TableName.Split('.');
_tableName = qualifiedTableName[qualifiedTableName.GetUpperBound(0)]; //Get last string
}
return _tableName;
}
}
}