I recently got into C# / Azure and have a small problem that I'd like to work out. The application works as intended but I'd like to refactor a bunch of classes because I'm sure that there is a simpler solution.
I currently have a bunch of functions to retrieve Entities from the Azure that only vary in the Type that gets retrieved, but optimally I'd only want one class like so:
public static Object Do(string RowKey, string partitionKey, string tableName)
{
var theTable = Connect.Initialize(tableName);
var retrieveOperation = TableOperation.Retrieve(
Base64.EncodeTo64(partitionKey),
Base64.EncodeTo64(RowKey));
var retrievedResult = theTable.Execute(retrieveOperation);
if (retrievedResult.Result != null) {
return retrievedResult.Result;
}
throw new ArgumentException(
String.Format("{0} not found in the Table", RowKey));
}
This, in itself, works and retrieves the required entities. However, I cannot cast the returned Object without an Error.
The Object Type that I want to cast it to implements the TableEntity-Type and matches the Result out of the table.
I know that I can cast it in the sense of
TableOperation.Retrieve<Type>(...)
but I'd really like to use a single function for this purpose which requires me to cast it on calling the function.
I suspect that the problem is related to the fact that the Result is of the Type DynamicTableEntity but I am pretty lost as to why.
Is there any way to solve this problem elegantly / is there a way to establish a parameter that holds the Type that I want as a result? (I tried it with "Type ..." but that doesn't work).
You could try something like this:
return (Entity)retrievedResult.Result;
class Entity : TableEntity
{
public string PartKey => PartitionKey;
public string RowKey => RowKey;
}
Try this,
Specify TEntity as TableEntity explicitly then cast it to generalized one
public async Task<TEntity> Get<TEntity>(string tableName, string partitionKey, string rowkey) where TEntity : TableEntity, new()
{
var table = this._tableClient.GetTableReference(tableName);
var result = await table.ExecuteAsync(TableOperation.Retrieve<TEntity>(partitionKey, rowkey));
return (TEntity)result.Result;
}
I had the same problem and the reason my cast was failing was because the Retrieve operation did not get all the columns needed by my type; by default it only got the PartitionKey, RowKey (and maybe ETag etc.)
To make my cast succeed, I specified the extra columns in the Retrieve call:
// Define the additional columns we want, and pass them into Retrieve
var columns = new List<string>(){ "Name", "Status" };
var retrieve = TableOperation.Retrieve<MyEntity>(partitionKey, rowKey, columns);
var returnedObject = await cloudTable.ExecuteAsync(retrieve).Result
// The cast succeeds
var myThing = (MyEntity)returnedObject;
For a more flexible approach, each of your TableEntity subtypes could have a static list of the columns they need public static List<string> Columns = new List<string>() { "Name", "Status" };
Then, to make your Do() generic, I think it would look something like this (not tested, you may need to do some more casting and look up some Generic magic...):
public static T Do<T>(string RowKey, string partitionKey, string tableName)
{
var theTable = Connect.Initialize(tableName);
var retrieveOperation = TableOperation.Retrieve<T>(
Base64.EncodeTo64(partitionKey),
Base64.EncodeTo64(RowKey),
T.Columns);
var retrievedResult = theTable.Execute(retrieveOperation);
if (retrievedResult.Result != null) {
return retrievedResult.Result;
}
throw new ArgumentException(
String.Format("{0} not found in the Table", RowKey));
}
For better integrity, each of your TableEntity classes should also conform to a new Interface, like ITableWithColumns stipulating that the TableEntity can provide a List<string> Columns as described above.
There's a little bit more detail on my blog post, Failure to cast an object from Azure TableOperation.Retrieve.
You can use functions with Generic Types in c#
Refer this link
You can also refer the stackoverflow example here
Related
I using the following library to do the Bulk insert. enter link description here i am trying to bulk insert a huge amount of data with its related items the solution is working fine with the first level but not inserting the children.
So, I have the following generic class
public class EFBatchOperation<TContext, T> : IEFBatchOperationBase<TContext, T>, IEFBatchOperationFiltered<TContext, T>
where T : class
where TContext : DbContext{
private ObjectContext context;
private DbContext dbContext;
private IDbSet<T> set;
private Expression<Func<T, bool>> predicate;
public EFBatchOperation(TContext context, IDbSet<T> set)
{
this.dbContext = context;
this.context = (context as IObjectContextAdapter).ObjectContext;
this.set = set;
}
public static IEFBatchOperationBase<TContext, T> For<TContext, T>(TContext context, IDbSet<T> set)
where TContext : DbContext
where T : class
{
return new EFBatchOperation<TContext, T>(context, set);
}
public BatchOperationResult InsertAll<TEntity>(IEnumerable<TEntity> items, DbConnection connection = null, int? batchSize = null) where TEntity : class, T
{
// the problem is here I want to call the current function 'InsertAll' but after changing the type of the function. passing a different type to the function. I tried the following but its not working var connectionToUse = connection ?? con.StoreConnection;
var currentType = typeof(TEntity);
var provider = Configuration.Providers.FirstOrDefault(p => p.CanHandle(connectionToUse));
if (provider != null && provider.CanInsert)
{
var mapping = EntityFramework.Utilities.EfMappingFactory.GetMappingsForContext(this.dbContext);
// use of T to get Type Mapping
var typeMapping = mapping.TypeMappings[typeof(T)];
var tableMapping = typeMapping.TableMappings.First();
var properties = tableMapping.PropertyMappings
.Where(p => currentType.IsSubclassOf(p.ForEntityType) || p.ForEntityType == currentType)
.Select(p => new ColumnMapping { NameInDatabase = p.ColumnName, NameOnObject = p.PropertyName }).ToList();
if (tableMapping.TPHConfiguration != null)
{
properties.Add(new ColumnMapping
{
NameInDatabase = tableMapping.TPHConfiguration.ColumnName,
StaticValue = tableMapping.TPHConfiguration.Mappings[typeof(TEntity)]
});
}
provider.InsertItems(items, tableMapping.Schema, tableMapping.TableName, properties, connectionToUse, batchSize);
var objectContext = ((IObjectContextAdapter)this.dbContext).ObjectContext;
var os = objectContext.CreateObjectSet<TEntity>();
var foreignKeyProperties = os.EntitySet.ElementType.NavigationProperties.Where(x => x.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many);
Type entityType = typeof(TEntity);
foreach (var foreignKeyProperty in foreignKeyProperties)
{
var childProperty = foreignKeyProperty.ToEndMember.GetEntityType();
foreach (var item in items)
{
var childValue = entityType.GetProperty(foreignKeyProperty.Name).GetValue(item);
Type childValueType = childProperty.GetType();
//MethodInfo method = typeof(EFBatchOperation).GetMethod("InsertAll");
MethodInfo method = typeof(EFBatchOperation<TContext, T>).GetMethod("InsertAll");
var newMethod = method.MakeGenericMethod(new[] { childValueType.DeclaringType });
newMethod.Invoke(this, new object[] { childValue });
// InsertAll<>(childValue, connection, batchSize);
}
}
}
}
I am calling InsertAll function as follows:
BatchOperationResult batchOperationResult = EFBatchOperation.For(context, dbSet).InsertAll(collectionOfEntitiesToInsert);
the problem is here I want to call the current function 'InsertAll' but after changing the type of the function. passing a different type to the function.
I have tried to call the function using reflection but its not working using the following code
MethodInfo method = typeof(EFBatchOperation<TContext, T>).GetMethod("InsertAll");
var newMethod = method.MakeGenericMethod(new[] { childValueType });
newMethod.Invoke(this, new object[] { childValue });
and I got the following error
GenericArguments [0], "System.Data.Entity.Core.Metadata.Edm.EntityType" for "EntityFramework.Utilities.BatchOperationResult InsertAll [TEntity] (System.Collections.Generic.IEnumerable1 [TEntity], System.Data.Common .DbConnection, System.Nullable1 [System.Int32]) "exceeds the" TEntity "type constraint.
Update:
the idea here is to insert child-related properties because the original code just inserted the main entity, not the child elements.
Also Updated the code with more code to clarify what i am trying to do here
I'm assuming that type T is some base class that all of your model entities extend? Including this childValueType?
From the error message, System.Data.Entity.Core.Metadata.Edm.EntityType does not qualify for the constraints on TEntity.
EntityType is the EF Core implementation of IEntityType. Though you have not included in your example where childValueType is defined, I believe you have assigned childValueType = [IEntityType].GetType(), where you intended childValueType = [IEntityType].ClrType.
Update, now that you have added more code. As I guessed, this; childProperty.GetType(); should be childProperty.ClrType.
Thanks, #JeremyLakeman for pointing out that I totally misread the exception message regarding int?.
Mohamed, your InsertAll method has where TEntity : class, T restriction. Even when calling it via reflection via MakeGenericMethod, you still cannot pass any arbitrary type - you have to pass a type that satisfies that restriction. This is what the error tells you: you have passed some type that does not satisfy either class, or T restriction.
That the T that comes from the class EFBatchOperation, and it apparently does not match the other entity type that InsertAll tries to process. For example, it starts with EFBatchOperation<House>, original method call is InsertAll<House> and it then tries to recurse into InsertAll<Tenant> - and fails, since Tenant probably does not comply with class,House restriction.
Is that relation between <TEntity> of InsertAll, and <T> of EFBatchOperation really needed? If not, just remove it and leave where TEntity: class. If it has to stay there for public calls, then maybe try writing a private version of InsertAll that can process any type and that doesn't require <T> and call it instead when recursing?
From the comments it looks like the actual problem is how to bulk insert a lot of rows. That's not the same as batch updates (combining multiple statements in a single script).
EF Core already batches updates and even allows modifying the default batch size. Batching 42 INSERTs into a single script will still execute 42 fully logged INSERTs though. Inserting thousands of rows will still be slow.
Bulk inserts use the same minimally logged mechanism as bcp or BULK INSERT to insert rows as fast as possible. Instead of logging every row change SQL Server will log the changes to the data pages. It's far faster than batching individual INSERT statements. Instead of caching records and changes in memory, the data is sent directly to the server in a stream.
There's no bulk update or delete mechanism, no matter what some libraries claim.
To perform bulk inserts you need the SqlBulkCopy. That class accepts either a DataTable or an IDataReader. You can create an IDataReader wrapper over any IEnumerable<T> using FastMember's ObjectReader :
var data = new List<Customer>();
....
using(var bcp = new SqlBulkCopy(connection))
using(var reader = ObjectReader.Create(data, "Id", "Name", "Description"))
{
bcp.DestinationTableName = "SomeTable";
bcp.WriteToServer(reader);
}
That's it.
ObjectReader will use the property names by default, or the list of names passed to ObjectReader.Create
By default, SqlBulkCopy doesn't use a transaction. Transaction and Bulk Copy Operations explains how to use a transaction and how to configure the batch size to commit changes in batches if desired.
I want to use a method to create an empty document in a collection and get its id from MongoDB. I was thinking about using same method for 2 different types of object types. For example:
public async Task<string> CreateObject (object x)
{
Type objectType = x.GetType();
_context.Database.
GetCollection<objectType>("CollectionName").
InsertOneAsync(x);
return x;
}
But this doesn't work as GetCollection<> will work only with entities or models thats defined already in the application and refusing to take object type dynamically according to my code. I dunno if its bad programming but is it possible to attain this or just stupid to try this?
Thanks :)
Well am not exactly sure what you are trying to achieve but what if you make your method a generic one like
public async Task<string> CreateObject<T> (T x)
{
_context.Database.
GetCollection<T>(nameof(T)).InsertOneAsync(x);
return x;
}
Why can't you use something like dynamic type?
_context.Database.
GetCollection<dynamic>("CollectionName").
InsertOneAsync(x);
Or you can use the BsonDocument class as well.
If you just want to add an empty document into a collection and get back an id in which the database server assigned it you can do the following:
var client = new MongoClient();
var database = client.GetDatabase("test");
await client.DropDatabaseAsync(database.DatabaseNamespace.DatabaseName);
var collection = database.GetCollection<BsonDocument>("collection");
var newDocument = new BsonDocument();
await collection.InsertOneAsync(newDocument);
var id = newDocument["_id"].AsObjectId;
Console.WriteLine($"_id is '{id}'");
I'm wondering, if it's even possible in the first place, how I would go about querying the database (using EF) using an ID and a table name.
For example, writing a function as:
QueryDynamicData(string tableName, long entityID){return GetItem(tableName, entityID);}
And could be called like:
var entry = QueryDynamicData("Person", 143);
To clarify, this is for a MVC ASP.Net project using Entity Frameworks.
Thanks in advance!
EDIT:
Following the example from #JPVenson, I came up with the following code. Note that it returns a list of Dictionaries, even though Id is unique, since I'm thinking ahead to when we may want to get all results for a dynamic table instead of just by Id. (This is only proof of concept level)
public List<Dictionary<string, object>> QueryDynamicData(string table, int entityID)
{
try
{
//Get the table desired based on the table name passed
PropertyInfo dbSetInfo = DBContext.GetType().GetProperties().FirstOrDefault(p => p.Name.ToLower().Equals(table.ToLower()));
//Return all results from the table into IQueryable set where Id = entityID passed
IQueryable anyDbSet = ((IQueryable)dbSetInfo.GetValue(DBContext)).Where("Id=" + entityID);
List<Dictionary<string,object>> listObjects = new List<Dictionary<String, Object>>();
//Iterate through results
foreach (Object entity in anyDbSet)
{
//Create dictionary of Field Name, Field Value from results
Dictionary<string, object> listDBValues = entity.GetType().GetProperties().ToDictionary(propertyInfo => propertyInfo.Name, propertyInfo => propertyInfo.GetValue(entity));
//Add dictionary to list of dictionaries - useful for returning list of found results instead of just one
listObjects.Add(listDBValues);
}
//Return list of dictionaries
return listObjects;
}
catch (Exception e) { }
return null;
}
Yes you can. There is a blog from ScottGu
https://weblogs.asp.net/scottgu/dynamic-linq-part-1-using-the-linq-dynamic-query-library
(MS Version of DynamicLinq https://github.com/kahanu/System.Linq.Dynamic/wiki)
that contains the wiki for a lib called DynamicLinq. I'm using it currently in a Project and it will fit your approach.
You still have to wrap it and use some Reflection to build a proper IQueryable but it does a lot of work for you
Edit Code Example
With some reflection you can access your dbSet like this (Untested Pseudocode!):
public object[] QueryDynamicData(string table, int entityId) {
//Your DbContext that contains all of your
var dbContext = new FooBaa()
//Get the DbSet in your DbContext that matches the "Table" name.
//You are searching for the generic parameter of the DbSet
var dbSetInfo = dbContext.GetType().GetProperties().FirstOrDefault(e => e.GetGenericArguments().Any(f => f.Name.Equals(table));
//Now get the DbSet from the DbContext and cast it to an IQueryabe
IQueryable anyDbSet = (IQueryable)dbSetInfo.GetValue(dbContext);
//Use Dynamic Linq to create a Query that selects an ID
//warning SQL-Injection possible checkout the 2nd argument of type IDictionary
return anyDbSet.Where("Id=" + entityId).ToArray();
}
I had a few minutes to work on this between meetings, and came up with this (add it to your DbContext class:
public dynamic FindEntity(string table, long Id)
{
PropertyInfo prop = this.GetType().GetProperty(table, BindingFlags.Instance | BindingFlags.Public);
dynamic dbSet = prop.GetValue(this, null);
return dbSet.Find(Id);
}
It uses some reflection to find the property on the DbContext with the name of the table, and grabs a reference to it. Then it calls Find on that DbSet<T> to find the object with the specified primary key. Since you aren't sending any actual Type information along, everything has to be dynamically typed.
You can call it like this:
using (var db = new MyContext())
{
var e = db.FindEntity("Animals", 1);
}
I can't vouch for how useful it will be do you, but it does return (dynamically typed) data in my test setup.
I want to make a factory class to return DbContext object where table name will be passed as string. There are more than 100 tables in database, each having a different structure/schema.
The idea is to pass tableName as string in the method which will return object in EF and we can select/update these records. I found this code in some article but have some confusion how to use it:
public ObjectContext Context(EntityObject entity)
{
var relationshipManager = ((IEntityWithRelationships)entity).RelationshipManager;
var wrappedOwnerProperty = relationshipManager.GetType().GetProperty("WrappedOwner", BindingFlags.Instance | BindingFlags.NonPublic);
var wrappedOwner = wrappedOwnerProperty.GetValue(relationshipManager);
var contextProperty = wrappedOwner.GetType().GetProperty("Context");
return (ObjectContext)contextProperty.GetValue(wrappedOwner);
}
I am not sure if this is what I need. Also what should I pass in EntityObject entity and where I should pass the tableName?
Please let me know if there is any other way to achieve the same thing.
One simple way to do this is to use the DbContext.Set() method, and get the type based on the string.
using (var db = new MyDbContext)
{
string tableName = "ApplicationUser";
var type = Assembly.GetExecutingAssembly()
.GetTypes()
.FirstOrDefault(t => t.Name == tableName);
if(type != null)
DbSet catContext = context.Set(type);
}
However, this has one drawback and that's that this is a non-generic DbSet (ie it's a DbSet not a DbSet<T>).
A way to get the Generic DbSet (which allows linq functionality) would be to do something like this:
using (var db = new IdentityDbContext())
{
string tableName = "ApplicationUser";
var type = Assembly.GetExecutingAssembly()
.GetTypes().FirstOrDefault(t => t.Name == tableName);
var method = db.GetType().GetMethods()
.First(x => x.IsGenericMethod && x.Name == "Set");
MethodInfo generic = method.MakeGenericMethod(type);
var set = generic.Invoke(db, null);
}
Of course set will be an object here, and you'll have to cast it somehow, which is still part of the problem.
When it boils down to it, if you aren't going to work with statically compiled types, you're going to have to keep dealing with reflection, particularly when you have generic types to deal with (ie DbSet<T>, among others). You have to get them cast to a static type at some point to call the methods, or keep doing MethodInfo.Invoke's.
Another option is to use dynamic, but you can't use dynamics with c# extension methods (without casting to a concrete type) so you're back in the same boat of no Linq support.
Using Linq by reflection is a huge pain.
To be honest, If you have 100 classes, I'd just bite the bullet and write the hard coded types, or use a code generator to do it for you like CodeSmith.
I have two similar methods that basically does the same thing only with different objects.
What's the best way to make a generic method out of this if possible?
The two objects:
public class StoreObject {
int Key;
string Address;
string Country;
int Latitude;
int Longitude;
}
public class ProjectObject {
int ProjectKey;
string Address;
string Description;
}
The two methods that I potentially want to make into a generic:
public StoreObject GetStoreByKey(int key)
{
using (DBEntities dbe = new DBEntities())
{
StoreObject so = new StoreObject();
var storeObject = (from s in dbe.StoreTables
where s.Key == key
select s).First();
so.Key = storeObject.key;
so.Address = storeObject.address;
so.Country = storeObject.country;
so.Latitude = storeObject.latitude;
so.Longitude = storeObject.longitude;
return so;
}
}
public ProjectObject GetProjectByKey(int projectKey)
{
using (DBEntities dbe = new DBEntities())
{
ProjectObject po = new ProjectObject();
var projectObject = (from p in dbe.ProjectTables
where p.ProjectKey == projectKey
select p).First();
po.Key = projectObject.p_key;
po.Address = projectObject.p_address;
po.Description = projectObject.p_description;
return po;
}
}
I must note that:
- I have no control over how the table fields are named (ie. p_description).
- StoreTable in the DB, for example, may have other properties (like telephone, postal code, etc) but I'm only interested in showing what I've shown in the code.
- The same goes for the ProjectTable.
Well, the tricky part is that your entities have different properties, so using generics to populate the different properties within one method will not be worth it. But you can return the whole object and then just use the properties you are interested in.
public T GetEntityByKey<T>(int key)
{
using (DBEntities dbe = new DBEntities())
{
return = dbe.StoreTables.Set<T>.Find(new object[] {key});
}
}
And to use it
StoreObject so = GetEntityByKey<StoreObject>(123);
if(so != null)
{
int lat = so.Latitude;
}
You can indeed abstract out the type returned, and factor the using, but for the rest you'd need either a switch on the type requested or, reflection to pass in the fields to retrieve as parameters and the DB query to use.
The former would be bad practice and brings little to the equation, and the latter is costly and can get messy.
This is not really a good candidate for generics, unless you have many of such look-alike methods, in which case I'd go for the reflection approach.
HTH,
Bab.
It's very unlikely that this is your entire 'unit of work' and thus the use of a fresh DBEntities() context in each of these methods is probably the root of your problem here.
Creating a Repository class that includes an instance of the DBEntities class for a single web request (or whatever other unit of request you have in your application) and which has these methods in it would be a better approach to eliminating the duplicate code here. The scope of the using() is then outside these methods and hopefully tied to your web request or other unit of time.
As an option instead of creating a new class you could also extend the DBEntities partial class to include methods like these (assuming this is generated code).
You essentially have two different functionalities in each method:
Query an entity
Map that entity to another type
The first part has been addressed by Steve Mallory.
For the second part, you can use a mapper framework to handle copying values from one instance to another. Since the names of each type do not match, you'll need to tell it how to map names (in your example, adding "p_" and making it lowercase). One possibility would be Emit Mapper.
If you were to factor out all commonality, it would be something like:
public TResult GetById<TResult, TEntity>(int id)
{
using (DBEntities dbe = new DBEntities())
{
T result = dbe.StoreTables.Set<T>.Find(new object[] {key});
var mapper = ObjectMapperManager.DefaultInstance
.GetMapper<TEntity, TResult>(
new DefaultMapConfig().MatchMembers((m1, m2) => "p_" + m1.ToLower() == m2));
return mapper.Map(result);
}
}