Entity Framework Updating with Related Entity - asp.net

I'm using the EF to try to update an entity with ASP.NET. I'm creating an entity, setting it's properties then passing it back to the EF on a separate layer with the ID so the change can be applied. I'm doing this because I only store the ID of the entity when it's been bound to the UI controls.
Everything works for standard properties, but I can't update the Category.ID of a Product (a related entity). I've tried EntityKey, EntityReference and a few other but the category ID isn't saved. This is what I have:
Product product = new Product();
product.CategoryReference.EntityKey = new EntityKey("ShopEntities.Categories", "CategoryID", categoryId);
product.Name = txtName.Text.Trim();
... other properties
StockControlDAL.EditProduct(productId, product);
public static void EditProduct(int productId, Product product) {
using(var context = new ShopEntities()) {
var key = new EntityKey("ShopEntities.Products", "ProductID", productId);
context.Attach(new Product() { ProductID = productId, EntityKey = key });
context.AcceptAllChanges();
product.EntityKey = key;
product.ProductID = productId;
context.ApplyPropertyChanges("ShopEntities.Products", product);
context.SaveChanges();
}
}
I really want to use the EF but I seem to be having a few problems with using it with ASP.NET.

The reason this fails is two fold.
In order to update a Reference (i.e. Product.Category) you have to have the original reference value in the context too.
ApplyPropertyChanges(...) only applies to regular / scalar properties of the Entity, the reference is left unchanged
So I would do something like this (Note this code makes heavy use of a trick called stub entities to avoid mucking around with EntityKeys)
Product product = new Product();
// Use a stub because it is much easier.
product.Category = new Category {CategoryID = selectedCategoryID};
product.Name = txtName.Text.Trim();
... other properties
StockControlDAL.EditProduct(productId, originalCategoryID);
public static void EditProduct(Product product, int originalCategoryID ) {
using(var context = new ShopEntities())
{
// Attach a stub entity (and stub related entity)
var databaseProduct = new Product {
ProductID = product.ProductID,
Category = new Category {CategoryID = originalCategoryID}
};
context.AttachTo("Products", databaseProduct);
// Okay everything is now in the original state
// NOTE: No need to call AcceptAllChanges() etc, because
// Attach puts things into ObjectContext in the unchanged state
// Copy the scalar properties across from updated product
// into databaseProduct in the ObjectContext
context.ApplyPropertyChanges("ShopEntities.Products", product);
// Need to attach the updated Category and modify the
// databaseProduct.Category but only if the Category has changed.
// Again using a stub.
if (databaseProduct.Category.CategoryID != product.Category.CategoryID)
{
var newlySelectedCategory =
new Category {
CategoryID = product.Category.CategoryID
};
context.AttachTo("Categories", newlySelectedCategory)
databaseProduct.Category = newlySelectedCategory;
}
context.SaveChanges();
}
}
This will do the job, assuming no typos etc.

This is accepted answer to this question Strongly-Typed ASP.NET MVC with Entity Framework
context.AttachTo(product.GetType().Name, product);
ObjectStateManager stateMgr = context.ObjectStateManager;
ObjectStateEntry stateEntry = stateMgr.GetObjectStateEntry(model);
stateEntry.SetModified();
context.SaveChanges();
Have you tried out that?
[Updated, code on top does not work]
This is small extension property I used so next code block is easier to understand:
public partial class Product
{
public int? CategoryID
{
set
{
CategoryReference.EntityKey = new EntityKey("ShopEntities.Categories", "CategoryID", value);
}
get
{
if (CategoryReference.EntityKey == null)
return null;
if (CategoryReference.EntityKey.EntityKeyValues.Count() > 0)
return (int)CategoryReference.EntityKey.EntityKeyValues[0].Value;
else
return null;
}
}
}
and that worked for me (this time for sure):
System.Data.EntityKey key = new System.Data.EntityKey("ShopEntities.Products", "ProductID", productId);
object originalItem;
product.EntityKey = key;
if (context.TryGetObjectByKey(key, out originalItem))
{
if (originalItem is EntityObject &&
((EntityObject)originalItem).EntityState != System.Data.EntityState.Added)
{
Product origProduct = originalItem as Product;
origProduct.CategoryID == product.CategoryID;//set foreign key again to change the relationship status
context.ApplyPropertyChanges(
key.EntitySetName, product);
}
}context.SaveChanges();
For sure it's looks hacky. I think that the reason is because the EF relationships have status as entities (modified, added, deleted) and based on that status EF changes the value of foreign keys or deletes row if many to many relationship is in case. For some reason (don't know why) the relationship status is not changed the same as property status. That is why I had to set the CategoryReference.EntityKey on originalItem in order to change the status of the relationship.

Related

Razor, ASP.NET Core, Entity Framework : updating some fields

I'm trying to update two entities at the same time but the change is not applying and I think that when I try to return the update entity it doesn't even found it.
Here is my Razor view:
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
return Page();
}
repositorioFamiliar.Actualizar(Familiar);
return RedirectToPage("/Familiares/DetalleFamiliar", new { IdPaciente = Familiar.IdPaciente });
}
Here is my update function:
public FamiliaresPer Actualizar(FamiliaresPer familiar)
{
var familiarActualizar = (from f in _context.Familiars.Where(p => p.IdFamiliar == familiar.IdFamiliar) select f).FirstOrDefault();
if (familiarActualizar != null)
{
familiarActualizar.Correo = familiar.Correo;
_context.Update(familiarActualizar);
_context.SaveChanges();
}
var personaActualizar = (from p in _context.Personas.Where(p => p.Id == familiar.IdPersona) select p).FirstOrDefault();
if (personaActualizar != null)
{
personaActualizar.Telefono = familiar.Telefono;
_context.Update(personaActualizar);
_context.SaveChanges();
}
var familiares = from p in _context.Familiars
from p1 in _context.Personas
where p.IdPaciente == familiar.IdPaciente
where p.IdPersona == p1.IdPersona
select new FamiliaresPer()
{
IdFamiliar = p.IdFamiliar,
IdPaciente = p.IdPaciente,
IdPersona = p1.IdPersona,
Id = p1.Id,
Nombres = p1.Nombres,
Apellidos = p1.Apellidos,
Genero = p1.Genero,
Telefono = p1.Telefono,
Parentesco = p.Parentesco,
Correo = p.Correo,
};
FamiliaresPer familiaresPer = familiares.FirstOrDefault();
return familiaresPer;
}
When I submit the form I get an error
NullReferenceException: Object reference not set to an instance of an object
And the link shows the IdPaciente = 0 when it should use the same IdPaciente of the updated entity (which the Id never changes).
In your OnPost( ) Action Method, you used repositorioFamiliar.Actualizar(Familiar);
but it looks like you didn't define 'Familiar'.
In addition, when I look at your code. I can give you an advice. Let's say your first update was done correctly and you got an error in the second update case. But you want both to be updated at the same time. Assume that the first object is updated in the database but the second one isn't. This is a problem, right? Unit of Work design pattern is very useful to solve this.
In brief, The approach should be to do SaveChanges() after both update processes are completed so there will be no changes in the database until both updates are completed.

Seeding many-to-many databases in EFCore5 with ModelBuilder?

There are many questions about seeding many-to-many relationships in Entity Framework. However, most of them are extremely old, and many-to-many behavior has changed significantly in EFCore5. The official docs recommend overriding OnModelCreating to implement ModelBuilder.Entity<>.HasData().
However, with the new many-to-many behavior (without explicit mappings), I can find no clear path to seed the intermediate tables. To use the example of this tutorial, the BookCategories class is now implicit. Therefore, there is no path to explicitly declare the intermediate table values while seeding.
I've also tried simply assigning the arrays, e.g.,:
public class Book
{
public int BookId { get; set; }
public string Title { get; set; }
public ICollection<Category> Categories { get; set; }
}
public class Category
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public ICollection<Book> Books { get; set; }
}
And then at seed time:
Book book = new Book() { BookId = 1, Title = "Brave New World" }
Category category = new Category() { CategoryId = 1, CategoryName = "Dystopian" }
category.Books = new List<Book>() { book };
book.Categories = new List<Category>() { category };
modelBuilder.Entity<Book>().HasData(book);
modelBuilder.Entity<Category>().HasData(category);
... but there are no entries created for BookCategories in the resulting migration. This was somewhat expected, as this article suggests that one must explicitly seed the intermediate table. What I want is something like this:
modelBuilder.Entity<BookCategory>().HasData(
new BookCategory() { BookId = 1, CategoryId = 1 }
);
However, again, since there is no concrete class to describe BookCategories in EFCore5, the only way I can think of to seed the table is to manually edit the migration with additional MigrationBuilder.InsertData commands, which rather defeats the purpose of seeding data via application code.
However, again, since there is no concrete class to describe BookCategories in EFCore5
Actually, as explained in the What's new link, EF Core 5 allows you to have explicit join entity
public class BookCategory
{
public int BookId { get; set; }
public EBook Book { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
and configure the many-to-many relationship to use it
modelBuilder.Entity<Book>()
.HasMany(left => left.Categories)
.WithMany(right => right.Books)
.UsingEntity<BookCategory>(
right => right.HasOne(e => e.Category).WithMany(),
left => left.HasOne(e => e.Book).WithMany().HasForeignKey(e => e.BookId),
join => join.ToTable("BookCategories")
);
This way you can use all normal entity operations (query, change tracking, data model seeding etc.) with it
modelBuilder.Entity<BookCategory>().HasData(
new BookCategory() { BookId = 1, CategoryId = 1 }
);
still having the new many-to-many skip navigations mapping.
This is probably the simplest as well as the type-safe approach.
In case you thing it's too much, using the conventional join entity is also possible, but you need to know the shared dictionary entity type name, as well as the two shadow property names. Which as you will see by convention might not be what you expect.
So, by convention the join entity (and table) name is
{LeftEntityName}{RightEntityName}
and the shadow property (and column) names are
{LeftEntityNavigationPropertyName}{RightEntityKeyName}
{RightEntityNavigationPropertyName}{LeftEntityKeyName}
The first question would be - which is the left/right entity? The answer is (not documented yet) - by convention the left entity is the one which name is less in alphabetical order. So with your example Book is left, Category is right, so the join entity and table name would be BookCategory.
It can be changed adding explicit
modelBuilder.Entity<Category>()
.HasMany(left => left.Books)
.WithMany(right => right.Categories);
and now it would be CategoryBook.
In both cases the shadow property (and column) names would be
CategoriesCategoryId
BooksBookId
So neither the table name, nor the property/column names are what you'd normally do.
And apart from the database table/column names, the entity and property names are important because you'd need them for entity operations, including the data seeding in question.
With that being said, even if you don't create explicit join entity, it's better to configure fluently the one created automatically by EF Core convention:
modelBuilder.Entity<Book>()
.HasMany(left => left.Categories)
.WithMany(right => right.Books)
.UsingEntity("BookCategory", typeof(Dictionary<string, object>),
right => right.HasOne(typeof(Category)).WithMany().HasForeignKey("CategoryId"),
left => left.HasOne(typeof(Book)).WithMany().HasForeignKey("BookId"),
join => join.ToTable("BookCategories")
);
Now you can use the entity name to access the EntityTypeBuilder
modelBuilder.Entity("BookCategories")
and you can seed it similar to normal entities with shadow FK properties with anonymous type
modelBuilder.Entity("BookCategory").HasData(
new { BookId = 1, CategoryId = 1 }
);
or for this specific property bag type entity, also with Dictionary<string, object> instances
modelBuilder.Entity("BookCategory").HasData(
new Dictionary<string, object> { ["BookId"] = 1, ["CategoryId"] = 1 }
);
Update:
People seem to misinterpret the aforementioned "extra" steps and find them redundant and "too much", not needed.
I never said they are mandatory. If you know the conventional join entity and property names, go ahead directly to the last step and use anonymous type or Dictionary<string, object>.
I already explained the drawbacks of taking that route - loosing the C# type safety and using "magic" strings out of your control. You have to be smart enough to know the exact EF Core naming conventions and to realize that if you rename class Book to EBook the new join entity/table name will change from "BookCategory" to "CategoryEBook" as well as the order of the PK properties/columns, associated indexes etc.
Regarding the concrete problem with data seeding. If you really want to generalize it (OP attempt in their own answer), at least make it correctly by using the EF Core metadata system rather than reflection and assumptions. For instance, the following will extract these names from the EF Core metadata:
public static void HasJoinData<TFirst, TSecond>(
this ModelBuilder modelBuilder,
params (TFirst First, TSecond Second)[] data)
where TFirst : class where TSecond : class
=> modelBuilder.HasJoinData(data.AsEnumerable());
public static void HasJoinData<TFirst, TSecond>(
this ModelBuilder modelBuilder,
IEnumerable<(TFirst First, TSecond Second)> data)
where TFirst : class where TSecond : class
{
var firstEntityType = modelBuilder.Model.FindEntityType(typeof(TFirst));
var secondEntityType = modelBuilder.Model.FindEntityType(typeof(TSecond));
var firstToSecond = firstEntityType.GetSkipNavigations()
.Single(n => n.TargetEntityType == secondEntityType);
var joinEntityType = firstToSecond.JoinEntityType;
var firstProperty = firstToSecond.ForeignKey.Properties.Single();
var secondProperty = firstToSecond.Inverse.ForeignKey.Properties.Single();
var firstValueGetter = firstToSecond.ForeignKey.PrincipalKey.Properties.Single().GetGetter();
var secondValueGetter = firstToSecond.Inverse.ForeignKey.PrincipalKey.Properties.Single().GetGetter();
var seedData = data.Select(e => (object)new Dictionary<string, object>
{
[firstProperty.Name] = firstValueGetter.GetClrValue(e.First),
[secondProperty.Name] = secondValueGetter.GetClrValue(e.Second),
});
modelBuilder.Entity(joinEntityType.Name).HasData(seedData);
}
Also here you don't need to know which type is "left" and which is "right", neither requires special base class or interface. Just pass sequence of entity pairs and it will properly seed the conventional join entity, e.g. with OP example, both
modelBuilder.HasJoinData((book, category));
and
modelBuilder.HasJoinData((category, book));
would do.
Update (EF Core 5.0.2)
It's working well using the name of the associative table:
builder.Entity("ContractDeclarationType").HasData(
new { ContractsId = 1L, DeclarationTypesId = 1L },
new { ContractsId = 1L, DeclarationTypesId = 2L },
new { ContractsId = 1L, DeclarationTypesId = 3L });
I ended up whipping up a generic solution to this problem based upon the answer from Ivan (thanks!). I'm now able to seed all my M2M tables with this syntax:
// Add book1 and book2 to category1:
modelBuilder.HasM2MData(new [] { book1, book2 }, new [] { category1 });
This may not be fully robust, but it should work with conventional M2M mappings.
It makes some assumptions:
T1 & T2 Inherit from some ModelBase that provides an Id property.
T1 & T2 Have exactly one ICollection<OtherType> property.
You know the correct order (which model is T1 and which is T2) — this can be discovered by running the migration for the tables first and inspecting the migration.
You're running EFCore5 RC2 or later (see this issue).
public static void HasM2MData<T1, T2>
(this ModelBuilder mb, T1[] t1s, T2[] t2s)
where T1 : ModelBase where T2 : ModelBase
{
string table = $"{typeof(T1).Name}{typeof(T2).Name}";
PropertyInfo t1Prop = GetM2MProperty<T1, T2>();
PropertyInfo t2Prop = GetM2MProperty<T2, T1>();
string t1Key = $"{t1Prop.Name}Id";
string t2Key = $"{t2Prop.Name}Id";
foreach (T1 t1 in t1s) {
foreach (T2 t2 in t2s) {
mb.Entity(table).HasData(new Dictionary<string, object>() { [t2Key] = t1.Id, [t1Key] = t2.Id });
}
}
}
// Get a property on T1 which is assignable to type ICollection<T2>, representing the m2m relationship
private static PropertyInfo GetM2MProperty<T1, T2>() {
Type assignableType = typeof(ICollection<T2>);
List<PropertyInfo> props = typeof(T1).GetProperties()
.Where(pi => pi.PropertyType.IsAssignableTo(assignableType))
.ToList();
if (props.Count() != 1) {
throw new SystemException(
$"Expected {typeof(T1)} to have exactly one column of type {assignableType}; got: {props.Count()}");
}
return props.First();
}
In the migration, we see something like:
migrationBuilder.InsertData(
table: "BookCategory",
columns: new[] { "BooksId", "CategoriesId" },
values: new object[,]
{
{ "book1", "category1" },
{ "book2", "category1" }
});

Dapper question. Getting values from returned object

Just started learning Dapper. I have an ADO.NET background. Using a demo I downloaded, I can insert/delete data from a webform into a MySql table just fine. This, however, I have searched all morning on.
In retrieving a single row from the db by ID, it doesn't return a LIST<>, it seems to be just an object (using code from the demo I downloaded). The query works, I get the object back. It has the fields: "ProductID, Description and Price".
The only way I could get the values to those three fields was like this:
System.Reflection.PropertyInfo pi = Product.GetType().GetProperty("ProductID");
System.Reflection.PropertyInfo desc = Product.GetType().GetProperty("Description");
System.Reflection.PropertyInfo price = Product.GetType().GetProperty("Price");
int _ProductID = (int)(pi.GetValue(Product, null));
string _Description = (string)(desc.GetValue(Product, null));
decimal _Price = (decimal)(price.GetValue(Product, null));
This works and gets the correct values for the three fields.
I'm used to looping through DataTables, but I just think there is probably a better way to get those values.
Is this the correct way to do this or am I missing something? I did actually read documentation and mess with this all morning before asking, too.
Some of the things I looked at seem to be very complex. I thought Dapper was supposed to simplify things.
OK, Thanks Marc. It was difficult for me to see what was supposed to be in the Dapper class files and what was supposed to be in my code behind. The original demo way of getting a product by ID had the query as .FirstOrDefault();
I changed everything to return a List<> and it all worked. I'm sure my ADO.NET is showing, but this works. In Dapper class files:
public List<Product> ProductAsList(int Id)
{
return this._db.Query<Product>("SELECT * FROM Cart_product WHERE ProductID=#Id", new { Id = Id }).**ToList()**;
}
This is just getting one row that matched the ProductID.
In page codebehind:
protected void CartItemAdd(string ProductId) // passing it the selected ProductID
{
var results = cartservice.ProductAsList(Convert.ToInt32(ProductId));
// returns that one row using Dapper ProductAsList(ProductId)
int _ProductId = 0;
string Description = string.Empty;
decimal Price = 0;
// Loop through the list and get the value of each item:
foreach (Product obj in results)
{
_ProductId = obj.ProductID;
Description = obj.Description;
Price = obj.Price;
}
// Using Dapper to insert the selected product into the shopping cart (table):
String UserName = "jbanks";
cartitem = new CartItem();
cartitem.ProductID = _ProductId;
cartitem.Quantity = 1;
cartitem.Description = Description;
cartitem.Price = Price;
cartitem.Created = DateTime.Now;
cartitem.CreatedBy = UserName;
result = cartservice.AddCartItem(cartitem);
if (result)
{
lblMessage.Text = string.Empty;
lblMessage.Text = "Successfully added a cart item";
}
}
}
It does indeed look up the product from one table and insert a selected item into another table.
Thanks again!
The main Query<T> API returns an IEnumerable<T>, which often will be a List<T>; the AsList<T>() extension method can get it back to a list without a copy, but either way: they are just T, for whatever T you asked for. If you asked for Query<Product>, then: they should be Product instances:
var results = connection.Query<Product>(someSql, someArgs); // perhaps .AsList()
foreach (Product obj in results) { // "var obj" would be fine here too
// now just use obj.ProductID, obj.Description and obj.Price
}
If that didn't work: check that you used the <T> version of Query. There is a non-generic variant too, which returns dynamic. Frankly, you should almost always use the <T> version.
Note: I'm assuming that somewhere you have something like
class Product {
public int ProductID {get;set;}
public string Description {get;set;}
public decimal Price {get;set;}
}

Why does Spring LDAP's LdapTemplate not return title, department & company attributes?

I'm using spring-ldap-core-2.3.1.RELEASE.jar over JDK 1.8 & Tomcat 8.0 to access AD information through LdapTemplate. The attributes such as title,department & company are not being returned by the ldapTemplate.search(..,.,..) method.
I'm using the following lines of code to search :-
LdapQuery ldapQuery = LdapQueryBuilder.query()
.where("objectclass").is("user")
.and("objectcategory").is("person")
.and("cn").like(strWildcardText+"*");
ldapTemplate.search(ldapQuery, new ADUserAttributesMapper());
Following is the ADUserAttributesMapper class :-
public class ADUserAttributesMapper implements AttributesMapper<ADUserBean> {
#Override
public ADUserBean mapFromAttributes(Attributes attributes) throws NamingException {
if(attributes==null) {
return null;
}
adUserBean.setName((attributes.get("name")!=null) ? attributes.get("name").get().toString() : null);
adUserBean.setCommonName((attributes.get("cn")!=null) ? attributes.get("cn").get().toString() : null);
adUserBean.setDisplayName((attributes.get("displayname")!=null) ? attributes.get("displayname").get().toString() : null);
adUserBean.setGivenName((attributes.get("givenname")!=null) ? attributes.get("givenname").get().toString() : null); // for FIRST NAME
adUserBean.setMiddleName((attributes.get("initials")!=null) ? attributes.get("initials").get().toString() : null); // for MIDDLE NAME / INITIALS
adUserBean.setLastName((attributes.get("sn")!=null) ? attributes.get("sn").get().toString() : null); // for LAST NAME
adUserBean.setDepartment((attributes.get("department")!=null) ? attributes.get("department").get().toString() : null);
adUserBean.setUserPrincipalName((attributes.get("userprincipalname")!=null) ? attributes.get("userprincipalname").get().toString() : null); // Logon Name
adUserBean.setsAMAccountName((attributes.get("samaccountname")!=null) ? attributes.get("samaccountname").get().toString() : null); // Logon Name (pre-Windows 2000)
adUserBean.setDistinguishedName((attributes.get("distinguishedname")!=null) ? attributes.get("distinguishedname").get().toString() : null);
adUserBean.setMailID((attributes.get("mail")!=null) ? attributes.get("mail").get().toString() : null);
adUserBean.setTitle((attributes.get("title")!=null) ? attributes.get("title").get().toString() : null); // Job Title
adUserBean.setTelephoneNumber((attributes.get("telephonenumber")!=null) ? attributes.get("telephonenumber").get().toString() : null);
adUserBean.setObjectCategory((attributes.get("objectcategory")!=null) ? attributes.get("objectcategory").get().toString() : null);
return adUserBean;
}
}
The title,department & company attributes belong to the Organization tab of the AD user properties as shown in the below image :-
Also, from the General tab the initials(initials) attribute is not being picked up/listed by Spring-LDAP's ldapTemplate. The LdapQueryBuilder.query() object has access to attributes(...) method that takes a string array of attribute names that are to be fetched. But even after mentioning them there explicitly, values for attributes such as initials, title, department & company are not returned.
The LDAP Browser plugin within the Eclipse IDE lists the title,department & company properties under the Organization tab without a problem.
Even the com4j API returns the title, department & company attributes.
Is there any configuration that is limiting the attribute(s) listing or is it a limitation with Spring-LDAP API itself? Are these attributes not part of BasicAttributes? How to fetch these attributes through Spring-LDAP?
UPDATE (01-Aug-2017):
The plain Java JNDI approach/code does NOT return department,company,title attributes (even with these attributes being explicitly mentioned in attributes string array), but surprisingly it does return the initials attribute value.
UPDATE (02-Aug-2017):
Similar to #Pierre's suggestion (below) tried the following code using SearchControls object :-
String strFilter= "(&(objectclass=top)(cn=cgma*))";
String[] attrs = new String[] {"cn","givenName","sn","initials","title","department","company"};
long maxResults = 10; // for example
SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
searchControls.setReturningAttributes(attrs);
searchControls.setCountLimit(maxResults);
List<String> aLstOfADUsers = ldapTemplate.search("",strFilter,searchControls,new AttributesMapper<String>()
{
public String mapFromAttributes(Attributes attrs) throws NamingException {
try
{
System.out.println(attrs.toString());
return attrs.get("cn").get().toString();
}
catch(Exception ex) {
ex.printStackTrace();
return null;
}
}
});
return aLstOfADUsers;
Even this does not return the initials, title, company & department attribute values.
The person attributes might be internal attributes which you wouldn't get back by default. You can specify explicitly which attributes you want returned BUT not in the search method you're using (the one where you pass in an LdapQuery object). If you take a look at the org.springframework.ldap.core.LdapTemplate class, it doesn't seem like you can pass in the SearchControls object to the method signature you're using. So, to be able to specify attributes to fetch, replace this:
LdapQuery ldapQuery = LdapQueryBuilder.query()
.where("objectclass").is("user")
.and("objectcategory").is("person")
.and("cn").like(strWildcardText+"*");
ldapTemplate.search(ldapQuery, new ADUserAttributesMapper());
With this:
LikeFilter filter = new LikeFilter("cn", strWildcardText+"*");
// list of attributes to retrieve
String[] attrs = new String[] {"title","department","company"};
long maxResults = 10; // for example
SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
searchControls.setReturningAttributes(attrs);
searchControls.setCountLimit(numResults);
ldapTemplate.search(DistinguishedName.EMPTY_PATH, filter.encode(), searchControls, new ADUserAttributesMapper());
The above should work. You could also try something like this (I haven't tried that yet):
ldapTemplate.search( "dc=yourorg,dc=com",
"(&(cn=" +strWildcardText + "*)(&(objectClass=person)(objectcategory=person)))",
SearchControls.SUBTREE_SCOPE,
new String[]{ "title","department","company" },
new ADUserAttributesMapper() );
Finally, to get ALL attributes back, ask to retrieve ALL attributes in the code above (my above example only asked for 3 attributes, this would return ALL of them):
String[] attrs = new String[]{"*","+"};
This is based on your AttributesMapper. I don't know what ADUserAttributesMapper is, so you'd have to provide that implementation.
Here's the javadoc for this interface. http://docs.spring.io/spring-ldap/docs/current/apidocs/org/springframework/ldap/core/AttributesMapper.html
Change ldap port from 3268 to 389

How to handle multiple session variables in ASP .Net?

As a part of online shopping, I implemented a cart using Session.
I have implemented the Cart in the following manner :
Session[pname] = qty;
where pname is a string variable which holds the name of the product and I used that as the key. qty is an integer variable which holds the number of items of that particular product.
To display the cart items I simply used the following loop :
foreach(string keys in Session.Keys)
Through this I get the names of the products along with the associated quantity and using this I display the cart items. The problem arises when I also have a session for the user active on the same page.
Session["uname"] = user_name;
And while retrieving the keys using Session.Keys, the uname gets included which I don't want as I need only the product's names. Is there any way I can read the keys from Session[pname] without reading from Session["uname"]?
Instead of storing an object in session for each product and quantity, just store a single object (e.g. List) which contains all of your cart items.
Here is an example which you could tweak to meet your needs:
First, a simple object to store the data:
public class CartItem {
public string Name { get; set; }
public int Quantity { get; set; }
}
Then if you need to add an object to the cart list:
var cartItems = new List<CartItem>();
cartItems.Add(new CartItem() {
Name = "",
Quantity = 1
});
Session["Cart"] = cartItems;
//Need to fetch the cart items later on?
cartItems = (List<CartItem>)Session["Cart"];
Obviously this can be implemented differently and this was just a quick example.
You mentioned needing an easier fix than what Justin Helgerson said, so here's a couple of suggestions, but they feel a little quick and dirty. Justin's is probably the superior solution. I used a quick Console app to demonstrate this, so place your constants where they belong, and you obviously don't have to create a dictionary.
const string USERSESSION = "uname";
Dictionary<string, object> session = new Dictionary<string, object>();
session["item1"] = 2;
session["item2"] = 1;
session[USERSESSION] = "StackOverflowUser";
// print cart items - minus the user name session key
foreach (string key in session.Keys.Where(s => s != USERSESSION))
{
Console.WriteLine("Key: {0} Value: {1}", key, session[key]);
}
Alternatively, if you plan on there being more keys than just "uname", use the Linq Except method.
// build up except set
List<string> exceptKeys = new List<string>
{
USERSESSION
};
foreach (string key in session.Keys.Except(exceptKeys))
{
Console.WriteLine("Key: {0} Value: {1}", key, session[key]);
}

Resources