I was adding some async calls in my project when i've encountered a problem. The same call between Session and AsyncSession doesn't return my document.
Here the document :
class Company {
string Id;
string Name;
BusinessUnit BusinessUnit;
}
class BusinessUnit {
string Name;
List<BusinessUnit> BusinessUnits;
List<Employee> Employees;
}
class Employee {
string Position;
string UserId;
}
class User {
string Id;
string FullName;
}
User and Company are two collections in my RavenDb. As you can see, we have a tree of business unit in our document Company. So when i want to load a Company, i make this call :
var company = Session.Include<Employee, User>(x => x.UserId)
.Load<Company>(companyId); //Working like a charm
But when i tried to do the same with Async :
var company = await AsyncSession.Include<Employee, User>(x => x.UserId)
.LoadAsync<Company>(companyId); //company is null
var company = await AsyncSession.LoadAsync<Company>(companyId); //This is working
I can't see why it isn't working.
During my searching of answers, i've found a small difference between the implementation of MultiLoaderWithInclude and AsyncMultiLoaderWithInclude. I don't know if my issue can be resolved by these classes.
Thanks for the failing test. The underlying reason is that you are using fields in there, not properties.
This is a bug in the client which will be fixed shortly, but in the meantime you can use properties and avoid it entirely.
This won't load the related User documents in a single request.
You can do something like this:
var company = session.Include<Company, User>(x => x.BusinessUnit.Employees.Select(y => y.UserId)).Load<Company>(companyId);
Related
The tutorials on enabling authentication work all right, but what identifier should be used to store data for a user in the database? The only thing easily available is User.Name, which seems to be my email address.
I see in the database there is an AspNetUsers table with that as the UserName column, and a varchar Id column that appears to be a GUID and is the primary key. It seems like the 'Id' field is the logical value to use, but it's not readily available in my app. I found I can get to it like this:
string ID_TYPE = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier";
var id = User.Claims.Where(x => x.Type == ID_TYPE).Select(x => x.Value).FirstOrDefault();
But that seems like a weird way to go about it. Is that the proper value to use say if I want to create a 'Posts' table that has a user associated with a post?
I've looked at these pages and it seems that a lot of this might be due to Microsoft integrating the same login process with ActiveDirectory.
Is there a reason to make the id so hard to get to and the name so easy? Should I be using the name instead? Should I be careful not to let the user change their user name then?
The shortest path to UserId is:
User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
Or create extension like so if you need to access UserId a lot:
public static class ClaimsPrincipalExtensions
{
public static string GetUserId(this ClaimsPrincipal principal)
{
if (principal == null)
return null; //throw new ArgumentNullException(nameof(principal));
string ret = "";
try
{
ret = principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
}
catch (System.Exception)
{
}
return ret;
}
}
Usage:
User.GetUserId()
In your controller use dependency injection to get the user manager:
Create a class MyUser that has your extended properties
public class MyUser : IdentityUser
{
public string MyExendedInfo { get; set; }
public int MyOtherInfo {get;set;}
}
add this property to the database using migration, or manually add it.
In Startup.cs in Configure Services add:
services.AddIdentity<MyUser, IdentityRole>()
Now inject this in your controller class:
private readonly UserManager<MyUser> _userManager;
public HomeController(
UserManager<MyUser> userManager)
{
_userManager = userManager;
}
Now you can access your additional proporties and your Id (if you still need this) in your action methods like this:
var user = await _userManager.GetUserAsync(HttpContext.User);
var id = user.Id;
var myExtendedInfo = user.MyExtendedInfo;
var myOtherInfo = user.MyOtherInfo;
etc
You can also update information about your user:
user.myExtendedInfo = "some string";
user.MyOtherInfo = myDatabase.pointer;
var result = await _userManager.UpdateAsync(user);
if (!result.Succeeded)
{
//handle error
}
So as long as you want only limited additional data stored in the database, you can create a custom user class, and use the Identity system to store it for you. I would not store it myself.
If however, you need to store large information in a separate table and/or reference the user from other tables, the Id is the correct field to use and you can access it as shown above.
I don't know what the best practice is for how much information can be stored in AspNetUsers, versus in claims, versus in your own table, but since the provided table already stores things like user name, phonenumber etc, I think it is Ok to extend it like this.
I am developing a site in which nhibernate is using. that is working fine for static mapping. but problem that i apply this application on existing database. so is there any way that mapping of classes took place at run time. i mean user provide tables and column names for mapping. Thanks
From your question I interpret you saying that the POCO classes exists, but you don't know the table or column names at build time.
So, if you already had this class:
public class MyGenericClass
{
public virtual long Id { get; set; }
public virtual string Title { get; set; }
}
You could bind it to a table and columns at runtime:
string tableName; // Set somewhere else by user input
string idColumnName; // Set somewhere else by user input
string titleColumnName; // Set somewhere else by user input
var configuration = new NHibernate.Cfg.Configuration();
configuration.Configure();
var mapper = new NHibernate.Mapping.ByCode.ModelMapper();
mapper.Class<MyGenericClass>(
classMapper =>
{
classMapper.Table(tableName);
classMapper.Id(
myGenericClass => myGenericClass.Id,
idMapper =>
{
idMapper.Column(idColumnName);
idMapper.Generator(Generators.Identity);
}
);
classMapper.Property(c => c.Title,
propertyMapper =>
{
propertyMapper.Column(titleColumnName);
}
);
}
);
ISessionFactory sessionFactory = configuration.BuildSessionFactory();
ISession session = sessionFactory.OpenSession();
////////////////////////////////////////////////////////////////////
// Now we can run an SQL query over this newly specified table
//
List<MyGenericClass> items = session.QueryOver<MyGenericClass>().List();
I don't think that could be possibly with NHibernate, but you could use a workaround.
You could use a view instead a table for the NHibernate mapping.
And in runtime, you could create that View or update it with the especified user mapping you need.
For example, you define a mapping in NHibernate to a view named ViewMapped with two columns Name and Mail.
And in the other hand, the user has a table with three columns Name, SecondName, EMail.
you can create a view on runtime with the following select:
(SELECT Name + ' ' + SecondName as Name, EMail as Mail FROM tableName) AS ViewMapped
I hope that helps you, or at least leads you to a solution.
Here's my migration code:
public Migrations(IRepository<ProductPartRecord> productPartRepository, IRepository<CategoryPartRecord> categoryPartRepository)
{
_productPartRepository = productPartRepository;
_categoryPartRepository = categoryPartRepository;
}
public int Create() {
ContentDefinitionManager.AlterTypeDefinition("Category", builder => builder
.WithPart("CommonPart")
.WithPart("TitlePart")
.WithPart("AutoroutePart"));
ContentDefinitionManager.AlterTypeDefinition("Category", builder => builder
.WithPart("AutoroutePart", partBuilder => partBuilder
.WithSetting("AutorouteSettings.AllowCustomPattern", "true")
.WithSetting("AutorouteSettings.AutomaticAdjustmentOnEdit", "false")
.WithSetting("AutorouteSettings.PatternDefinitions", "[{Name:'Category Title', Pattern: 'category/{Content.Slug}', Description: 'category/category-title'}]")));
SchemaBuilder.CreateTable("CategoryPartRecord", table => table
.ContentPartRecord()
.Column<string>("Name")
.Column<string>("Description")
.Column<string>("Image")
);
ContentDefinitionManager.AlterTypeDefinition("Category", builder => builder
.WithPart("CategoryPart"));
ContentDefinitionManager.AlterTypeDefinition("Category", builder => builder
.Creatable()
.Draftable());
return 1;
}
public int UpdateFrom1() {
_categoryPartRepository.Create(new CategoryPartRecord { Name = "Category1", Description = "Description1", Image = "Image1" });
return 2;
}
UpdateFrom1 obviously attempts to insert a dummy record, but this causes nHibernate to throw this exception:
"attempted to assign id from null one-to-one property: ContentItemRecord"
The Part Record looks like:
public class CategoryPartRecord : ContentPartRecord {
public CategoryPartRecord()
{
CategoryProducts = new List<CategoryProductRecord>();
}
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual string Image { get; set; }
public virtual IList<CategoryProductRecord> CategoryProducts { get; set; }
}
Any clues as to where I'm going wrong here? Google produced nothing.
Okay, so you are creating a contentpartrecord, not a content item there. What you want is something more along the lines of:
var item = _orchardServices.ContentManager.New("Category").As<CategoryPart>();
item.Name = "Bobs Item"; // Something like that...
item.ContentItem.As<TitlePart>().Title = "Yay a title"; // This syntax may be wrong, I'm very tired
_orchardServices.ContentManager.Create(item);
_orchardServices.ContentManager.Publish(item.ContentItem);
I think that is how you would do it. Maybe you would want to look into creating content items using the import/export module, that is the more common and safe way to do it.
Not sure if the answer from Hazza works. Haven't tried that.
I usually just do this: (But not sure if it's an inferior approach in some way)
var item = _orchardServices.ContentManager.New("Category");
var cpart = item.As<CategoryPart>();
var tpart = item.As<TitlePart>();
cpart.Name = "SomeName";
tpart.Title = "SomeTitle";
_orchardServices.ContentManager.Create(item);
But to address the comment by Lawrence Johnson:
Category in this case is the content item. He is creating a new Category content item, and then extracting the corresponding CategoryPart from it.
If you are getting null when trying to extract the part you're probably missing something.
In order for this to work you need to implement the CategoryPart, CategoryPartRecord, CategoryPartHandler and CategoryPartDriver. (And of course make sure to attach your CategoryPart to you Category content item. Not certain if placement.info is required, but would add it for consistency anyway.)
You can't leave any of these out if you plan to use a Part attached to a content item.
I'm not sure if/how you can create a Part with no content item, but you can create a Record with no part and no content item (Just make sure you don't inherit ContentPartRecord in your record object). If you simply want to add a record with no part or content item, then the code in UpdateFrom1 used by Ben Power would work for creating a record. (But migration part would have to be changed, taking out the content item and part, and manually setting the Id to be a primary key for the record)
I have a very strange issue that I cannot explain. I have my base mapping with this
//This will automatically cast the row into the correct object type based on the value in AccountType
DiscriminateSubClassesOnColumn<string>("AccountType")
.Formula(String.Format("CASE AccountType WHEN {0} THEN '{1}' WHEN {2} THEN '{3}' ELSE '{4}' END",
(int)PaymentMethodType.CheckingAccount,
typeof(ACH).Name,
(int)PaymentMethodType.SavingsAccount,
typeof(ACH).Name,
typeof(CreditCard).Name));
I have looked in the logs, I have executed the sql that nhibernate is generating, and all records have the same data. There is not difference in them that would denote why this should not work.
The base class is PaymentMethodBase. I have 2 subclasses, CreditCard and ACH, which inherit from PaymentMethodBase.
Then, I have this extension
public static string PaymentMethodName(this PaymentMethodBase paymentMethod)
{
if (paymentMethod is ACH)
{
var ach = (ACH)paymentMethod;
return String.Format("{0} {1}", ach.BankName, String.Format("XXXX{0}", ach.AccountNumber.Substring(ach.AccountNumber.Length - 4)));
}
if (paymentMethod is CreditCard)
{
var creditCard = (CreditCard)paymentMethod;
return String.Format("{0} {1}", creditCard.Name, creditCard.CreditCardNumber);
}
return "Unknown Payment Method";
}
Which I call like this.
public SelectList PaymentMethodsSelectList
{
get
{
var methods = (from p in PaymentMethods
where p != null
select new
{
id = p.PaymentMethodId,
name = p.PaymentMethodName()
}).OrderBy(x => x.name);
var results = methods.ToList();
results.Insert(0, new { id = (int)NewPaymentMethods.ACH, name = "<New eCheck Account...>" });
results.Insert(0, new { id = (int)NewPaymentMethods.CreditCard, name = "<New Credit Card...>" });
return new SelectList(results, "id", "name");
}
}
This code is used by 2 models. The collection of payment methods are all coming from the same object - a customer object. The collection is mapped like this.
HasMany<PaymentMethodBase>(x => x.PaymentMethods)
.KeyColumn("CustomerId")
.Where(y => y.AccountType < 10)
.Inverse()
.Cascade.All();
So, I get the customer 2 different ways. One is that I get the customer through another object (main site). The other has the object being pulled directly by id (in an iframe). The direct by id method works every time. The other method, where I get the customer through another object, causes only the first and last payment method to cast correctly. If there are more than 2, they are left in the base class and appear in the middle of the list after the sort.
I have tried changing the parent object to just mapping the id and then getting the customer record by the ID. Failure. There is something else in the way that is causing this to happen, but only on that one model.
I suspect that the issue here is Fetching issue.
Since the extension method is running on the .Net side rather than in the "NHibernate level" it cannot run properly when the collection of payment methods is not available.
Perhaps in the direct by Id methos you have Fetching set up for the payments whereas in the indirect method the automatic fetching goes only to the Customer object but stops short before fetching the Payment methods.
Try to instruct NHibernate to pre-fetch the Payment methods for you in the indirect method.
Something like:
Session.Query<SomeObject>.Where(.....).Fetch(x => x.Customer).ThenFetch(c => c.PaymentMethods)
The problem here appeared to be the usage of extension methods within a linq statement to return a value based on the type. The issue is that it would work some places but not others, probably due to some fetching issue, as suggested by Variant.
I solved this problem by making a property in my base class like this
public virtual string DisplayName { get { return "Unknown"; } }
Then I overrode the property in my child class and added the logic that was in the extension method for that type.
public override string DisplayName { get { return String.Format("{0} {1}", Name, AccountMask); } }
I am having a minor problem with WCF service proxies where the message contains List<string> as a parameter.
I am using the 'Add Service reference' in Visual Studio to generate a reference to my service.
// portion of my web service message
public List<SubscribeInfo> Subscribe { get; set; }
public List<string> Unsubscribe { get; set; }
These are the generated properties on my MsgIn for one of my web methods.
You can see it used ArrayOfString when I am using List<string>, and the other takes List<SubscribeInfo> - which matches my original C# object above.
[System.Runtime.Serialization.DataMemberAttribute(EmitDefaultValue=false)]
public System.Collections.Generic.List<DataAccess.MailingListWSReference.SubscribeInfo> Subscribe {
get {
return this.SubscribeField;
}
set {
if ((object.ReferenceEquals(this.SubscribeField, value) != true)) {
this.SubscribeField = value;
this.RaisePropertyChanged("Subscribe");
}
}
}
[System.Runtime.Serialization.DataMemberAttribute(EmitDefaultValue=false)]
publicDataAccess.MailingListWSReference.ArrayOfString Unsubscribe {
get {
return this.UnsubscribeField;
}
set {
if ((object.ReferenceEquals(this.UnsubscribeField, value) != true)) {
this.UnsubscribeField = value;
this.RaisePropertyChanged("Unsubscribe");
}
}
}
The ArrayOfString class generated looks like this. This is a class generated in my code - its not a .NET class. It actually generated me a class that inherits from List, but didn't have the 'decency' to create me any constructors.
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0")]
[System.Runtime.Serialization.CollectionDataContractAttribute(Name="ArrayOfString", Namespace="http://www.example.com/", ItemName="string")]
[System.SerializableAttribute()]
public class ArrayOfString : System.Collections.Generic.List<string> {
}
The problem is that I often create my message like this :
client.UpdateMailingList(new UpdateMailingListMsgIn()
{
Email = model.Email,
Name = model.Name,
Source = Request.Url.ToString(),
Subscribe = subscribeTo.ToList(),
Unsubscribe = unsubscribeFrom.ToList()
});
I really like the clean look this gives me.
Now for the actual problem :
I cant assign a List<string> to the Unsubscribe property which is an ArrayOfString - even though it inherits from List. In fact I cant seem to find ANY way to assign it without extra statements.
I've tried the following :
new ArrayOfString(unsubscribeFrom.ToList()) - this constructor doesn't exist :-(
changing the type of the array used by the code generator - doesn't work - it always gives me ArrayOfString (!?)
try to cast List<string> to ArrayOfString - fails with 'unable to cast', even though it compiles just fine
create new ArrayOfString() and then AddRange(unsubscribeFrom.ToList()) - works, but I cant do it all in one statement
create a conversion function ToArrayOfString(List<string>), which works but isn't as clean as I want.
Its only doing this for string, which is annoying.
Am i missing something? Is there a way to tell it not to generate ArrayOfString - or some other trick to assign it ?
Any .NET object that implements a method named "Add" can be initialized just like arrays or dictionaries.
As ArrayOfString does implement an "Add" method, you can initialize it like this:
var a = new ArrayOfString { "string one", "string two" };
But, if you really want to initialize it based on another collection, you can write a extension method for that:
public static class U
{
public static T To<T>(this IEnumerable<string> strings)
where T : IList<string>, new()
{
var newList = new T();
foreach (var s in strings)
newList.Add(s);
return newList;
}
}
Usage:
client.UpdateMailingList(new UpdateMailingListMsgIn()
{
Email = model.Email,
Name = model.Name,
Source = Request.Url.ToString(),
Subscribe = subscribeTo.ToList(),
Unsubscribe = unsubscribeFrom.To<ArrayOfString>()
});
I prefer not to return generic types across a service boundary in the first place. Instead return Unsubscribe as a string[], and SubscriptionInfo as SubscriptionInfo[]. If necessary, an array can easily be converted to a generic list on the client, as follows:
Unsubscribe = new List<string>(unsubscribeFrom);
Subscribe = new List<SubscriptionInfo>(subscribeTo);
Too late but can help people in the future...
Use the svcutil and explicitly inform the command line util that you want the proxy class to be serialized by the XmlSerializer and not the DataContractSerializer (default). Here's the sample:
svcutil /out:c:\Path\Proxy.cs /config:c:\Path\Proxy.config /async /serializer:XmlSerializer /namespace:*,YourNamespace http://www.domain.com/service/serviceURL.asmx
Note that the web service is an ASP.NET web service ok?!
If you are using VS 2008 to consume service then there is an easy solution.
Click on the "Advanced..." button on the proxy dialog that is displayed when you add a Service Reference. In the Collection Type drop down you can select System.Generic.List. The methods returning List should now work properly.
(Hope this is what you were asking for, I'm a little tired and the question was a tad difficult for me to read.)