Should I unit test by using database simulations - c#

I'm currently in the process of writing various unit tests for an application.
Now, I do have tests to check if the code is working, which it is. But should I also simulate, for example:
Database Unavailable.
Database query returns null.
Database query takes very long time to execute.
I know this is not a coding question, but I would like to have some general thoughts about it.
If it's needed, I thought of the following approach:
SettingsUnavailableMock.Setup(x => x.PageRepository.All()).Throws(new Exception());
SettingsUnavailableMock.Setup(x => x.PageRepository.Get(It.IsAny<int>())).Throws(new Exception());
SettingsUnavailableMock.Setup(x => x.PageRepository.Get(It.IsAny<string>())).Throws(new Exception());
SettingsUnavailableMock.Setup(x => x.PageRepository.Refresh(It.IsAny<Page>())).Throws(new Exception());
SettingsUnavailableMock.Setup(x => x.PageRepository.Save()).Throws(new Exception());
Off course, add for all the repositories.
Then in my test class, I can just choose which Mock I would like to use.

Ideally you should test all of the above; however, it depends on your circumstances. Personally I always test everything that I can feasibly test.
Queries that take a long time are very realistic.
Databases being unavialable is also very realistic.
The Query returning null I'm not so sure about; however, if that is a realistic scenario then by all means stub it and test it.
Update - based on comments I thought this would be a good thing to add
public interface IRepository<T> where T : IRepositoryEntry, new()
{
event EventHandler<RepositoryOperationEventArgs> InsertEvent;
event EventHandler<RepositoryOperationEventArgs> UpdateEvent;
event EventHandler<RepositoryOperationEventArgs> DeleteEvent;
IList<String> PrimaryKeys { get; }
void Insert(T Entry);
void Update(T Entry);
void Delete(Predicate<T> predicate);
bool Exists(Predicate<T> predicate);
T Retrieve(Predicate<T> predicate);
IEnumerable<T> RetrieveAll();
}
public interface IRepositoryEntry
{
IList<String> GetPrimaryKeys();
}
public class OracleRepository
{
const string ConnectionString = "*"
public static IDbConnection GetIDbConnection()
{
IDbConnection connection = new OracleConnection(ConnectionString).OpenConnection();
return connection;
}
public IDbConnection GetConnection()
{
IDbConnection connection = new OracleConnection(ConnectionString).OpenConnection();
return connection;
}
}
public class OracleRepository<T> : OracleRepository, IDisposable, IRepository<T> where T : RepositoryEntryBase, IRepositoryEntry, new()
{
/// <summary>
/// Gets all property names from a type.
/// </summary>
/// <returns>IEnumerable of strings</returns>
static IEnumerable<String> GetEntryPropertyNames(Type type)
{
foreach (var propInfo in type.GetProperties())
yield return propInfo.Name;
}
public event EventHandler<RepositoryOperationEventArgs> InsertEvent;
public event EventHandler<RepositoryOperationEventArgs> UpdateEvent;
public event EventHandler<RepositoryOperationEventArgs> DeleteEvent;
#region Properties
public IList<String> PrimaryKeys
{
get
{
return primaryKeys.AsReadOnly();
}
private set
{
primaryKeys = new List<String>(value);
}
}
public IList<String> Properties { get; private set; }
public String InsertText { get; private set; }
public String UpdateText { get; private set; }
public String DeleteText { get; private set; }
public String SelectText { get; private set; }
#endregion
#region Fields
IDbConnection connection;
IDbTransaction transaction;
List<String> primaryKeys;
#endregion
#region Constructors
public OracleRepository()
{
PrimaryKeys = new List<String>(new T().GetPrimaryKeys());
Properties = new List<String>(GetEntryPropertyNames(typeof(T))).AsReadOnly();
InsertText = GenerateInsertText();
UpdateText = GenerateUpdateText();
SelectText = GenerateSelectText();
DeleteText = GenerateDeleteText();
connection = GetConnection();
}
#endregion
#region Interface Implementations
public void Insert(T Entry)
{
Insert(connection, Entry);
}
public void Update(T Entry)
{
Update(connection, Entry);
}
public void Delete(Predicate<T> predicate)
{
Delete(connection, predicate);
}
public T Retrieve(Predicate<T> predicate)
{
return Retrieve(connection, predicate);
}
public bool Exists( Predicate<T> predicate)
{
return Exists(connection, predicate);
}
public IEnumerable<T> RetrieveAll()
{
return RetrieveAll(connection);
}
public void Dispose()
{
if (transaction != null)
transaction.Dispose();
connection.Dispose();
}
#endregion
#region Public Methods
public void StartTransaction()
{
if (transaction != null)
throw new InvalidOperationException("Transaction is already set. Please Rollback or commit transaction");
transaction = connection.BeginTransaction();
}
public void CommitTransaction()
{
transaction.Commit();
transaction.Dispose();
transaction = null;
}
public void RollbackTransaction()
{
transaction.Rollback();
transaction.Dispose();
transaction = null;
}
public void Insert(IDbConnection connection, T Entry)
{
Type type = typeof(T);
List<Object> args = new List<Object>();
for (int i = 0; i < Properties.Count; i++)
args.Add(type.GetProperty(Properties[i]).GetValue(Entry));
connection.NonQuery(InsertText, args.ToArray());
if (InsertEvent != null)
InsertEvent(this, new OracleRepositoryOperationEventArgs() { Entry = Entry, Transaction = (transaction != null) });
}
public void Update(IDbConnection connection, T Entry)
{
Type type = typeof(T);
List<Object> args = new List<Object>();
foreach (var propertyName in Properties.Where(p => !PrimaryKeys.Any(k => k == p)))
args.Add(type.GetProperty(propertyName).GetValue(Entry));
foreach (var PropertyName in PrimaryKeys)
args.Add(type.GetProperty(PropertyName).GetValue(Entry));
connection.NonQuery(UpdateText, args.ToArray());
if (UpdateEvent != null)
UpdateEvent(this, new OracleRepositoryOperationEventArgs() { Entry = Entry, Transaction = (transaction != null) });
}
public void Delete(IDbConnection connection, Predicate<T> predicate)
{
var entryList = RetrieveAll(connection).Where(new Func<T, bool>(predicate));
Type type = typeof(T);
foreach(var entry in entryList)
{
List<Object> args = new List<Object>();
foreach (var PropertyName in PrimaryKeys)
args.Add(type.GetProperty(PropertyName).GetValue(entry));
connection.NonQuery(DeleteText, args.ToArray());
if (DeleteEvent != null)
DeleteEvent(this, new OracleRepositoryOperationEventArgs() { Entry = null, Transaction = (transaction != null) });
}
}
public T Retrieve(IDbConnection connection, Predicate<T> predicate)
{
return RetrieveAll(connection).FirstOrDefault(new Func<T, bool>(predicate));
}
public bool Exists(IDbConnection connection, Predicate<T> predicate)
{
return RetrieveAll(connection).Any(new Func<T, bool>(predicate));
}
public IEnumerable<T> RetrieveAll(IDbConnection connection)
{
List<T> collection = new List<T>();
var result = connection.Query(SelectText);
foreach (var row in result.Tuples)
collection.Add(RepositoryEntryBase.FromPlexQueryResultTuple(new T(), row) as T);
return collection;
}
#endregion
#region Private Methods
String GenerateInsertText()
{
String statement = "INSERT INTO {0}({1}) VALUES ({2})";
//Do first entry here becasse its unique input.
String columnNames = Properties.First();
String delimiter = ", ";
String bph = ":a";
String placeHolders = bph + 0;
//Start # 1 since first entry is already done
for (int i = 1; i < Properties.Count; i++)
{
columnNames += delimiter + Properties[i];
placeHolders += delimiter + bph + i;
}
statement = String.Format(statement, typeof(T).Name, columnNames, placeHolders);
return statement;
}
String GenerateUpdateText()
{
String bph = ":a";
String cvpTemplate = "{0} = {1}";
String statement = "UPDATE {0} SET {1} WHERE {2}";
//Can only set Cols that are not a primary Keys, Get those Columns
var Settables = Properties.Where(p => !PrimaryKeys.Any(k => k == p)).ToList();
String cvp = String.Format(cvpTemplate, Settables.First() , bph + 0 );
String condition = String.Format(cvpTemplate, PrimaryKeys.First(), bph + Settables.Count);
//These are the values to be set | Start # 1 since first entry is done above.
for (int i = 1; i < Settables.Count; i++)
cvp += ", " + String.Format(cvpTemplate, Settables[i], bph + i);
//This creates the conditions under which the values are set. | Start # 1 since first entry is done above.
for (int i = Settables.Count + 1; i < Properties.Count; i++)
condition += ", " + String.Format(cvpTemplate, PrimaryKeys[i - Settables.Count], bph + i);
statement = String.Format(statement, typeof(T).Name, cvp, condition);
return statement;
}
String GenerateDeleteText()
{
String bph = ":a";
String cvpTemplate = "{0} = {1}";
String statement = "DELETE FROM {0} WHERE {1}";
String condition = String.Format(cvpTemplate, PrimaryKeys.First(), bph + 0);
for (int i =1; i < PrimaryKeys.Count; i++)
condition += ", " + String.Format(cvpTemplate, PrimaryKeys[i], bph + i);
statement = String.Format(statement, typeof(T).Name, condition);
return statement;
}
String GenerateSelectText()
{
String statement = "SELECT * FROM {0}";
statement = String.Format(statement, typeof(T).Name);
return statement;
}
#endregion
}
This is what an element of that implements IReposistoryEntry looks like:
public class APPS : RepositoryEntryBase, IRepositoryEntry
{
public int APP_ID { get; set; }
public string AUTH_KEY { get; set; }
public string TITLE { get; set; }
public string DESCRIPTION { get; set; }
public int IS_CLIENT_CUSTOM_APP { get; set; }
public APPS() : base() {
primaryKeys.Add("APP_ID");
}
public APPS(PlexQueryResultTuple plexTuple) : base(plexTuple) { }
}
public class RepositoryEntryBase
{
public static RepositoryEntryBase FromPlexQueryResultTuple( RepositoryEntryBase reb, PlexQueryResultTuple plexTuple)
{
if (plexTuple.parent == null)
throw new NotSupportedException("This Operation is Not supported by this PlexTuple.");
Type type = reb.GetType();
var pInfo = type.GetProperties();
PlexQueryResult result = plexTuple.parent;
foreach (var p in pInfo)
{
int index = result.Tuples.IndexOf(plexTuple);
if (result[p.Name, index] == null)
continue;
var conversationType = Nullable.GetUnderlyingType(p.PropertyType) ?? p.PropertyType;
object value = Convert.ChangeType(result[p.Name, index], (result[p.Name, index] != null)?conversationType: p.PropertyType);
p.SetValue(reb, value);
}
return reb;
}
protected IList<String> primaryKeys;
public RepositoryEntryBase()
{
primaryKeys = new List<String>();
}
public RepositoryEntryBase(PlexQueryResultTuple plexTuple) : this()
{
FromPlexQueryResultTuple(this, plexTuple);
}
public IList<String> GetPrimaryKeys()
{
return primaryKeys;
}
}
Below I've posted the mock database. What is important to recogonize here is that the tests actually use the interface and I can exchange can interchange the real database with the mock one very easily. I like to reuse this code a fair amount (its actually in a dll for me). So I don't have to recode database code for every project.
public class InMemoryRepository<T> : IRepository<T> where T : IRepositoryEntry, new()
{
//RepositoryEntryBase,
public event EventHandler<RepositoryOperationEventArgs> InsertEvent;
public event EventHandler<RepositoryOperationEventArgs> UpdateEvent;
public event EventHandler<RepositoryOperationEventArgs> DeleteEvent;
public IList<String> PrimaryKeys { get; protected set; }
List<T> data;
public InMemoryRepository() {
PrimaryKeys = new List<String>(new T().GetPrimaryKeys());
data = new List<T>();
}
public void Insert(T Entry){
if(Get(Entry) != null)
throw new Exception("Duplicate Entry - Identical Key already exists");
data.Add(Entry);
if (InsertEvent != null)
InsertEvent(this, new RepositoryOperationEventArgs() { Entry = Entry });
}
public void Update(T Entry){
var obj = Get(Entry);
if (obj == null)
throw new Exception("Object does not exist");
obj = Entry;
if (UpdateEvent != null)
UpdateEvent(this, new RepositoryOperationEventArgs() { Entry = obj });
}
public void Delete(Predicate<T> predicate)
{
data.RemoveAll(predicate);
if (DeleteEvent != null)
DeleteEvent(this, new RepositoryOperationEventArgs() { Entry = null });
}
public bool Exists(Predicate<T> predicate)
{
return data.Exists(predicate);
}
public T Retrieve(Predicate<T> predicate)
{
return data.FirstOrDefault(new Func<T, bool>(predicate));
}
public IEnumerable<T> RetrieveAll()
{
return data.ToArray();
}
T Get(T Entry)
{
//Returns Entry based on Identical PrimaryKeys
Type entryType = typeof(T);
var KeyPropertyInfo = entryType.GetProperties().Where(p => PrimaryKeys.Any(p2 => p2 == p.Name));
foreach (var v in data)
{
//Assume the objects are identical by default to prevent false positives.
Boolean AlreadyExists = true;
foreach (var property in KeyPropertyInfo)
if (!property.GetValue(v).Equals(property.GetValue(Entry)))
AlreadyExists = false;
if (AlreadyExists)
return v;
}
return default(T);
}
}

It really depends on what your code interacting with the database is doing.
The philosophy behind unit test is to test a class by itself. All its external dependencies should be mocked.
However you might also check if your class correctly uses its dependencies. This would be an interaction test
And eventually, if everything sounds good you want to check if the whole system works together. It is an integration test.
Note that there is some librairies allowing you to perform integration tests more easily, such as Specflow
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
public interface IProductRepository
{
List<Product> LoadProducts();
}
public class ProductRepository : IProductRepository
{
public List<Product> LoadProducts()
{
// database code which returns the list of product
return new List<Product>();
}
}
public class StorageStatisticsGenerator
{
private readonly IProductRepository _repository;
public StorageStatisticsGenerator(IProductRepository repository)
{
_repository = repository;
}
public int ComputeNumberOfProducts()
{
var products = _repository.LoadProducts();
return products.Count;
}
}
Given the following class you might want to test different kind of things.
[TestFixture]
public class StorageStatisticsGeneratorTests
{
private Mock<IProductRepository> _productRepository;
private StorageStatisticsGenerator _statisticGenerator;
[SetUp]
public void Setup()
{
_productRepository = new Mock<IProductRepository>();
_statisticGenerator = new StorageStatisticsGenerator(_productRepository.Object);
}
// In this test we test if the statistic generator works correctly
// This is a UNIT TEST
[Test]
public void ComputeNumberOfProducts_Should_Returns_TheCorrectCount()
{
// Arrange
_productRepository.Setup(p => p.LoadProducts()).Returns(new List<Product>
{
new Product(), new Product(), new Product()
});
// Act
int result = _statisticGenerator.ComputeNumberOfProducts();
// Assert
Assert.AreEqual(3, result);
}
// In this test we test if the statistic generator use the repository as expected
// This is an INTERACTION TEST, you could check corner case using "real life data"
[Test]
public void ComputeNumberOfProducts_Should_Use_The_Product_Repository()
{
// Arrange
_productRepository.Setup(p => p.LoadProducts()).Returns(new List<Product>
{
new Product()
});
// Act
_statisticGenerator.ComputeNumberOfProducts();
// Assert
_productRepository.Verify(p => p.LoadProducts());
}
// In this test we use the real repository this is an INTEGRATION TEST
// You can flag this kind of slow test to run only during the night for instabce
[Test, Category("Nightly")]
public void ComputeNumberOfProducts_Should_Correctly_Integrate_With_ProductRepository()
{
// Arrange
_statisticGenerator = new StorageStatisticsGenerator(new ProductRepository());
// Act
_statisticGenerator.ComputeNumberOfProducts();
// Assert
_productRepository.Verify(p => p.LoadProducts());
}
}
If you want to know more, you can read The art of unit testing

Related

What design pattern should use in this case?

I have big model, that aggragates data for buisness entity.
class BigObject
{
TypeA DataA { get;set; }
TypeB DataB { get;set; }
TypeC DataC { get;set; }
}
and have service, which fill fields of model from differents sources. Some data depends from another data
class DataService
{
public BigObject GetModel()
{
var model = new BigObject();
model.DataA = sourceServiceA.GetData();
model.DataB = sourceServiceB.GetData(model.DataA.Id);
model.DataC = sourceServiceC.GetData();
}
}
In method GetModel() I need to configure, which fields should be filled, which should not. For example, I want to fill DataA property, but don't want fill others. First idea is pass in method object BigObjectFilter
public BigObject GetModel(BigObjectFilter filter)
class BigObjectFilter
{
bool FillDataA { get; set; }
bool FillDataB { get; set; }
bool FillDataC { get; set; }
}
and initialize this object in DataService clients.
In GetObject method I was going to add conditions like
if (filter.FillDataA)
{
model.DataA = sourceServiceA.GetData();
}
if (filter.FillDataC)
{
model.DataC = sourceServiceC.GetData();
}
I see, that this solution looks like bad practice. I would like to improve this construction. How can i improve it? I can't see, how to use builder pattern in this case, because i have requeired and optional data, one depends on the other.
For the sake of simplicity let's assume that TypeA, TypeB and TypeC are int?.
We can define a command class for the BigObject with the following constructors:
class BigObjectCommand
{
private readonly Func<BigObjectFilter, bool> canExecute;
private readonly Action<BigObject>? executeWithoutParam;
private readonly Action<int, BigObject>? executeWithParam;
private readonly Expression<Func<BigObject, int?>>? dependsOn;
public BigObjectCommand(Func<BigObjectFilter, bool> canExecute, Action<BigObject> execute)
{
this.canExecute = canExecute;
this.executeWithoutParam = execute;
}
public BigObjectCommand(Func<BigObjectFilter, bool> canExecute, Action<int, BigObject> execute, Expression<Func<BigObject, int?>> dependsOn)
{
this.canExecute = canExecute;
this.executeWithParam = execute;
this.dependsOn = dependsOn;
}
}
The first constructor will be used to cover the DataA and DataC properties' initialization
The second constructor will be used to cover the initialization of DataB property
Now, we can define an Evaluate method to implement the core logic
public void Evaluate(BigObjectFilter filter, BigObject context)
{
if (!canExecute(filter))
return; //or throw exception
if (executeWithoutParam is not null)
{
executeWithoutParam(context);
return;
}
var input = dependsOn!.Compile()(context);
if (!input.HasValue)
return; //or throw exception
executeWithParam!(input.Value, context);
}
If the condition fails we don't do the assignment
If the assignment does not require any input then we simply execute it
If the assignment depends on an input then we check whether it is populated or not and depending on the result we may or may not execute the assignment
With these in our hand the GetModel can be implemented like this:
private readonly List<BigObjectCommand> Commands;
public DataService()
{
Commands = new()
{
new (filter => filter.FillDataA, (m) => m.DataA = sourceServiceA.GetData()),
new (filter => filter.FillDataB, (i, m) => m.DataB = sourceServiceB.GetData(i), m => m.DataA),
new (filter => filter.FillDataC, (m) => m.DataC = sourceServiceC.GetData()),
};
}
public BigObject GetModel(BigObjectFilter filter)
{
var model = new BigObject();
foreach (var command in Commands)
{
command.Evaluate(filter, model);
}
return model;
}
This solution is far from perfect I just wanted to share with you the basic idea how to apply the Command pattern for your problem.
Here you can a find a working example. By changing the FillDataXYZ values you can see how the Evaluate works in practice.
It looks like you have at least two choices here:
use some collection which stores value to handle
an approach inspired by Chain-of-responsibility pattern
Let's start from collection which stores value to handle
At first, we need our class with properties to be filled:
public class BigObject
{
public int A { get; set; }
public int B { get; set; }
public int C { get; set; }
}
Then this is our class which will handle all your properties:
public class BigObjectHandler
{
Dictionary<string, Action> _handlerByproperty = new ();
BigObject _bigObject;
public BigObjectHandler(BigObject bigObject)
{
_bigObject = bigObject;
_handlerByproperty.Add("A", GetDataA);
_handlerByproperty.Add("B", GetDataB);
_handlerByproperty.Add("C", GetDataC);
}
public void Handle(string propertyName) =>
_handlerByproperty[propertyName].Invoke();
private void GetDataA()
{
_bigObject.A = 1; // sourceServiceA.GetData();
}
private void GetDataB()
{
_bigObject.B = 1; // sourceServiceA.GetData();
}
private void GetDataC()
{
_bigObject.C = 1; // sourceServiceA.GetData();
}
}
And then you can call the above code like this:
IEnumerable<string> propertiesToFill = new List<string> { "A", "B" };
BigObject bigObject = new ();
BigObjectHandler bigObjectMapHandler = new (bigObject);
foreach (var propertyToFill in propertiesToFill)
{
bigObjectMapHandler.Handle(propertyToFill);
}
OUTPUT:
A = 1
B = 1
Chain-of-responsibility pattern
If you have many if else statements, then you can try to use "Chain-of-responsibility pattern". As wiki says:
the chain-of-responsibility pattern is a behavioral design pattern
consisting of a source of command objects and a series of processing
objects. Each processing object contains logic that defines the
types of command objects that it can handle; the rest are passed to
the next processing object in the chain. A mechanism also exists for
adding new processing objects to the end of this chain
However, we will not stop execution if some of condition is met. Let me show an example.
At first, we need some abstraction of handler:
public abstract class BigObjectHandler
{
private BigObjectHandler _nextBigObjectHandler;
public void SetSuccessor(BigObjectHandler bigObjectHandler)
{
_nextBigObjectHandler = bigObjectHandler;
}
public virtual BigObject Execute(BigObject bigObject,
BigObjectFilter parameter)
{
if (_nextBigObjectHandler != null)
return _nextBigObjectHandler.Execute(bigObject, parameter);
return bigObject;
}
}
Then we need concrete implemenatation of these handlers for your properties. This properties will be filled
by your sourceServiceX.GetData():
public class BigObjectAHandler : BigObjectHandler
{
public override BigObject Execute(BigObject bigObject, BigObjectFilter filter)
{
if (filter.FillA)
{
bigObject.A = 1; // sourceServiceA.GetData();
}
return base.Execute(bigObject, filter);
}
}
And:
public class BigObjectBHandler : BigObjectHandler
{
public override BigObject Execute(BigObject bigObject, BigObjectFilter filter)
{
if (filter.FillB)
{
bigObject.B = 2; // sourceServiceB.GetData();
}
return base.Execute(bigObject, filter);
}
}
And:
public class BigObjectCHandler : BigObjectHandler
{
public override BigObject Execute(BigObject bigObject, BigObjectFilter filter)
{
if (filter.FillC)
{
bigObject.C = 3; // sourceServiceC.GetData();
}
return base.Execute(bigObject, filter);
}
}
And these are object with data:
public class BigObject
{
public int A { get; set; }
public int B { get; set; }
public int C { get; set; }
}
And some filter which will contain settings of what property should be filled:
public class BigObjectFilter
{
public bool FillA { get; set; } = true;
public bool FillB { get; set; }
public bool FillC { get; set; }
}
And then we can call the above code like this:
BigObjectHandler chain = new BigObjectAHandler();
BigObjectHandler objectBHandler = new BigObjectBHandler();
BigObjectHandler objectCHandler = new BigObjectCHandler();
chain.SetSuccessor(objectBHandler);
objectBHandler.SetSuccessor(objectCHandler);
BigObjectFilter bigObjectFilter = new BigObjectFilter();
bigObjectFilter.FillA = true;
BigObject vehicle = chain.Execute(new BigObject(), bigObjectFilter); // A = 1
It can be seen after code execution that onle property A is handled. Output is:
A = 1
B = 1

What is the best way to order methods based on a RunBefore attribute in C#

As the title suggests, I'm looking for a way to arrange methods by their attribute values in such a way that they can always be processed before the method specified in an array.
class Program
{
static void Main()
{
var classType = typeof(MethodsTest);
var methods = classType.GetMethods()
.Where(
m => m.Name.StartsWith("Test")
)
.OrderBy(/* ??? */)
.ToArray();
}
}
class MethodsTest
{
public void TestUnrelatedMethod() { }
public void TestMethod() { }
[RunBefore(nameof(TestMethod))]
public void TestBeforeMethod() { }
[RunBefore(nameof(TestBeforeMethod))]
public void TestBeforeOther() { }
}
public class RunBeforeAttribute : Attribute
{
public string methodName;
public string callerName;
public RunBeforeAttribute(string method, [CallerMemberName] string callerMember = null)
{
methodName = method;
callerName = callerMember;
}
}
As a result, the MethodInfo array should look like this:
TestBeforeOther
TestBeforeMethod
TestMethod
TestUnrelatedMethod
I'm currently standing here and don't know if this is the right way:
.OrderBy(
m => m.GetCustomAttributes(typeof(RunBeforeAttribute), false).First(),
/* */ )
)
Based on #klaus-gütter comment about Topological Sort, also based on this article with a small modifications (c# 8/9) to make compiler not to trigger warnings:
<TargetFramework>net6.0</TargetFramework>
Added AttributeUsageAttribute to the RunBeforeAttribute
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class RunBeforeAttribute : Attribute
{
public string methodName;
public string? callerName;
public RunBeforeAttribute(string method, [CallerMemberName] string? callerMember = null)
{
methodName = method;
callerName = callerMember;
}
}
Test class (based on the schema on wiki):
class MethodsTest
{
public void TestUnrelatedMethod() { }
public void TestMethod() { }
[RunBefore(nameof(Test11))]
public void Test2() { }
public void Test3() { }
public void Test5() { }
public void Test7() { }
[RunBefore(nameof(Test7)), RunBefore(nameof(Test3))]
public void Test8() { }
[RunBefore(nameof(Test8)), RunBefore(nameof(Test11))]
public void Test9() { }
[RunBefore(nameof(Test11)), RunBefore(nameof(Test3))]
public void Test10() { }
[RunBefore(nameof(Test5)), RunBefore(nameof(Test7))]
public void Test11() { }
}
Simple Node wrapper:
public class Node<T> where T : notnull
{
public T Item { get; init; }
public List<Node<T>> Dependencies { get; init; }
public Node(T item)
{
Item = item;
Dependencies = new();
}
}
Topological sorter taken from the article mentioned above
public static class TopologicalSort
{
public static IList<T> Sort<T>(IEnumerable<T> source, Func<T, IEnumerable<T>> getDependencies) where T : notnull
{
var sorted = new List<T>();
var visited = new Dictionary<T, bool>();
foreach (var item in source)
{
Visit(item, getDependencies, sorted, visited);
}
return sorted;
}
public static void Visit<T>(T item, Func<T, IEnumerable<T>> getDependencies, List<T> sorted, Dictionary<T, bool> visited) where T : notnull
{
var alreadyVisited = visited.TryGetValue(item, out bool inProcess);
if (alreadyVisited)
{
if (inProcess)
{
throw new ArgumentException("Cyclic dependency found.");
}
}
else
{
visited[item] = true;
var dependencies = getDependencies(item);
if (dependencies != null)
{
foreach (var dependency in dependencies)
{
Visit(dependency, getDependencies, sorted, visited);
}
}
visited[item] = false;
sorted.Add(item);
}
}
}
Actual main code:
class Program
{
static void Main()
{
var classType = typeof(MethodsTest);
var methods = classType.GetMethods().Where(m => m.Name.StartsWith("Test")).ToList();
var methodsWithAttributes = methods
.Select(x =>
{
var runBeforeNames = x.GetCustomAttributes(typeof(RunBeforeAttribute), true)
.Select(x => (x as RunBeforeAttribute)?.methodName)
.Where(x => !string.IsNullOrWhiteSpace(x))
.Distinct()
.ToList();
return new { methodInfo = x, runBeforeNames = runBeforeNames };
})
.ToList();
var nodesUnsorted = methodsWithAttributes.Select(x => new Node<MethodInfo>(x.methodInfo)).ToList();
nodesUnsorted.ForEach(x =>
{
var current = methodsWithAttributes.Single(z => z.methodInfo == x.Item);
var dependencyNodes = nodesUnsorted.Where(z => current.runBeforeNames.Contains(z.Item.Name)).ToList();
x.Dependencies.AddRange(dependencyNodes);
});
nodesUnsorted.ForEach(x => Console.WriteLine($"Node {x.Item.Name} <= {string.Join(", ", x.Dependencies.Select(z => z.Item.Name))}"));
var sorted = TopologicalSort.Sort(nodesUnsorted, x => x.Dependencies).ToList();
Console.WriteLine("Sorted: ");
sorted.ForEach(x => Console.WriteLine($"Node {x.Item.Name}"));
}
}
And the output:
Node TestUnrelatedMethod <=
Node TestMethod <=
Node Test2 <= Test11
Node Test3 <=
Node Test5 <=
Node Test7 <=
Node Test8 <= Test3, Test7
Node Test9 <= Test8, Test11
Node Test10 <= Test3, Test11
Node Test11 <= Test5, Test7
Sorted:
Node TestUnrelatedMethod
Node TestMethod
Node Test5
Node Test7
Node Test11
Node Test2
Node Test3
Node Test8
Node Test9
Node Test10

How to make the placehoder in Generics generic

I am using Strategy Pattern, I have heaps of rules and I need to check all rows in Azure storage table against each Rule.
interface IRule where TEntity : TableEntity, new()
{
string TableName { get; } // It could be "ContractAccount", "Bill", "Transaction" etc.
string Rule { get; }
string SaveToTable { get; }
TableQuery<TEntity> TableQuery { get; }
ReportEntity Handle(TableEntity entity);
}
So instance of rules lives inside the Validator.
public Validator()
{
Rules = new List<IRule>();
Rules.Add(new AddressRule());
}
The Table Entity class(ContractAccount.cs Bill.cs etc.) will have the same name as the value IRule.TableName holds.
So this is where the ContractAccount comes from.
Then in the Validator, I have Validate() which looks like:
public async void Validate(CloudStorageAccount storageAccount)
{
var tableClient = storageAccount.CreateCloudTableClient();
//.....
var query = new TableQuery<ContractAccount>(); //<-- I want to replace ContractAccount with something generic
//...
var rows = await tableToBeValidated.ExecuteQuerySegmentedAsync(query, token);
}
//...
}
In my AddressRule.cs
public class AddressRule : IRule<ContractAccount>
{
public string TableName => "ContractAccount";
public string Rule => "Email cannot be empty";
public string SaveToTable => "XXXX";
public TableQuery<ContractAccount> TableQuery => new TableQuery<ContractAccount>();
public ReportEntity Handle(TableEntity entity)
{
var contract = entity as ContractAccount;
if(contract == null)
{
throw new Exception($"Expecting entity type {TableName}, but passed in invalid entity");
}
if (string.IsNullOrWhiteSpace(contract.Address))
{
var report = new ReportEntity(this.Rule, contract.UserId, contract.AccountNumber, contract.ContractNumber)
{
PartitionKey = contract.UserId,
RowKey = contract.AccountNumber
};
return report;
}
return null;
}
}
As you can see
var query = new TableQuery<ContractAccount>();
I need to replace the Hard-coded with something like:
var type = Type.GetType(tableName);
var query = new TableQuery<type>();
but the placeholder(ContractAccount) will change when app is running, it could be Bill, Policy, Transaction etc....
I cannot use the <T> thing.
How can I replace the ContractAccount with a generic thing?
Update 2
After applied Juston.Another.Programmer's suggection, I got this error.
Update 3
Now I updated code to below:
interface IRule<TEntity> where TEntity : TableEntity
{
string TableName { get; }
string Rule { get; }
string SaveToTable { get; }
ReportEntity Handle(TableEntity entity);
TableQuery<TEntity> GetTableQuery();
}
Which I specified what type of class the TEntity has to be, it removes the 1st error, but the 2nd error persists:
Error CS0310 'TEntity' must be a non-abstract type with a public
parameterless constructor in order to use it as parameter 'TElement'
in the generic type or method 'TableQuery'
Update 4
I found how to fix the another error:
interface IRule<TEntity>
where TEntity : TableEntity, new()
But then, I have problem to add my AddressRule into Rules in the Validator class.
public Validator()
{
Rules = new List<IRule<TableEntity>>();
var addressRule = new AddressRule();
Rules.Add(addressRule);
}
Something like this:
var genericType = typeof(TableQuery<>);
Type[] itemTypes = { Type.GetType("MyNamespace.Foo.Entities." + tableName) };
var concretType = genericType.MakeGenericType(itemTypes);
var query = Activator.CreateInstance(concretType);
You could use reflection like #Christoph suggested, but in this case there's an easier approach. Add a TEntity generic parameter to your IRule class instead of using the TableName string property and add a GetTableQuery method to the class.
interface IRule<TEntity>
{
string Rule { get; }
string SaveToTable { get; }
ReportEntity Handle(TableEntity entity);
TableQuery<TEntity> GetTableQuery();
}
Then, in your IRule<TEntity> implementations add the correct entity. Eg for AddressRule.
public class AddressRule : IRule<ContractAcccount>
{
public string TableName => "ContractAccount";
public string Rule => "Email cannot be empty";
public string SaveToTable => "XXXX";
public ReportEntity Handle(TableEntity entity)
{
var contract = entity as ContractAccount;
if(contract == null)
{
throw new Exception($"Expecting entity type {TableName}, but passed in invalid entity");
}
if (string.IsNullOrWhiteSpace(contract.Address))
{
var report = new ReportEntity(this.Rule, contract.UserId, contract.AccountNumber, contract.ContractNumber)
{
PartitionKey = contract.UserId,
RowKey = contract.AccountNumber
};
return report;
}
return null;
}
public TableQuery<ContractAccount> GetTableQuery()
{
return new TableQuery<ContractAccount>();
}
}
Now, in your Validate method, you can use the GetTableQuery method from the IRule.
public async void Validate(CloudStorageAccount storageAccount)
{
var tableClient = storageAccount.CreateCloudTableClient();
//.....
var query = rule.GetTableQuery();
//...
var rows = await tableToBeValidated.ExecuteQuerySegmentedAsync(query, token);
}
//...
}
The longer I think about it the more I get the feeling that what you need is a generic solution and not one with generics. I guess that the table client in line
var tableClient = storageAccount.CreateCloudTableClient();
does always return something like a DataTable or an object with an IEnumerable, independently of whether you ask for a ContractAccount or a Bill. If that's the case, it might be better to have a validator that loads all the rules of all entities from the database (or through factory patterns or hardcoded) and then applies the according ones to the given entity.
Like that, the set of rules can be defined using XML or some other sort of serialization (not part of this example) and only a few rule classes are needed (I call them EntityValidationRule).
The parent of all rules for all entities could look like this:
public abstract class EntityValidationRule {
//Private Fields
private Validator validator;
//Constructors
public EntityValidationRule(String tableName, IEnumerable<String> affectedFields) {
TableName = tableName ?? throw new ArgumentNullException(nameof(tableName));
AffectedFields = affectedFields?.ToArray() ?? Array.Empty<String>();
}
//Public Properties
public String TableName { get; }
public String[] AffectedFields { get; }
public virtual String Description { get; protected set; }
//Public Methods
public Boolean IsValid(DataRow record, ref IErrorDetails errorDetails) {
if (record == null) throw new InvalidOperationException("Programming error in Validator.cs");
if (!Validator.IdentifyerComparer.Equals(record.Table.TableName, TableName)) throw new InvalidOperationException("Programming error in Validator.cs");
String myError = GetErrorMessageIfInvalid(record);
if (myError == null) return true;
errorDetails = CreateErrorDetails(record, myError);
return false;
}
//Protected Properties
public Validator Validator {
get {
return validator;
}
internal set {
if ((validator != null) && (!Object.ReferenceEquals(validator, value))) {
throw new InvalidOperationException("An entity validation rule can only be added to a single validator!");
}
validator = value;
}
}
//Protected Methods
protected virtual IErrorDetails CreateErrorDetails(DataRow record, String errorMessage) {
return new ErrorDetails(record, this, errorMessage);
}
protected abstract String GetErrorMessageIfInvalid(DataRow record);
}
and to stay with your example, the sample implementation for an empty text field check could look like this (having an intermediate class OneFieldRule):
public abstract class OneFieldRule : EntityValidationRule {
public OneFieldRule(String tableName, String fieldName) : base(tableName, new String[] { fieldName }) {
}
protected String FieldName => AffectedFields[0];
}
and like this:
public class TextFieldMustHaveValue : OneFieldRule {
public TextFieldMustHaveValue(String tableName, String fieldName) : base(tableName, fieldName) {
Description = $"Field {FieldName} cannot be empty!";
}
protected override String GetErrorMessageIfInvalid(DataRow record) {
if (String.IsNullOrWhiteSpace(record.Field<String>(FieldName))) {
return Description;
}
return null;
}
}
Then the central validator that works like a service to validate whatever entity needs to be validated I might implement like this:
public sealed class Validator {
//Private Fields
private Dictionary<String, List<EntityValidationRule>> ruleDict;
//Constructors
//The list of all rules we just have somehow...
public Validator(IEnumerable<EntityValidationRule> rules, StringComparer identifyerComparer) {
if (rules == null) throw new ArgumentNullException(nameof(rules));
if (identifyerComparer == null) identifyerComparer = StringComparer.OrdinalIgnoreCase;
IdentifyerComparer = identifyerComparer;
ruleDict = new Dictionary<String, List<EntityValidationRule>>(IdentifyerComparer);
foreach (EntityValidationRule myRule in rules) {
myRule.Validator = this;
List<EntityValidationRule> myRules = null;
if (ruleDict.TryGetValue(myRule.TableName, out myRules)) {
myRules.Add(myRule);
} else {
myRules = new List<EntityValidationRule> { myRule };
ruleDict.Add(myRule.TableName, myRules);
}
}
}
//Public Properties
public StringComparer IdentifyerComparer { get; }
//Public Methods
public Boolean IsValid(DataRow record, ref IErrorDetails[] errors) {
//Check whether the record is null
if (record == null) {
errors = new IErrorDetails[] { new ErrorDetails(record, null, "The given record is null!") };
return false;
}
//Loop through every check and invoke them
List<IErrorDetails> myErrors = null;
IErrorDetails myError = null;
foreach (EntityValidationRule myRule in GetRules(record.Table.TableName)) {
if (myRule.IsValid(record, ref myError)) {
if (myErrors == null) myErrors = new List<IErrorDetails>();
myErrors.Add(myError);
}
}
//Return true if there are no errors
if (myErrors == null) return true;
//Otherwise assign them as result and return false
errors = myErrors.ToArray();
return false;
}
//Private Methods
private IEnumerable<EntityValidationRule> GetRules(String tableName) {
if (ruleDict.TryGetValue(tableName, out List<EntityValidationRule> myRules)) return myRules;
return Array.Empty<EntityValidationRule>();
}
}
And the error details as an interface:
public interface IErrorDetails {
DataRow Entity { get; }
EntityValidationRule Rule { get; }
String ErrorMessage { get; }
}
...and an implementation of it:
public class ErrorDetails : IErrorDetails {
public ErrorDetails(DataRow entity, EntityValidationRule rule, String errorMessage) {
Entity = entity;
Rule = rule;
ErrorMessage = errorMessage;
}
public DataRow Entity { get; }
public EntityValidationRule Rule { get; }
public String ErrorMessage { get; }
}
I know this is a totally different approach as you started off, but I think the generics give you a hell of a lot of work with customized entities that have customized validators for each and every table in your database. And as soon as you add a table, code needs to be written, compiled and redistributed.

Dynamic LINQ query to get Field value from Database

is it possible?
Public String Get_Filed_By_Id(string table_Name,String Field_Name,string PK_val)
{
string strRes="";
using(mydbcontext db=new mydbcontext())
{
var x=db.table_Name.Where(p=>p.Id=PK_val).FirstOrDefault().Field_Name;
strRes=Convert.Tostring(x);
}
return strRes;
}
OR
var x=(from o in db.table_Name where o.Id=PK_val select o.Field_Name).FirstOrDefault();
Here, i'm passing Table_Name,Column_Name and the Condition value(PK_val) to Get the Column_Name from Table_Name within a Certain Condition(Id=Pk_val).
Is it possible??
Is it possible??
Yes, it is.
First, some helpers:
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace YourNamespace
{
internal static class DbHelpers
{
public static object GetColumnById(this object dbContext, string tableName, string columnName, object id)
{
var table = (IQueryable)dbContext.GetType().GetProperty(tableName).GetValue(dbContext, null);
var row = Expression.Parameter(table.ElementType, "row");
var filter = Expression.Lambda(Expression.Equal(Expression.Property(row, "Id"), Expression.Constant(id)), row);
var column = Expression.Property(row, columnName);
var selector = Expression.Lambda(column, row);
var query = Call(Where.MakeGenericMethod(row.Type), table, filter);
query = Call(Select.MakeGenericMethod(row.Type, column.Type), query, selector);
var value = Call(FirstOrDefault.MakeGenericMethod(column.Type), query);
return value;
}
private static readonly MethodInfo Select = GetGenericMethodDefinition<
Func<IQueryable<object>, Expression<Func<object, object>>, IQueryable<object>>>((source, selector) =>
Queryable.Select(source, selector));
private static readonly MethodInfo Where = GetGenericMethodDefinition<
Func<IQueryable<object>, Expression<Func<object, bool>>, object>>((source, predicate) =>
Queryable.Where(source, predicate));
private static readonly MethodInfo FirstOrDefault = GetGenericMethodDefinition<
Func<IQueryable<object>, object>>(source =>
Queryable.FirstOrDefault(source));
private static MethodInfo GetGenericMethodDefinition<TDelegate>(Expression<TDelegate> e)
{
return ((MethodCallExpression)e.Body).Method.GetGenericMethodDefinition();
}
private static object Call(MethodInfo method, params object[] parameters)
{
return method.Invoke(null, parameters);
}
}
}
and now your function:
public string Get_Field_By_Id(string table_Name, string field_Name, string PK_val)
{
using (var db = new mydbcontext())
return Convert.ToString(db.GetColumnById(table_Name, field_Name, PK_val));
}
It is not really possible with EntityFramework actually(as far as I know). If you only needed the field by its name, then you could have used #Den's proposed solution. But you want to specify the table name too as a parameter. So I suggest you to use standard Sql Connector api, and build the query string with the parameters you provide.
Check this link for usage of standard sql connector api.
I had this question too ,I know this is not exactly what you want and you need write more code but it's much cleaner than those you want to write.
Using repository pattern
For every table you should have a model class and Repository class.
Consider this code(this code from one of my project)
This is my comment table(this can be anything with or without navigation property)
public sealed class Comment
{
public string CommentText { get; set; }
public DateTime PostDate { get; set; }
public int PostId { get; set; }
public int? PageId { get; set; }
public Page Page { get; set; }
public User User { get; set; }
public string UserId { get; set; }
public int? ParentId { get; set; }
public Comment[] ChildComments { get; set; }
}
RepositoryComment
public sealed class CommentRepository : BaseRepository<Comment>
{
public CommentRepository(BabySitterContext context)
: base(context)
{
}
}
and a base class that you send your query with table name(here model) and field(you can extend clas for more functionality)
public class BaseRepository<T> where T : class
{
protected BabySitterContext Context;
private readonly PluralizationService _pluralizer = PluralizationService.CreateService(CultureInfo.GetCultureInfo("en"));
public BaseRepository(BabySitterContext context)
{
this.Context = context;
}
public bool Add(T t)
{
Context.Set<T>().Add(t);
Context.SaveChanges();
return true;
}
public bool Update(T t)
{
var entityName = GetEntityName<T>();
object originalItem;
var key = ((IObjectContextAdapter)Context).ObjectContext.CreateEntityKey(entityName, t);
if (((IObjectContextAdapter)Context).ObjectContext.TryGetObjectByKey(key, out originalItem))
{
((IObjectContextAdapter)Context).ObjectContext.ApplyCurrentValues(key.EntitySetName, t);
}
Context.SaveChanges();
return true;
}
public void Attach(T t)
{
if (t == null)
{
throw new ArgumentNullException("t");
}
Context.Set<T>().Attach(t);
Context.SaveChanges();
}
public void Remove(T t)
{
if (t == null)
{
throw new ArgumentNullException("t");
}
Context.Set<T>().Remove(t);
Context.SaveChanges();
}
public IEnumerable<T> Get(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, string includeProperties = "")
{
IQueryable<T> query = Context.Set<T>();
if (filter != null)
{
query = query.Where(filter.Expand());
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}
private string GetEntityName<TEntity>() where TEntity : class
{
return string.Format("{0}.{1}", ((IObjectContextAdapter)Context).ObjectContext.DefaultContainerName, _pluralizer.Pluralize(typeof(TEntity).Name));
}
public virtual IEnumerable<T> GetByBusinessKey(T entity)
{
return null;
}
}
For any other table just make model class and reposiotry then inherite from base class
Using code
var context = new BabySitterContext();
var _commentRepository = new CommentRepository(context);
var comment = _commentRepository.Get(x => x.PostId == id).FirstOrDefault();
No, but in this way
Public String Get_Filed_By_Id(string table_Name,String Field_Name,string PK_val)
{
string strRes="";
using(mydbcontext db=new mydbcontext())
{
var x=db.table_Name.Where(p=>p.Id=PK_val).Select(b=>b.Field_Name).FirstOrDefault();
strRes=Convert.Tostring(x);
}
return strRes;
}

Casting to generic type in non-generic method

What I'm doing now:
void Main()
{
var command1 = new PersistenceCommand(new MyIntBO());
var command2 = new PersistenceCommand(new MyGuidBO());
var command3 = new PersistenceCommand(new PersistentBO());
Console.WriteLine(command1.ToString());
Console.WriteLine(command2.ToString());
Console.WriteLine(command3.ToString());
}
public class PersistenceCommand
{
public PersistenceCommand(PersistentBO businessObject)
{
_businessObject = businessObject;
}
public override string ToString()
{
string result = _businessObject.GetType().Name;
var keyed = _businessObject as IPrimaryKeyed<int>;
if (keyed != null)
{
result += " " + keyed.Id.ToString();
}
return result;
}
private readonly PersistentBO _businessObject;
}
public interface IPrimaryKeyed<out TKey>
{
TKey Id { get; }
}
public class PersistentBO {}
public class MyIntBO : PersistentBO, IPrimaryKeyed<int>
{
public int Id { get { return 1008; } }
}
public class MyGuidBO : PersistentBO, IPrimaryKeyed<Guid>
{
public Guid Id
{
get
{
return new Guid("6135d49b-81bb-43d4-9b74-dd84c2d3cc29");
}
}
}
This prints:
MyIntBO 1008
MyGuidBO
PersistentBO
I'd like it to print:
MyIntBO 1008
MyGuidBO 6135d49b-81bb-43d4-9b74-dd84c2d3cc29
PersistentBO
What's the most elegant way to do that?
I want to support all types of keys - int, long, Guid, etc. - so I'd rather not do multiple casts. Note that not every business object implements that interface (some do not have a single primary key).
I realize I could use reflection and try to access the Id property. I was wondering if there's a better solution.
Clarification: To address #Acaz Souza and #Petar Ivanov's answers, we have dozens of classes scattered over multiple assemblies that already implement IPrimaryKeyed<T>. I do not want to break all of them by extending the interface contract. If I were designing this from scratch, their solutions would work.
Just create a non-generic interface and replace the generic one with generic abstract class. Then check for the interface:
public interface IPrimaryKeyed
{
object ObjId { get; }
}
public abstract class PrimaryKeyed<TKey> : IPrimaryKeyed
{
public object ObjId { get { return Id; } }
public abstract TKey Id { get; }
}
---
public override string ToString()
{
string result = _businessObject.GetType().Name;
var keyed = _businessObject as IPrimaryKeyed;
if (keyed != null)
{
result += " " + keyed.ObjId.ToString();
}
return result;
}
Using reflection doesn't seem like a bad way to go here.
ToString method:
// for getting the Id prop
var identProp = _businessObject.GetType().GetProperty("Id");
string result = _businessObject.GetType().Name;
if (identProp != null)
{
result += " " + identProp.GetValue(_businessObject, null).ToString();
}
The problem is in that line:
var keyed = _businessObject as IPrimaryKeyed<int>;
Your other type is not IPrimaryKeyed<int> is IPrimaryKeyed<Guid>, then the if (keyed != null) is false.
You can try do this:
static void Main()
{
var command1 = new PersistenceCommand(new MyIntBO());
var command2 = new PersistenceCommand(new MyGuidBO());
var command3 = new PersistenceCommand(new PersistentBO());
Console.WriteLine(command1.ToString());
Console.WriteLine(command2.ToString());
Console.WriteLine(command3.ToString());
Console.ReadLine();
}
public class PersistenceCommand
{
public PersistenceCommand(PersistentBO businessObject)
{
_businessObject = businessObject;
}
public override string ToString()
{
string result = _businessObject.GetType().Name;
var keyed = _businessObject as IPrimaryKeyed;
if (keyed != null)
{
result += " " + keyed.Id.ToString();
}
return result;
}
private readonly PersistentBO _businessObject;
}
public interface IPrimaryKeyed
{
object Id { get; }
}
public class PersistentBO { }
public class MyIntBO : PersistentBO, IPrimaryKeyed
{
public object Id { get { return 1008; } }
}
public class MyGuidBO : PersistentBO, IPrimaryKeyed
{
public object Id { get { return new Guid("6135d49b-81bb-43d4-9b74-dd84c2d3cc29"); } }
}

Categories

Resources