Hello,
Consider the following object model that represents users, groups and clients (like websites, third party apps etc) that could be members of groups.
namespace Sample
{
public class abstract Principal
{
public List<Group> Groups { get; set; }
}
public sealed class Group : Principal
{
public List<Principal> Members { get; set; }
public string Name { get; set; }
}
public sealed class User : Principal
{
public string Username { get; set; }
}
public sealed class Client : Principal
{
public string Identifier { get; set; }
}
}
I mapped it using the LightSpeed designer and the resulting model looks as follows:
namespace Sample
{
using System;
using System.CodeDom.Compiler;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using Mindscape.LightSpeed;
using Mindscape.LightSpeed.Linq;
using Mindscape.LightSpeed.Validation;
[Serializable]
[GeneratedCode("LightSpeedModelGenerator", "1.0.0.0")]
[DataObject]
public class Principal : Entity<long>
{
#region Fields
[ValidateLength(0, 255)]
private string _discriminator;
#pragma warning disable 649 // "Field is never assigned to" - LightSpeed assigns these fields internally
private readonly int _lockVersion;
private readonly DateTime _createdOn;
private readonly DateTime _updatedOn;
private readonly DateTime? _deletedOn;
#pragma warning restore 649
#endregion
#region Field attribute and view names
/// <summary>
/// Identifies the Discriminator entity attribute.
/// </summary>
public const string DiscriminatorField = "Discriminator";
#endregion
#region Relationships
[ReverseAssociation("Principal")]
private readonly EntityCollection<GroupMember> _groupMembers = new EntityCollection<GroupMember>();
private ThroughAssociation<GroupMember, Group> _groups;
#endregion
#region Properties
[DebuggerNonUserCode]
public EntityCollection<GroupMember> GroupMembers
{
get
{
return Get(_groupMembers);
}
}
[DebuggerNonUserCode]
public ThroughAssociation<GroupMember, Group> Groups
{
get
{
if (_groups == null)
{
_groups = new ThroughAssociation<GroupMember, Group>(_groupMembers);
}
return Get(_groups);
}
}
[DebuggerNonUserCode]
public string Discriminator
{
get
{
return Get(ref _discriminator, "Discriminator");
}
set
{
Set(ref _discriminator, value, "Discriminator");
}
}
/// <summary>
/// Gets the row version number for concurrency checking
/// </summary>
[DebuggerNonUserCode]
public int LockVersion
{
get
{
return _lockVersion;
}
}
/// <summary>
/// Gets the time the entity was created
/// </summary>
[DebuggerNonUserCode]
public DateTime CreatedOn
{
get
{
return _createdOn;
}
}
/// <summary>
/// Gets the time the entity was last updated
/// </summary>
[DebuggerNonUserCode]
public DateTime UpdatedOn
{
get
{
return _updatedOn;
}
}
/// <summary>
/// Gets the time the entity was soft-deleted
/// </summary>
[DebuggerNonUserCode]
public DateTime? DeletedOn
{
get
{
return _deletedOn;
}
}
#endregion
}
[Serializable]
[GeneratedCode("LightSpeedModelGenerator", "1.0.0.0")]
[DataObject]
[Discriminator(Attribute = "Discriminator", Value = "group")]
public class Group : Principal
{
#region Fields
[ValidateLength(0, 255)]
private string _name;
#endregion
#region Field attribute and view names
/// <summary>
/// Identifies the Name entity attribute.
/// </summary>
public const string NameField = "Name";
#endregion
#region Relationships
[ReverseAssociation("Group")]
private readonly EntityCollection<GroupMember> _groupMembers = new EntityCollection<GroupMember>();
private ThroughAssociation<GroupMember, Principal> _members;
#endregion
#region Properties
[DebuggerNonUserCode]
public EntityCollection<GroupMember> GroupMembers
{
get
{
return Get(_groupMembers);
}
}
[DebuggerNonUserCode]
public ThroughAssociation<GroupMember, Principal> Members
{
get
{
if (_members == null)
{
_members = new ThroughAssociation<GroupMember, Principal>(_groupMembers);
}
return Get(_members);
}
}
[DebuggerNonUserCode]
public string Name
{
get
{
return Get(ref _name, "Name");
}
set
{
Set(ref _name, value, "Name");
}
}
#endregion
}
[Serializable]
[GeneratedCode("LightSpeedModelGenerator", "1.0.0.0")]
[DataObject]
[Discriminator(Attribute = "Discriminator", Value = "user")]
public class User : Principal
{
#region Fields
[ValidateLength(0, 255)]
private string _username;
#endregion
#region Field attribute and view names
/// <summary>
/// Identifies the Username entity attribute.
/// </summary>
public const string UsernameField = "Username";
#endregion
#region Properties
[DebuggerNonUserCode]
public string Username
{
get
{
return Get(ref _username, "Username");
}
set
{
Set(ref _username, value, "Username");
}
}
#endregion
}
[Serializable]
[GeneratedCode("LightSpeedModelGenerator", "1.0.0.0")]
[DataObject]
[Discriminator(Attribute = "Discriminator", Value = "client")]
public class Client : Principal
{
#region Fields
[ValidateLength(0, 255)]
private string _identifier;
#endregion
#region Field attribute and view names
/// <summary>
/// Identifies the Identifier entity attribute.
/// </summary>
public const string IdentifierField = "Identifier";
#endregion
#region Properties
[DebuggerNonUserCode]
public string Identifier
{
get
{
return Get(ref _identifier, "Identifier");
}
set
{
Set(ref _identifier, value, "Identifier");
}
}
#endregion
}
[Serializable]
[GeneratedCode("LightSpeedModelGenerator", "1.0.0.0")]
[DataObject]
public class GroupMember : Entity<long>
{
#region Fields
private long _groupId;
private long _principalId;
#endregion
#region Field attribute and view names
/// <summary>
/// Identifies the PrincipalId entity attribute.
/// </summary>
public const string PrincipalIdField = "PrincipalId";
/// <summary>
/// Identifies the GroupId entity attribute.
/// </summary>
public const string GroupIdField = "GroupId";
#endregion
#region Relationships
[ReverseAssociation("GroupMembers")]
private readonly EntityHolder<Group> _group = new EntityHolder<Group>();
[ReverseAssociation("GroupMembers")]
private readonly EntityHolder<Principal> _principal = new EntityHolder<Principal>();
#endregion
#region Properties
[DebuggerNonUserCode]
public Principal Principal
{
get
{
return Get(_principal);
}
set
{
Set(_principal, value);
}
}
[DebuggerNonUserCode]
public Group Group
{
get
{
return Get(_group);
}
set
{
Set(_group, value);
}
}
[DebuggerNonUserCode]
public long PrincipalId
{
get
{
return Get(ref _principalId, "PrincipalId");
}
set
{
Set(ref _principalId, value, "PrincipalId");
}
}
[DebuggerNonUserCode]
public long GroupId
{
get
{
return Get(ref _groupId, "GroupId");
}
set
{
Set(ref _groupId, value, "GroupId");
}
}
#endregion
}
/// <summary>
/// Provides a strong-typed unit of work for working with the LightSpeedModel1 model.
/// </summary>
[GeneratedCode("LightSpeedModelGenerator", "1.0.0.0")]
public class LightSpeedModel1UnitOfWork : UnitOfWork
{
public IQueryable<Principal> Principals
{
get
{
return this.Query<Principal>();
}
}
public IQueryable<Group> Groups
{
get
{
return this.Query<Group>();
}
}
public IQueryable<User> Users
{
get
{
return this.Query<User>();
}
}
public IQueryable<Client> Clients
{
get
{
return this.Query<Client>();
}
}
public IQueryable<GroupMember> GroupMembers
{
get
{
return this.Query<GroupMember>();
}
}
}
}
The resulting migrations looks as follows:
[Migration("20120331103459")]
public class V1 : Migration
{
public override void Up()
{
AddKeyTable("KeyTable", null, ModelDataType.Int32, 1);
AddTable("Principals",
new Field("Id", ModelDataType.Int64, false),
new Field("Discriminator", ModelDataType.String, false).WithSize(255),
new Field("CreatedOn", ModelDataType.DateTime, false),
new Field("UpdatedOn", ModelDataType.DateTime, false),
new Field("DeletedOn", ModelDataType.DateTime, true),
new Field("LockVersion", ModelDataType.Int32, false));
AddColumn("Principals", null, "Name", ModelDataType.String, true, 255);
AddColumn("Principals", null, "Identifier", ModelDataType.String, true, 255);
AddColumn("Principals", null, "Username", ModelDataType.String, true, 255);
AddTable("GroupMembers", new Field("Id", ModelDataType.Int64, false),
new ForeignKeyField("GroupId", ModelDataType.Int64, false, "Principals", null, "Id"),
new ForeignKeyField("PrincipalId", ModelDataType.Int64, false, "Principals", null, "Id"));
}
public override void Down()
{
DropTable("Principals", null);
}
}
The following program, throws an exception with a message: "Could not determine the reverse association of [GroupMember.Principal (Principal)]"
internal class Program
{
private static void Main(string[] args)
{
try
{
var context = new LightSpeedContext
{
ConnectionString =
"server=localhost;User Id=root;Persist Security Info=True;database=t3; Password=Pa$$w0rd1",
DataProvider = DataProvider.MySql5,
PluralizeTableNames = true
};
using (var unitOfWork = context.CreateUnitOfWork())
{
var user = new User {Username = "bob"};
unitOfWork.Add(user);
var client = new Client {Identifier = "https://sample.example.com"};
unitOfWork.Add(client);
var group = new Group {Name = "Users"};
unitOfWork.Add(@group);
@group.Members.Add(user);
@group.Members.Add(client);
unitOfWork.SaveChanges();
}
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
}
}
I noticed that both Principal and Group has a property "GroupMembers". I removed the one from Group (as it should already be defined in its base class "Principal" and made the _groupMembers field protected). The error persists. I'm running LightSpeed v4.0.1095.19489, mysql Ver 14.14 Distrib 5.5.9, for Win64 (x86) and .NET 4.0. I upgraded to 4.0.1264.20228, but the problem persist.
Any suggestions?
Thanks,
Werner