Problem with selecting from database to complex type using EF Core - c#

I try to use complex type if EF Core.
my table structure
source code
User
-----------
Id (uniqueidentifier)
FirstName (nvarchar(255))
LastName (nvarchar(255))
and my class strcuture is
public class UserId
{
public Guid Value { getl }
private UserId() { }
public UserId (Guid newId) {
//check and assign
}
}
public class User
{
public UserId Id { get; }
public Name Name { get; }
private User() { }
public User(Name name) {
//.... anti corruption
}
}
public class Name
{
public string First { get; }
public string Last { get; }
private Name() { }
public Name(string firstName, string lastName)
{
//anti curruption
}
}
and here is OnModelCreating
modelBuilder.Entity<User>()
.ToTable("User");
modelBuilder.Entity<User>()
.HasKey(u => u.Id);
modelBuilder.Entity<User>()
.OwnsOne(u => u.Name, un =>
{
un.Property(x => x.First).HasColumnName("FirstName");
un.Property(x => x.Last).HasColumnName("LastName");
});
every thing is work fined when I created a User and Save to database
but when I try to read it from database. the Name property is null
but again when I use .AsNoTracking. It work fined.
(I got from exception but I can't remember how to did it again)
MyDbContext db = new MyDbContext();
var newUser = new User(name: new Name("Foo", "Fighter"));
db.Users.Add(newUser);
db.SaveChanges();
var u1 = db.Users.Take(1).First();
PrintResult("Case 1", u1); //output > Could not read Name from database
var u2 = db.Users.AsNoTracking()
.Take(1).First();
PrintResult("Case 2", u2); //output > Read Name from database success
Console.Read();
my print result method
static void PrintResult(string label, User u)
{
Console.WriteLine($"{label} >>>>>");
if (u.Name == null)
{
Console.WriteLine("Could not read Name from database");
}
else
{
Console.WriteLine("Read Name from database success");
}
}
Can someone tell me that did I do something wrong ?
Why I have to use .AsNoTracking ?

In fact the issue does not reproduce with the posted model here. But it does with the one from the link, and the difference is the type of the PK property in the model - there is no problem when using well known primitive type, but you are using custom id class - yet another DDD "sugar", but with improper/missing equality implementation.
Without implementing value semantics for your id class, EF Core will compare it by reference, thus not finding the "owner PK" needed by the owned entity type. The no tracking queries have no such search, that's why it is "working".
The correct action is to implement value equality semantics in your UserId class (used as a type of User.Id property)
public class UserId
{
public Guid Value { get; }
protected UserId() { }
public UserId(Guid id)
{
if (id == Guid.Empty)
throw new ArgumentException("UserId must not be Empty");
Value = id;
}
}
for instance by adding
public override int GetHashCode() => Value.GetHashCode();
public override bool Equals(object obj) => obj is UserId other && Value == other.Value;

Related

How to access the property values when DbEntityEntry.Member("JoinTable").CurrentValue return an Object?

I was doing a audit log setup on dbcontext.cs and my next step is to call the primary key values of join table in order log the parent ID in the audit log table.
And now I am working on the calling property of a table called "TAXONOMY" which is a join table of "CONSERVATIONSTATUS" with many to many relationship called "CONSERVATIONSTATUSLINK".
Here is the structure of table generated in edmx:
public partial class CONSERVATIONSTATU
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public CONSERVATIONSTATU()
{
this.TAXONOMies = new HashSet<TAXONOMY>();
}
public int SPECIESCONSERVATIONID { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<TAXONOMY> TAXONOMies { get; set; }
}
public partial class TAXONOMY
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public TAXONOMY()
{
this.CONSERVATIONSTATUS = new HashSet<CONSERVATIONSTATU>();
}
public int TAXONID { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<CONSERVATIONSTATU> CONSERVATIONSTATUS { get; set; }
}
}
When it post and go through save changes through dbcontext.cs
// This is overridden to prevent someone from calling SaveChanges without specifying the user making the change
public override int SaveChanges()
{
// Get all Added/Deleted/Modified entities (not Unmodified or Detached)
foreach (var ent in this.ChangeTracker.Entries().Where(p => p.State == System.Data.Entity.EntityState.Added || p.State == System.Data.Entity.EntityState.Deleted || p.State == System.Data.Entity.EntityState.Modified))
{
// For each changed record, get the audit record entries and add them
foreach (var x in GetAuditRecordsForChange(ent, userId))
{
this.AUDITLOGs.Add(x);
}
}
// Call the original SaveChanges(), which will save both the changes made and the audit records
return base.SaveChanges();
}
private List<AUDITLOG> GetAuditRecordsForChange(DbEntityEntry dbEntry, string userId)
{
var testingvalue = dbEntry.Member("TAXONOMies").CurrentValue; //Count = 1
var testingvalue1 = dbEntry.Member("TAXONOMies").CurrentValue.ToString(); //"System.Collections.Generic.HashSet`1[oraFresh.TAXONOMY]"
var testingvalue2 = dbEntry.Member("TAXONOMies").CurrentValue.GetType(); //{Name = "HashSet`1" FullName = "System.Collections.Generic.HashSet`1[[oraFresh.TAXONOMY, oraFresh, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]"}
}
In the variable testingvalue defined in GetAuditRecordsForChange, when I add watch to it but I can't access the "TAXONID":
Name Value Type
testingvalue Count = 1 object{System.Collections.Generic.HashSet<db.TAXONOMY>}
[0] {db.TAXONOMY} db.TAXONOMY
TAXONID 300000 int
Raw View
Comparer {System.Collections.Generic.ObjectEqualityComparer<db.TAXONOMY>} System.Collections.Generic.IEqualityComparer<db.TAXONOMY>{System.Collections.Generic.ObjectEqualityComparer<db.TAXONOMY>}
Count 1 int
Could someone provide suggestion do I approach wrong path to call property values or it is possible to access it please? Thanks.

Entity Framework Oracle 00932 error "expected - got NCLOB"

This is literally driving me nuts. I created a table, as you can see from the image below:
And this is the fluent configuration for the User entity:
//this is the User entity model class
public class User
{
public long Id { get; set; }
public string EmailAddress { get; set; }
public string HashedPassword { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int IsAdmin { get; set; }
}
public class UserEntityModelBuilder : IEntityModelBuilder
{
public void Build(DbModelBuilder modelBuilder)
{
var config = modelBuilder.Entity<User>();
config.Property(e => e.Id).HasColumnName("ID");
config.Property(e => e.EmailAddress).HasColumnName("EMAIL");
config.Property(e => e.HashedPassword).HasColumnName("PASSWORD");
config.Property(e => e.FirstName).HasColumnName("NOME");
config.Property(e => e.LastName).HasColumnName("COGNOME");
config.Property(e => e.IsAdmin).HasColumnName("ADMIN");
config.ToTable("UTENTIEROGAZIONE").HasKey(e => e.Id);
}
}
Now I'm running a really simple LINQ statement just to test the login and it's throwing this weird exception message:
The fact is I'm using just VARCHAR2 fields, I can't understand what the hell is going on...any clues?
EDIT (added DbContext initialization):
public BarcodePrinterDbContext(IConnectionStringProvider connectionStringProvider,
IEnumerable<IEntityModelBuilder> entityModelBuilders)
: base(connectionStringProvider.GetConnectionString())
{
_entityModelBuilders = entityModelBuilders ??
throw new ArgumentNullException(nameof(entityModelBuilders));
Database.SetInitializer(
new NullDatabaseInitializer<BarcodePrinterDbContext>());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//setting the schema for Oracle DB
SchemaSetup.SetupSchema(modelBuilder);
//registering all entities fluent configurations on the model builder
foreach (var entityModelBuilder in _entityModelBuilders)
entityModelBuilder.Build(modelBuilder);
/*
* I googled out something on wrong mappings on string types,
* so I tried to set all string fields to a maximum of 2000
* characters, unfortunately with no success.
*/
modelBuilder
.Properties()
.Where(p => p.PropertyType == typeof(string) &&
p.GetCustomAttributes(typeof(MaxLengthAttribute), false).Length == 0)
.Configure(p => p.HasMaxLength(2000));
}
FYI it was all about the target schema setting: I was injecting an old SchemaSetup object with a different schema instead of "IMPEGNATIVE". Unfortunately I was able to run the query over it 'cause I had all sorts of privileges set on the db user, thus the error (the table in this other schema actually had NCLOB fields).
So, always check your target schema!

Fluent hHibernate One-To-Many set value in blank

first of all, sorry for my bad English
I have the following entities
public class User
{
public virtual int Id { get; set; }
public virtual Application Application { get; set; }
public User ()
{
Application = new Application ();
}
}
UserMap
public class UserMap : ClassMap<User>
{
public UserMap ()
{
Table ("Users");
Id (p => p.Id);
References (x => x.Application).Cascade.SaveUpdate ();
}
}
Application
public class Application
{
public virtual int Id { get; set; }
public virtual string ApplicationName { get; set; }
}
ApplicationMap
public ApplicationMap ()
{
Table ("Applications");
Id (x => x.Id);
Map (x => x.ApplicationName);
}
I received this json
{
"Application": 1,
}
and save the object this way
var user = new User();
user.Application.Id = Cast.To<int>(userModel.Application);
userService.Add(user);
userService.Commit();
Correctly records the data in the table "users" but left blank, the "ApplicationName" table field "application"
I think the error is in this line (user.Application.Id = Cast.To (userModel.Application);)
because I did not set  "ApplicationName" field
  but if I get the id as a parameter, I will need to get the application object by id, and assign the user object?.
thank you very much
You are right, conversion from ID into Entity (Applicaton) will require call to data layer and its operation GetById().
session.Load<Application>(id)
In cases, that we can be sure, that the passed Application ID exists, NHibernate has a dedicated way how to convert ID into its ENTITY. It is a Load() method, which does NOT hit the DB, just creates a proxy with provided ID, and the operation will succeed:
var user = new User();
var applicationId = Cast.To<int>(userModel.Application);
// behind is session.Load<Aplication>(applicaitonId)
var application = applicationService.Load(applicationId);
user.Application = application;
userService.Add(user);
session.Get<Application>(id)
The alternative is a Get() method, which always loads the instance by ID, i.e. hits the DB. If the ID does not match any ID, null is returned. Advantage in this scenario is, that we can even change the referenced ApplicaitonName (if application exists)
var user = new User();
var applicationId = Cast.To<int>(userModel.Application);
// behind is session.Get<Aplication>(applicaitonId)
var application = applicationService.GetById(applicationId);
if(application !== null)
{
application.ApplicationName = ... // here we can even change that;
user.Application = application;
}
cascade for new.
In case we would recieve brand new object
{
"Application": { "ApplicaitonName" : ... }
}
We can create one, and because of Cascading setting above, it will work as well
user.Application = new Appliation
{
ApplicationName = ...,
}

C# Entity Framework with linq returns null reference

I have a problem with entity framework in C#.
I have 2 entities, User and UserRole. They are bond by relationships User *->1 UserRole
Whenever I use this query in a function:
User user = context.User.Where(i => i.id == id).FirstOrDefault();
return user.UserRole.accessLevel;
The query returns user, but UserRole is null. The User table has roleId which is related to id of UserRole, and the value of roleId when debugging is correct, although UserRole entity is null. This is strange as it never happened before...
I already made sure that my relationships in model and database are correct. I have correct rows added to database.
EDIT:
Sorry, I should've mentioned I use custom testable database controller:
public class DBController : IUnitOfWork
{
readonly ObjectContext context;
const string ConnectionStringName = "MarketPlaceDBEntities";
public DBController()
{
var connectionString =
ConfigurationManager
.ConnectionStrings[ConnectionStringName]
.ConnectionString;
context = new ObjectContext(connectionString);
}
public void Commit()
{
context.SaveChanges();
}
public IObjectSet<Category> Category
{
get { return context.CreateObjectSet<Category>(); }
}
public IObjectSet<ItemComment> ItemComment
{
get { return context.CreateObjectSet<ItemComment>(); }
}
public IObjectSet<ItemRating> ItemRating
{
get { return context.CreateObjectSet<ItemRating>(); }
}
public IObjectSet<Item> Item
{
get { return context.CreateObjectSet<Item>(); }
}
public IObjectSet<ItemSale> ItemSale
{
get { return context.CreateObjectSet<ItemSale>(); }
}
public IObjectSet<ItemScreenshot> ItemScreenshot
{
get { return context.CreateObjectSet<ItemScreenshot>(); }
}
public IObjectSet<UserRole> UserRole
{
get { return context.CreateObjectSet<UserRole>(); }
}
public IObjectSet<User> User
{
get { return context.CreateObjectSet<User>(); }
}
}
And I do operations via it. Maybe it has to do something with my prob.
interface IUnitOfWork
{
IObjectSet<Category> Category { get; }
IObjectSet<ItemComment> ItemComment { get; }
IObjectSet<ItemRating> ItemRating { get; }
IObjectSet<Item> Item { get; }
IObjectSet<ItemSale> ItemSale { get; }
IObjectSet<ItemScreenshot> ItemScreenshot { get; }
IObjectSet<UserRole> UserRole { get; }
IObjectSet<User> User { get; }
void Commit();
}
I had this whole thing working before, but don't know why it went wrong..
EDIT2:
Solved! Thanks RicoSuter.
Enabling lazy loading in constructor of my db controller solved the problem. I thought it was already enabled, because it was set to true in database model, but it looks like that when creating a new context, you have to enable it manually again.
public DBController()
{
var connectionString =
ConfigurationManager
.ConnectionStrings[ConnectionStringName]
.ConnectionString;
context = new ObjectContext(connectionString);
context.ContextOptions.LazyLoadingEnabled = true;
}
try to eagerly load UserRole (join):
context.User.Include("UserRole").Where(i => i.id == id).FirstOrDefault();
or enable lazy loading first:
context.ContextOptions.LazyLoadingEnabled = true;
context.User.Where(i => i.id == id).FirstOrDefault();
otherwise there is no relation to a UserRole in your database...
Try this
User user = context.User.Where(i => i.id == id).FirstOrDefault();
return user==null?null:user.UserRole.accessLevel;
Most simply u can do this:
UserRole user = context.User.Where(i => i.id == id).Select(i => i.UserRole);
return user.accessLevel;
Edit:
Assuming you already have a relation between User and UserRole
User user = context.User.Where(i => i.id == id).FirstOrDefault();
UserRole role = context.UserRole.Where(i => user.Contains(i.id)).FirstOrDefault();
return role.accessLevel;
Assuming you dont have a relation between User and UserRole
int roleid = Convert.ToInt32(context.User.Where(i => i.id == id).Select(i => i.roleid));
UserRole role = context.UserRole.Where(i => i.id == roleid).FirstOrDefault();
return role.accessLevel;
Also if you have relation but cant see UserRole under User than try adding this to your model
public IDisposable User()
{
YourDataContext context = new YourDataContext();
Type ct = context.User.GetType();
return (IDisposable)(ct);
}

How to set NHibernate mappings for 2 entities which share data that is stored in another table?

Given is the following class hierarchy:
Class Diagram http://img535.imageshack.us/img535/4802/personusermanager.jpg
Additional info:
The Person class is not abstract.
A person could be a User, a Manager or something else that implements the IPerson interface.
The Person class must not have any knowledge about its child classes.
A child class could reside in another assembly.
It is also possible that a Person is a User and a Manager as wel. In that case the UserRepository must return a User object for the given PersonId and the ManagerRepository must return a Manager for the same PersonId.
It must also be possible to get the Person (base) part for all objects that implement the IPerson interface via a PersonRepository.
How can this be mapped within NHibernate?
We are using FluentNHibernate 1.2 and NHibernate 3.1.
In our current situation each class has its own table. So we have a Person table, a User table and a Manager table.
I have already tried the following options without success:
Mapping this with inheritence mapping (one table per subclass);
A join with Person in the mapping for User and Manager (without inheritance mapping);
A HasOne mapping with Person in the User and Manager mapping (without the join and inheritance mapping);
As you've no doubt discovered, it is easy enough to map inheritance like this: Person -> User, or Person -> Manager, or Person -> Manager -> User (or alternately, Person -> Manager -> User).
NHibernate does not allow you to promote/demote to or from a subclass. You'd have to run native SQL to promote or demote.
However, if you followed my initial "map" of your inheritance, you should have had an epiphany that using subclasses for what you are trying to do is an inappropriate solution for what you are trying to model. And that's only with two subclasses! What happens when you add more roles?
What you have is a Person, who can be a member of any number of roles, where roles are extensible. Consider this solution (Source on github: https://github.com/HackedByChinese/NHibernateComposition):
(Assume we have an Entity abstract class which handles equality, where to objects of the same type with the same ID are considered equal)
Project: Models
public class Person : Entity, IPerson
{
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual IList<Role> Roles { get; protected set; }
public Person()
{
Roles = new List<Role>();
}
public virtual void AddRole(Role role)
{
if (Roles.Contains(role)) return;
role.Person = this;
Roles.Add(role);
}
public virtual void RemoveRole(Role role)
{
if (!Roles.Contains(role)) return;
role.Person = null;
Roles.Remove(role);
}
}
public interface IPerson
{
string FirstName { get; set; }
string LastName { get; set; }
Int32 Id { get; }
}
public abstract class Role : Entity
{
public virtual Person Person { get; set; }
public virtual string RoleName { get; protected set; }
}
public class User : Role
{
public virtual string LoginName { get; set; }
public virtual string Password { get; set; }
}
Project: Models.B
public class Manager : Role
{
public virtual string Division { get; set; }
public virtual string Status { get; set; }
}
Project: Models.Impl
I placed fluent mappings for both projects into one to save time. There could easily be separate mapping assemblies for Models and Models.B
public class PersonMap : ClassMap<Person>
{
public PersonMap()
{
Id(c => c.Id)
.GeneratedBy.HiLo("100");
Map(c => c.FirstName);
Map(c => c.LastName);
HasMany(c => c.Roles)
.Inverse()
.Cascade.AllDeleteOrphan();
}
}
public class RoleMap : ClassMap<Role>
{
public RoleMap()
{
Id(c => c.Id)
.GeneratedBy.HiLo("100");
DiscriminateSubClassesOnColumn<string>("RoleName");
References(c => c.Person);
}
}
public class UserMap : SubclassMap<User>
{
public UserMap()
{
DiscriminatorValue("User");
Join("User", joined =>
{
joined.Map(c => c.LoginName);
joined.Map(c => c.Password);
});
}
}
Project: Models.Impl.Tests
[TestFixture]
public class MappingTests
{
private ISessionFactory _factory;
#region Setup/Teardown for fixture
[TestFixtureSetUp]
public void SetUpFixture()
{
if (File.Exists("test.db")) File.Delete("test.db");
_factory = Fluently.Configure()
.Database(() => SQLiteConfiguration.Standard
.UsingFile("test.db")
.ShowSql()
.FormatSql())
.Mappings(mappings => mappings.FluentMappings
.AddFromAssemblyOf<PersonMap>())
.ExposeConfiguration(config =>
{
var exporter = new SchemaExport(config);
exporter.Execute(true, true, false);
})
.BuildSessionFactory();
}
[TestFixtureTearDown]
public void TearDownFixture()
{
_factory.Close();
}
#endregion
#region Setup/Teardown for each test
[SetUp]
public void SetUpTest()
{
}
[TearDown]
public void TearDownTest()
{
}
#endregion
[Test]
public void Should_create_and_retrieve_Person()
{
var expected = new Person
{
FirstName = "Mike",
LastName = "G"
};
using (var session = _factory.OpenSession())
using (var tx = session.BeginTransaction())
{
session.SaveOrUpdate(expected);
tx.Commit();
}
expected.Id.Should().BeGreaterThan(0);
using (var session = _factory.OpenSession())
using (var tx = session.BeginTransaction())
{
var actual = session.Get<Person>(expected.Id);
actual.Should().NotBeNull();
actual.ShouldHave().AllProperties().EqualTo(expected);
}
}
[Test]
public void Should_create_and_retrieve_Roles()
{
// Arrange
var expected = new Person
{
FirstName = "Mike",
LastName = "G"
};
var expectedManager = new Manager
{
Division = "One",
Status = "Active"
};
var expectedUser = new User
{
LoginName = "mikeg",
Password = "test123"
};
Person actual;
// Act
expected.AddRole(expectedManager);
expected.AddRole(expectedUser);
using (var session = _factory.OpenSession())
using (var tx = session.BeginTransaction())
{
session.SaveOrUpdate(expected);
tx.Commit();
}
using (var session = _factory.OpenSession())
using (var tx = session.BeginTransaction())
{
actual = session.Get<Person>(expected.Id);
// ignore this; just forcing the Roles collection to be lazy loaded before I kill the session.
actual.Roles.Count();
}
// Assert
actual.Roles.OfType<Manager>().First().Should().Be(expectedManager);
actual.Roles.OfType<Manager>().First().ShouldHave().AllProperties().But(c => c.Person).EqualTo(expectedManager);
actual.Roles.OfType<User>().First().Should().Be(expectedUser);
actual.Roles.OfType<User>().First().ShouldHave().AllProperties().But(c => c.Person).EqualTo(expectedUser);
}
}
If you want to constrain a Person to one instance of a particular role, just put a unique index and mess with the Equals method to check if Id is the same OR RoleName is the same.
You can easily get or check a user's role of any type:
if (person.Roles.OfType<User>().Any())
{
var user = person.Roles.OfType<User>().FirstOrDefault();
}
You can also query roles directly to look up their Person:
var peopleWhoAreManagersInDistrictOne = (from role in session.Query<Manager>()
where role.District == "One"
select role.Person);
You can also see that other assemblies can define additional roles. Manager is in a different assembly than Models.
So, you can see this will do everything you want plus more, despite the fact that it uses a different approach.

Categories